aio-epoll
epoll是Linux高效网络的基础,比如event poll(例如nodejs),是使用libev,而libev的底层就是epoll(只不过不同的平台可能用epoll,可能用kqueue)。epoll能够高效支持百万级别的句柄监听。
select需要轮询所有fd才能得知哪些fd有事件发生(在fd很多时,轮询就变得吃力了),而epoll有事件发生的fd被主动存放在了一个链表中(在中断处理程序中注册回调函数,回调函数会将发生事件的fd放入链表中);在每次select时都需要重新向内核复制需要监听的fd,而epoll只需要最初复制一次即可(如果是EPOLLONESHOT,则需要每次都重新监听事件)。
epoll的接口只有3个函数:
1 //创建一个epoll的句柄,size用来告诉内核这个监听的数目的估计值,用于预分配空间 2 int epfd = epoll_create(intsize); 3 //将被监听的描述符添加到epoll句柄或从epool句柄中删除或者对监听事件进行修改。 4 //epfd即epoll_create的返回值,op有3个可选值(EPOLL_CTL_ADD,EPOLL_CTL_MOD,EPOLL_CTL_DEL),fd即需要监听的fileDescriptor,event是在fd上需要监听的事件,有7个可选值(EPOLLIND等) 5 int epoll_ctl(int epfd, int op, int fd, struct epoll_event*event); 6 //等待事件触发,当超过timeout还没有事件触发时,就超时 7 int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
epoll的数据结构如下,其中最重要的就是rdllist(List of ready file descriptors)和rbr(red black root) ,rdllist链表元素代表就绪事件,rbr红黑树上的节点代表所有注册在该epoll上的fileDescriptor,插入、更新、删除fileDescriptor时是在红黑树上操作,会很快。在epoll上注册事件后就不需要重复注册了(除非使用了EPOLLONESHOT),socket的中断处理程序中注册一个回调函数,当socket与内核缓冲区完成数据交互后就会运行中断处理程序,此时就会运行回调函数,该函数会将socket事件放入rdllist中。epoll只需要监控rdllist链表即可,当rdllist非空时就会将rdllist上的事件返回给用户区,这时我们就得到了事件及发生事件的socket(epoll_wait会返回事件地址,通过事件地址就可以得到fileDescriptor,在java的EPollPort中维护着fileDescriptor到channel的映射),然后就可以进一步处理了。
1 struct eventpoll{ 2 .... 3 /*红黑树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件*/ 4 struct rb_root rbr; 5 /*双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件*/ 6 struct list_head rdlist; 7 .... 8 };
1 struct epitem{ 2 struct rb_node rbn;//红黑树节点 3 struct list_head rdllink;//双向链表节点 4 struct epoll_filefd ffd; //事件句柄信息 5 struct eventpoll *ep; //指向其所属的eventpoll对象 6 struct epoll_event event; //期待发生的事件类型 7 }
LT, ET
当一个socket句柄上有事件时,内核会把该句柄插入上面所说的准备就绪list链表,这时我们调用epoll_wait,会把准备就绪的socket拷贝到用户态内存,然后清空准备就绪list链表,最后,epoll_wait干了件事,就是检查这些socket,如果是LT模式(非ET模式),并且这些socket上确实有未处理的事件时,又把该句柄放回到刚刚清空的准备就绪链表了。所以,LT的句柄,只要它上面还有事件,epoll_wait每次都会返回这个句柄。(从上面这段,可以看出,LT还有个回放的过程,低效了)
java中的aio是LT模式,因为sun.nio.ch.Port中只存在以下4个事件
1 static final short POLLIN = 0x0001; 2 static final short POLLOUT = 0x0004; 3 static final short POLLERR = 0x0008; 4 static final short POLLHUP = 0x0010;
events
events可以是以下几个宏的集合:
EPOLLIN: 触发该事件,表示对应的文件描述符上有可读数据。(包括对端SOCKET正常关闭);
EPOLLOUT: 触发该事件,表示对应的文件描述符上可以写数据;
EPOLLPRI: 表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR: 表示对应的文件描述符发生错误;
EPOLLHUP: 表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT: 只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里。
参考:http://www.cnblogs.com/charlesblc/p/6242479.html
http://blog.csdn.net/yusiguyuan/article/details/15027821