一. 回顾 

  做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. Bot Framework测试

    在开发完成Bot Framework后,在本机的模拟器都是成功的,但未知在发布后会出现什么样的问题,所以需要将本机发布的站点给到Bot 1.在Bot Framework注册一个Bot,打开Bot Fr ...

  2. [c] 段错误(core dump): 一个格式化输出引起的问题

    #include <stdio.h> int len = sizeof(int); printf("%s\n",len); /* 编译的时候是没问题的,运行的时候就报错 ...

  3. 取消Gridvie中button的焦点

    Gridview中添加button,onclick方法使得 GridView的setOnItemClickListener方法无效. 解决方法: 设置Button的XML布局文件,也就是自定义的Ada ...

  4. 【推导】Codeforces Round #402 (Div. 2) A. Pupils Redistribution

    一次交换,会让Group A里面的某个数字的数量-1,另一个数字的数量+1:对Group B恰好相反. 于是答案就是xigma(i=1~5,numA[i]-numB[i]>0)(numA[i]- ...

  5. 【博弈论】bzoj1115 [POI2009]石子游戏Kam

    差分后与阶梯博弈很类似. #include<cstdio> using namespace std; int n,T,a[1001],ans; int main() { scanf(&qu ...

  6. Java学习笔记(6)

    java是面向对象的语言. 对象:真实存在的唯一的事物. 类:实际就是对某种类型事物的共性属性与行为的抽取 面向对象的计算机语言的核心思想:找适合的对象做适合的事情. 如何找适合的对象呢: 1.sun ...

  7. mysql-启动、关闭与重启

    启动 service mysqld start mysql.server start 停止 service mysqld stop mysql.server stop 重启 mysql.server ...

  8. oracle--v$lock type字段详解

    Name Description AD ASM Disk AU Lock AF Advisor Framework AG Analytic Workspace Generation AK GES De ...

  9. 如何用css做一个爱心

    摘要:HTML的标签都比较简单,入门非常的迅速,但是CSS是一个需要我们深度挖掘的东西,里面的很多样式属性掌握几个常用的便可以实现很好看的效果,下面我便教大家如何用CSS做一个爱心. 前期预备知识: ...

  10. Swift,字符串

    1.字符串只能使用双引号 var a="你好" 2.单字与多字 var a:Character="1" var b:String="12" ...