IO多路复用——epoll

epoll

I/O复用:指应用程序内核 注册一组事件(eg: 新连接到达——监听套接字上的可读事件),然后阻塞在IO复用函数上(select,poll,epoll),内核通过IO复用函数把就绪的事件通知给应用程序。

一、epoll基本API

epoll_create

int epoll_create(int size);
int epoll_create1(int flags);

创建一个内核事件表(epoll实例)

size:一个大于0的整数;之前的内核被设计为用参数size,告知内核期望监听的文件描述字大小,作为内核数据结构初始化的值;从 Linux2.6.8 开始,这个参数就被忽略了 (epoll源码)

返回值

若成功返回一个大于0的值,表示epoll实例;若返回-1表示出错

flags:

若为0,行为同epoll_create(); 可以设置为EPOLL_CLOEXEC.

EXEC:子进程默认打开父进程所有的文件描述符(包括epoll内核事件表,

CLOEXEC指当前进程 fork 出来的任何子进程 没有访问父进程 epoll 实例的权限。


epoll_ctl

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

函数功能:向epoll实例中增加/删除监听的事件

参数说明
epfd:epoll句柄

op:操作类型

  • EPOLL_CTL_ADD: 向 epoll 实例注册文件描述符对应的事件;
  • EPOLL_CTL_DEL:向 epoll 实例删除文件描述符对应的事件;
  • EPOLL_CTL_MOD: 修改文件描述符对应的事件。

event:向内核注册的某个具体事件,epoll_event类型
fd:事件对应的文件描述符

 struct epoll_event {
     uint32_t     events;      /* Epoll events */
     epoll_data_t data;        /* User data variable */
 };
typedef union epoll_data {
     void        *ptr;
     int          fd;
     uint32_t     u32;
     uint64_t     u64;
 } epoll_data_t;

成员:
events:一系列事件的按位或

常见事件类型

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

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

EPOLLRDHUP:表示套接字的一端已经关闭,或者半关闭

EPOLLHUP:表示对应的文件描述字被挂起

EPOLLET:设置为 edge-triggered,默认为 level-triggered。

epoll_data_t:一个联合体

常用的成员是fd,代表用户感兴趣事件对应的描述符

void* ptr 传入用户数据


epoll_wait

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

events:

以epoll_event数组形式返回用户空间需要处理的I/O事件

  • events成员表示发生的事件类型,事件类型取值和 epoll_ctl 可设置的值一样
  • data值即epoll_ctl里设置的data.

返回值: 成功返回的是一个大于0的数,表示就绪事件的个数;返回0表示的是超时时间到;若出错返回-1.

二、ET和LT

LT(Level Trigger)条件触发模式 (默认,效率较高的poll)

  • epoll_wait检测到文件描述符有事件发生,则将其通知给应用程序,应用程序可以不立即处理该事件。

  • 当下一次调用epoll_wait时,epoll_wait还会再次向应用程序报告此事件,直至被处理

ET边缘触发模式

  • epoll_wait检测到文件描述符有事件发生,则将其通知给应用程序,应用程序必须立即处理该事件 (只有第一次满足条件的时候才触发,之后就不会再传递同样的事件了)

  • 必须要一次性将数据读取完,使用非阻塞I/O,读取到出现eagain

三、使用epoll服务器工作流程

IO多路复用——epoll_第1张图片

  • 服务进程通过 epoll_wait 获取内核就绪事件处理。

  • 如果就绪事件是监听套接字(server_fd)上的读事件,调用accept 获取新连接对应的文件描述符 (client_fd),设置非阻塞,然后 epoll_ctl 监控 client_fd 的可读事件 EPOLLIN。

  • 如果就绪事件是已连接套接字上的读事件,read 读取客户端发送数据,进行逻辑处理。如果 read == 0代表对方请求关闭连接, epoll_ctl 删除 EPOLLIN事件,close 关闭对应 fd 从而完成四次挥手。

  • 处理逻辑过程中需要 write 回复客户端,write 内容很大,超出了内核缓冲区,没能实时发送完成所有数据,需要下次继续发送;那么调用epoll_ctl注册client_fd 的 EPOLLOUT 可写事件,下次触发事件进行发送。发送完毕后, epoll_ctl 删除 EPOLLOUT 事件。

图片来源:https://wenfh2020.com/2020/04/14/epoll-workflow/

四、性能对比

select、poll、epoll三组IO复用系统调用:

用户设置感兴趣事件的方式:

  • select:参数类型fd_set,没有将文件描述符和事件类型绑定,仅仅是一个文件描述符集合。因此select需要提供3个fd_set类型的参数,分别传入 可读、可写、异常等事件。
  • poll:参数类型pollfd,设置文件描述符+事件类型
struct pollfd { 
int fd; /* file descriptor */ 
short events; /*由用户设置感兴趣的事件类型*/ 
short revents; /* 内核返回就绪事件 */
};

内核如何将结果返回给应用程序:

  • select:内核对fd_set在线修改,应用程序需要重置集合
  • poll:内核修改pollfd的revents成员

文件描述符数量:

  • select通过数组描述文件描述符集合,最大文件描述符数量有上限,一般是1024,但可以修改源码,重新编译内核,不推荐

  • poll是链表描述,突破了文件描述符上限,最大可以达到系统允许打开的最大文件描述符的数目(65535)。

  • epoll通过红黑树描述,最大可以打开文件的数目,可以通过命令ulimit -n number修改,仅对当前终端有效

应用程序索引就绪文件描述符:

  • select/poll只返回就绪的文件描述符的个数,返回整个用户注册的事件集合(就绪的和未就绪的),若知道是哪个发生了事件,同样需要遍历。时间复杂度O(n)

  • epoll返回就绪事件的个数和,将用户传入的结构体数组events,表示就绪的事件,时间复杂度O(1)

你可能感兴趣的:(UNP学习笔记,网络,服务器,c++)