NIO原理浅析(三)

epoll

首先认识一下epoll的几个基础函数

int s = socket(AF_INET, SOCK_STREAM, 0);
bind(s, ...);
listen(s, ...);

int epfd = epoll_create(...)
epoll_ctl(epfd, ...); //将所有需要监听的socket添加到epfd中

while(1) {
    int n = epoll_wait(...);
    for(接受到数据的socket) {
      //处理数据
    }
}

这段代码涉及到几个与epoll相关的函数方法:

  • epoll_create: 创建一个文件句柄

  • epoll_ctl: 向epoll对象添加/修改/删除要管理的连接

  • epoll_wait: 等待其管理的连接上的IO事件

1、epoll_create
int epoll_create(int size);

功能描述:用于生成一个epoll专用的文件描述符

参数size:因为epoll底层使用红黑树来保存文件描述符,红黑树的查找时间复杂度为O(logN),这个size并没有必要限制大小。size可以设置为大于0的任何数。

返回值:如果成功,返回epoll专用的文件描述符,如果失败,则返回-1。

2、epoll_ctl
int epoll_ctl(int epfd, int op, int fd, struct epoll_event * event);

epoll中的事件注册函数,用于注册要监听的事件类型

参数

  • epfd: epoll专用的文件描述符,epoll_create()的返回值

  • op: 表示动作,用三个宏来表示:

    • EPOLL_CTL_ADD: 注册新的fd到epfd中

    • EPOLL_CTL_MOD: 修改已经注册的fd的监听事件

    • EPOLL_CTL_DEL: 从epfd中删除一个fd

  • fd: 需要被监听的文件描述符

  • event: 告诉内核需要监听什么事件

  • 返回值:0表示成功,-1表示失败

epoll_event结构体如文档描述:

       #include 

       struct epoll_event {
           uint32_t      events;  /* Epoll events */
           epoll_data_t  data;    /* User data variable */
       };

       union epoll_data {
           void     *ptr;
           int       fd;
           uint32_t  u32;
           uint64_t  u64;
       };

       typedef union epoll_data  epoll_data_t;

events可以是以下几个宏的集合:

  • EPOLLIN: 表示对应的文件描述符可以读

  • EPOLLOUT:表示对应的文件描述符可以写

  • EPOLLPRI:表示对应的文件描述符有紧急的数据可以读

  • EPOLLERR: 表示对应的文件描述符发生错误

  • EPOLLHUP:表示对应的文件描述符被挂断

  • EPOLLET: 将EPOLL设为边缘触发模式,不设置为水平触发模式

  • EPOLLONESHOT: 只监听一次事件,当监听完这次事件之后,如果还需要监听这次事件,则需要把socket重新加入到EPOLL中。

3、epoll_wait
int epoll_wait(int epfd, struct epoll_event * event, 
               int maxevents, int timeout);

功能:本函数用于监控事件的发生,当epoll监控的事件中存在已经发送的事件,那么就可以将此事件收集起来。

参数:

  • epfd:epoll自身产生的文件描述符,调用epoll_create的返回值

  • event:事先必须将空间分配好的结构体数组,epoll将即将发生的事件赋值到event数组中。

  • maxevents:告诉内核共有多少个event数组的大小

  • timeout:超时时间,单位为毫秒,设为-1时,该函数状态为阻塞。

  • 返回值:如果为-1,表示失败;如果为0,表示已经超时;如果成功,返回需要处理的事件数目。

epoll整体流程图如下所示:

NIO原理浅析(三)_第1张图片

我们在之前文章中讨论过,select和poll存在三个缺陷,epoll方式很好的解决了这些问题:

epoll在内核中使用红黑树这个数据结构来保存文件描述符,红黑树这个数据结构的增删改的时间复杂度为O(logn),select/poll每次监听套接字,都需要将套接字列表整个复制到内核态,而epoll使用epoll_ctl,每次将一个监听套接字复制到内核态,这明显减少了用户空间到内核态的大量数据拷贝和内存分配。

epoll使用异步回调的机制,内核态维护一个就绪队列,如果某个socket已经准备好,那么就会通过回调函数触发内核将socket事件加入到就绪事件列表。用户调用epoll_wait函数,就会返回有事件发生的文件描述符的个数。这个过程中,没有像select/poll一样对socket集合进行了O(N)时间复杂度的轮询。提高了检测的效率。

边缘触发和水平触发

epoll支持两种事件触发模式:

  • 边缘触发:当被监控的Socket描述符有可读事件发生时,服务器端在调用epoll_wait这个函数时,只会苏醒一次,然后从内核缓冲区中读取所有的数据

  • 水平触发:当被监控的Socket描述符有可读事件发生时,服务器端在调用epoll_wait这个函数时,会不断的苏醒,直到内核中没有数据可读才停止苏醒。

你可能感兴趣的:(nio,java)