一. 回顾 

  做java开发的,一定对BIO,NIO,AIO通信很了解了,现在再在下面罗列一下:

同步阻塞IO(JAVA BIO): 
  同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。 
  同步非阻塞IO(Java NIO)

  同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。用户进程也需要时不时的询问IO操作是否就绪,这就要求用户进程不停的去询问。 
异步阻塞IO(Java NIO):  
  此种方式下是指应用发起一个IO操作以后,不等待内核IO操作的完成,等内核完成IO操作以后会通知应用程序,这其实就是同步和异步最关键的区别,同步必须等待或者主动的去询问IO是否完成,那么为什么说是阻塞的呢?因为此时是通过select系统调用来完成的,而select函数本身的实现方式是阻塞的,而采用select函数有个好处就是它可以同时监听多个文件句柄(如果从UNP的角度看,select属于同步操作。因为select之后,进程还需要读写数据),从而提高系统的并发性!  
(Java AIO(NIO.2))异步非阻塞IO:  
  在此种模式下,用户进程只需要发起一个IO操作然后立即返回,等IO操作真正的完成以后,应用程序会得到IO操作完成的通知,此时用户进程只需要对数据进行处理就好了,不需要进行实际的IO读写操作,因为真正的IO读取或者写入操作已经由内核完成了。

BIO、NIO、AIO适用场景分析: 
    BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。 
    NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。 
    AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。

  针对Java开发,基本上也就上面的三种通信模型了。我们都知道Linux下的五种I/O模型

1)阻塞I/O(blocking I/O)
2)非阻塞I/O (nonblocking I/O)
3) I/O复用(select 和poll) (I/O multiplexing)
4)信号驱动I/O (signal driven I/O (SIGIO))
5)异步I/O (asynchronous I/O (the POSIX aio_functions))

  前四种都是同步,只有最后一种才是异步IO。是的,异步IO是今天介绍的重点。

二. 异步IO通信版本

  异步IO通信在linux下有两个版本:glibc版本,linux内核版本。

2.1 glibc版本

2.1.1 接口和实现信息

int aio_read(struct aiocb *aiocbp);  /* 提交一个异步读 */
int aio_write(struct aiocb *aiocbp); /* 提交一个异步写 */
int aio_cancel(int fildes, struct aiocb *aiocbp); /* 取消一个异步请求(或基于一个fd的所有异步请求,aiocbp==NULL) */
int aio_error(const struct aiocb *aiocbp);        /* 查看一个异步请求的状态(进行中EINPROGRESS?还是已经结束或出错?) */
ssize_t aio_return(struct aiocb *aiocbp);         /* 查看一个异步请求的返回值(跟同步读写定义的一样) */
int aio_suspend(const struct aiocb * const list[], int nent, const struct timespec *timeout); /* 阻塞等待请求完成 */

其中,struct aiocb主要包含以下字段:
int                 aio_fildes;                 /* 要被读写的fd */
void *            aio_buf;                 /* 读写操作对应的内存buffer */
__off64_t      aio_offset;           /* 读写操作对应的文件偏移 */
size_t             aio_nbytes;             /* 需要读写的字节长度 */
int                 aio_reqprio;               /* 请求的优先级 */
struct sigevent   aio_sigevent;      /* 异步事件,定义异步操作完成时的通知信号或回调函数 */

glibc的aio实现是比较通俗易懂的:
1、异步请求被提交到request_queue中;
2、request_queue实际上是一个表结构,"行"是fd、"列"是具体的请求。也就是说,同一个fd的请求会被组织在一起;
3、异步请求有优先级概念,属于同一个fd的请求会按优先级排序,并且最终被按优先级顺序处理;
4、随着异步请求的提交,一些异步处理线程被动态创建。这些线程要做的事情就是从request_queue中取出请求,然后处理之;
5、为避免异步处理线程之间的竞争,同一个fd所对应的请求只由一个线程来处理;
6、异步处理线程同步地处理每一个请求,处理完成后在对应的aiocb中填充结果,然后触发可能的信号通知或回调函数(回调函数是需要创建新线程来调用的);
7、异步处理线程在完成某个fd的所有请求后,进入闲置状态;
8、异步处理线程在闲置状态时,如果request_queue中有新的fd加入,则重新投入工作,去处理这个新fd的请求(新fd和它上一次处理的fd可以不是同一个);
9、异步处理线程处于闲置状态一段时间后(没有新的请求),则会自动退出。等到再有新的请求时,再去动态创建;

  

