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

原文地址:https://www.cnblogs.com/holoyong/p/7354244.html