2.2 linux内核版本

2.2.1 接口和实现信息

下面再来看看linux版本的异步IO。它主要包含如下系统调用接口:
int io_setup(int maxevents, io_context_t *ctxp);  /* 创建一个异步IO上下文(io_context_t是一个句柄) */
int io_destroy(io_context_t ctx);  /* 销毁一个异步IO上下文(如果有正在进行的异步IO,取消并等待它们完成) */
long io_submit(aio_context_t ctx_id, long nr, struct iocb **iocbpp);  /* 提交异步IO请求 */
long io_cancel(aio_context_t ctx_id, struct iocb *iocb, struct io_event *result);  /* 取消一个异步IO请求 */
long io_getevents(aio_context_t ctx_id, long min_nr, long nr, struct io_event *events, struct timespec *timeout)  /* 等待并获取异步IO请求的事件(也就是异步请求的处理结果) */

其中,struct iocb主要包含以下字段:
__u16     aio_lio_opcode;     /* 请求类型(如:IOCB_CMD_PREAD=读、IOCB_CMD_PWRITE=写、等) */
__u32     aio_fildes;         /* 要被操作的fd */
__u64     aio_buf;            /* 读写操作对应的内存buffer */
__u64     aio_nbytes;         /* 需要读写的字节长度 */
__s64     aio_offset;         /* 读写操作对应的文件偏移 */
__u64     aio_data;           /* 请求可携带的私有数据(在io_getevents时能够从io_event结果中取得) */
__u32     aio_flags;          /* 可选IOCB_FLAG_RESFD标记,表示异步请求处理完成时使用eventfd进行通知(百度一下) */
__u32     aio_resfd;          /* 有IOCB_FLAG_RESFD标记时,接收通知的eventfd */

其中,struct io_event主要包含以下字段:
__u64     data;               /* 对应iocb的aio_data的值 */
__u64     obj;                /* 指向对应iocb的指针 */
__s64     res;                /* 对应IO请求的结果(>=0: 相当于对应的同步调用的返回值;<0: -errno) */

  io_context_t句柄在内核中对应一个struct kioctx结构,用来给一组异步IO请求提供一个上下文。其主要包含以下字段:
struct mm_struct*     mm;             /* 调用者进程对应的内存管理结构(代表了调用者的虚拟地址空间) */
unsigned long         user_id;        /* 上下文ID,也就是io_context_t句柄的值(等于ring_info.mmap_base) */
struct hlist_node     list;           /* 属于同一地址空间的所有kioctx结构通过这个list串连起来,链表头是mm->ioctx_list */
wait_queue_head_t     wait;           /* 等待队列(io_getevents系统调用可能需要等待,调用者就在该等待队列上睡眠) */
int                   reqs_active;    /* 进行中的请求数目 */
struct list_head      active_reqs;    /* 进行中的请求队列 */
unsigned              max_reqs;       /* 最大请求数(对应io_setup调用的int maxevents参数) */
struct list_head      run_list;       /* 需要aio线程处理的请求列表(某些情况下,IO请求可能交给aio线程来提交) */
struct delayed_work   wq;             /* 延迟任务队列(当需要aio线程处理请求时,将wq挂入aio线程对应的请求队列) */
struct aio_ring_info  ring_info;      /* 存放请求结果io_event结构的ring buffer */

其中,这个aio_ring_info结构比较值得一提,它是用于存放请求结果io_event结构的ring buffer。它主要包含了如下字段:
unsigned long   mmap_base;       /* ring buffer的地始地址 */
unsigned long   mmap_size;       /* ring buffer分配空间的大小 */
struct page**   ring_pages;      /* ring buffer对应的page数组 */
long            nr_pages;        /* 分配空间对应的页面数目(nr_pages * PAGE_SIZE = mmap_size) */
unsigned        nr, tail;        /* 包含io_event的数目及存取游标 */

2.3 例子

#include <stdio.h>
#include <libaio.h>
#include <sys/eventfd.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string>
using std::string; #define TEST_FILE "./aiotestfile.txt"
#define ALIGN_SIZE 512
#define RD_SIZE 1024
#define RD_EVENT_NUM 5 bool create_test_file(const string &a_strFileName)
{
FILE *file = fopen(a_strFileName.c_str(), "w");
if(NULL == file)
{
perror("fopen");
return false;
}
fprintf(file, "my name is \n");
fprintf(file, "stars\n");
fprintf(file, "what's your name?\n");
fprintf(file, "what's your name?\n");
fprintf(file, "what's your name?\n");
fprintf(file, "what's your name?\n");
fprintf(file, "what's your name?\n");
fprintf(file, "what's your name?\n");
fprintf(file, "what's your name?\n");
fprintf(file, "what's your name?\n");
fprintf(file, "what's your name?\n");
fprintf(file, "stars\n");
fprintf(file, "stars\n");
fprintf(file, "stars\n");
fprintf(file, "stars\n");
fprintf(file, "stars\n"); fclose(file); return true;
} void aio_call_back(io_context_t ctx, struct iocb *iocb, long res, long res2)
{
printf("can read %d bytes, and in fact has read %d bytes\n", iocb->u.c.nbytes, res);
printf("the content is :\n%s", iocb->u.c.buf);
} int main(int argc, char* argv[])
{
if(!create_test_file(TEST_FILE))
{
perror("create_test_file");
return -;
} int efd = eventfd(, EFD_NONBLOCK|EFD_CLOEXEC);
if(efd == -)
{
perror("eventfd");
return -;
}
int epollfd = epoll_create();
if(epollfd == -)
{
perror("epoll_create");
return -;
}
struct epoll_event epevent;
epevent.events = EPOLLIN|EPOLLET;
epevent.data.ptr = NULL;
if(epoll_ctl(epollfd, EPOLL_CTL_ADD, efd, &epevent) != )
{
perror("epoll_ctr");
return -;
} int fd = open(TEST_FILE, O_RDWR|O_DIRECT);
if(fd < )
{
perror("open");
return -;
} void *buf;
if(posix_memalign(&buf, ALIGN_SIZE, RD_SIZE))
{
perror("posix_memalign");
return -;
} struct iocb *ocbs[RD_EVENT_NUM];
for(int i = ; i < RD_EVENT_NUM; ++i)
{
ocbs[i] = (struct iocb*)malloc(sizeof(struct iocb));
io_prep_pread(ocbs[i], fd, buf, RD_SIZE, RD_SIZE * i);
io_set_eventfd(ocbs[i], efd);
io_set_callback(ocbs[i], aio_call_back);
} io_context_t ctx = NULL;
if(io_setup(, &ctx))
{
perror("io_setup");
return -;
}
if(io_submit(ctx, RD_EVENT_NUM, ocbs) != RD_EVENT_NUM)
{
perror("io_submit");
return -;
} struct epoll_event *events_list = (struct epoll_event *)malloc(sizeof(struct epoll_event) * );
int retnum = ;
while(retnum < RD_EVENT_NUM)
{
int epnum = epoll_wait(epollfd, events_list, , -);
if(epnum <= ){
if(errno != EINTR){
perror("epoll_wait");
exit();
}
}
else
{
int ready;
int n = read(efd, &ready, sizeof(ready));
if(n != ){
perror("read error");
exit();
}
printf("finished io number: %d\n", ready); while(ready > )
{
struct timespec tms;
tms.tv_sec = ;
tms.tv_nsec = ;
struct io_event *events = (struct io_event *)malloc(sizeof(struct io_event) * RD_EVENT_NUM);
int ret = io_getevents(ctx, , RD_EVENT_NUM, events, &tms);
if(ret > )
{
for(int v = ; v < ret; ++v)
{
((io_callback_t)(events[v].data))(ctx, events[v].obj, events[v].res, events[v].res2);
}
retnum += ret;
ready -= ret;
}
}
}
} close(epollfd);
free(buf);
close(fd);
io_destroy(ctx);
close(efd);
remove(TEST_FILE); for(int m = ; m < RD_EVENT_NUM; m++)
{
free(ocbs[m]);
} return ;
}

2.4 比较

  从上面的流程可以看出,linux版本的异步IO实际上只是利用了CPU和IO设备可以异步工作的特性(IO请求提交的过程主要还是在调用者线程上同步完成的,请求提交后由于CPU与IO设备可以并行工作,所以调用流程可以返回,调用者可以继续做其他事情)。相比同步IO,并不会占用额外的CPU资源。
而glibc版本的异步IO则是利用了线程与线程之间可以异步工作的特性,使用了新的线程来完成IO请求,这种做法会额外占用CPU资源(对线程的创建、销毁、调度都存在CPU开销,并且调用者线程和异步处理线程之间还存在线程间通信的开销)。不过,IO请求提交的过程都由异步处理线程来完成了(而linux版本是调用者来完成的请求提交),调用者线程可以更快地响应其他事情。如果CPU资源很富足,这种实现倒也还不错。

  还有一点,当调用者连续调用异步IO接口,提交多个异步IO请求时。在glibc版本的异步IO中,同一个fd的读写请求由同一个异步处理线程来完成。而异步处理线程又是同步地、一个一个地去处理这些请求。所以,对于底层的IO调度器来说,它一次只能看到一个请求。处理完这个请求,异步处理线程才会提交下一个。而内核实现的异步IO,则是直接将所有请求都提交给了IO调度器,IO调度器能看到所有的请求。请求多了,IO调度器使用的类电梯算法就能发挥更大的功效。请求少了,极端情况下(比如系统中的IO请求都集中在同一个fd上,并且不使用预读),IO调度器总是只能看到一个请求,那么电梯算法将退化成先来先服务算法,可能会极大的增加碰头移动的开销。

  最后,glibc版本的异步IO支持非direct-io,可以利用内核提供的page cache来提高效率。而linux版本只支持direct-io,cache的工作就只能靠用户程序来实现了。

参考地址:

http://blog.csdn.net/u012398613/article/details/22897279 参考

http://lse.sourceforge.net/io/aio.html   使用帮助

http://www.ibm.com/developerworks/cn/linux/l-async/ 专业技术文档

http://www.jiangmiao.org/blog/2290.html API整理

http://blog.sina.com.cn/s/blog_87c80c500100yol3.html 有例子

http://hedengcheng.com/?p=98 innodb中的异步IO
http://www.pagefault.info/?p=76 nginx中的异步IO
http://stackoverflow.com/questions/6497217/c-how-to-use-both-aio-read-and-aio-write 与网络编程结合

http://blog.sina.com.cn/s/blog_3e3fcadd0100grgk.html 例子和API讲的很细

linux 异步IO通信的更多相关文章

  1. linux异步IO的两种方式【转】

    转自:https://blog.csdn.net/shixin_0125/article/details/78898146 知道异步IO已经很久了,但是直到最近,才真正用它来解决一下实际问题(在一个C ...

  2. Linux异步IO【转】

    转自:http://blog.chinaunix.net/uid-24567872-id-87676.html Linux® 中最常用的输入/输出(I/O)模型是同步 I/O.在这个模型中,当请求发出 ...

  3. Linux异步IO操作

    Linux® 中最常用的输入/输出(I/O)模型是同步 I/O.在这个模型中,当请求发出之后,应用程序就会阻塞,直到请求满足为止.这是很好的一种解决方案,因为调用应用程序在等待 I/O 请求完成时不需 ...

  4. Linux 网络编程的5种IO模型:异步IO模型

    Linux 网络编程的5种IO模型:异步IO模型 资料已经整理好,但是还有未竟之业:复习多路复用epoll 阅读例程, 异步IO 函数实现 背景 上一讲< Linux 网络编程的5种IO模型:信 ...

  5. ORACLE数据库异步IO介绍

    异步IO概念 Linux 异步 I/O (AIO)是 Linux 内核中提供的一个增强的功能.它是Linux 2.6 版本内核的一个标准特性,当然我们在2.4 版本内核的补丁中也可以找到它.AIO 背 ...

  6. JAVA基础知识之网络编程——-基于AIO的异步Socket通信

    异步IO 下面摘子李刚的<疯狂JAVA讲义> 按照POSIX标准来划分IO,分为同步IO和异步IO.对于IO操作分为两步,1)程序发出IO请求. 2)完成实际的IO操作. 阻塞IO和非阻塞 ...

  7. 同步IO和异步IO

    链接: 同步IO和异步IO socket阻塞与非阻塞,同步与异步.I/O模型 Linux的IO系统常用系统调用及分析 linux异步IO的两种方式

  8. linux异步IO--aio

    简述 linux下异步方式有两种:异步通知和异步IO(AIO),异步通知请参考:linux异步通知 Linux的I/O机制经历了一下几个阶段的演进: 1. 同步阻塞I/O: 用户进程进行I/O操作,一 ...

  9. Netty学习第二节Java IO通信

    一.Java IO通信 名词解释: BIO通信:       采用BIO通信模型的服务端,通常由一个独立的Acceptor线程负责监听客户端连接,在接收到客户端请求后,为每一个客户端建立一个新的线程负 ...

随机推荐

  1. pycurl mac 安装报错Curl is configured to use SSL,

    1.使用安装第三方插件的方式安装pycurl:pip3 install pycurl 报错提示如下: Curl is configured to use SSL, but we have not be ...

  2. Codeforces Gym100952 A.Who is the winner? (2015 HIAST Collegiate Programming Contest)

      A. Who is the winner?   time limit per test 1 second memory limit per test 64 megabytes input stan ...

  3. poj1062 最短路径 dijkstra

    题目连接:http://poj.org/problem?id=1062 Description 年轻的探险家来到了一个印第安部落里.在那里他和酋长的女儿相爱了,于是便向酋长去求亲.酋长要他用 1000 ...

  4. codeforces Round #440 A Search for Pretty Integers【hash/排序】

    A. Search for Pretty Integers [题目链接]:http://codeforces.com/contest/872/problem/A time limit per test ...

  5. Linux查看内核信息或系统信息

    先说说为什么会写这个.这是我去面试的时候面试官问的一个问题,我感觉是一个普遍会被问到的问题.为了让我自己记住,也便于收集下Linux运维方向考官的题目. 第一,查看内核信息 cat /proc/ver ...

  6. 【分块】bzoj3295 [Cqoi2011]动态逆序对

    考虑每次删除pos位置一个数x后,所造成的的影响就是,逆序对的个数少了在1~pos-1中大于x的数的个数加上pos+1~n中小于x的数的个数. 那么我们需要的操作就只有查询区间内比某数大(小)的个数. ...

  7. 【分块】【链表】bzoj2738 矩阵乘法

    http://www.cnblogs.com/jianglangcaijin/p/3460012.html 首先将矩阵的数字排序.设置size,每次将size个数字插入.插入时,我们用h[i][j]记 ...

  8. virtualenvwrapper的安装及问题解决

    安装virtualenvwrapperyum install python-setuptools python-develpip install virtualenvwrapper # linux下 ...

  9. Problem B: 调用函数,求1!+2!+3!+......+10!

    #include<stdio.h> double fact(int i); int main() { int i; ; ;i<=;i++) sum=sum+fact(i); prin ...

  10. ujmp使用心得

    1)对矩阵转置运算时: Matrix test2 = oneMatrix.transpose(); Matrix test2 = oneMatrix.transpose(Ret.LINK); Matr ...