I/O复用——epoll

epoll系列函数

#include <sys/epoll.h>
// 创建内核事件表,记录用户关注的所有文件描述符以及其关注的事件类型,成功返回内核事件表的标识符,失败返回-1
int epoll_create(int size); // size现在并不起作用,只是给内核一个提示

// 管理内核事件表:添加、删除、修改
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

// 监听用户关注的文件描述符,成功返回就绪的个数,失败返回-1,超时返回0
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
epoll_ctl参数含义
epfd需要操作的内核事件表的标识
opEPOLL_CTL_ADD(添加) EPOLL_CTL_DEL(删除) EPOLL_CTL_MOD(修改)
fd需要操作的文件描述符
event指定关注的事件
epoll_wait参数含义
epfd需要监听的内核事件表
events是一个用户数组, events的数据是由内核在 epoll_wait返回时填充就绪事件的文件描述符和事件类型
maxeventsevents数组的长度,即一次 epoll wai最多返回的文件描述符的个数
timeout单位毫秒,为-1时永久阻塞
epoll_event结构体
struct epoll_event
{
	short events;     // epoll事件
	epoll_data_t data;   // 用户数据
};

typedef union epoll_data
{
	void *ptr;
	int fd;
	uint32_t u32;
	unit64_t u64;
}epoll_data_t;
  • epoll支持的事件类型与poll基本相同,表示epoll事件的宏是在poll对应的宏前加上’E’poll支持的事件类型),但是epoll有两个额外的事件类型:EPOLLET和 EPOLLONESHOT
  • data成员用于存储用户数据,是一个联合体,其中fd成员使用的最多,指定事件从属的目标文件描述符

select、poll和epoll的区别

  • epoll()是Linux系统独有的
  • select()和poll()通过fd_set和struct pollfd结构记录文件描述符,都是在用户态存在的,所以每次调用select()和poll()时都会存在两次拷贝,一次是在调用时,一次是在返回时;而epoll()将文件描述符记录到内核事件表,所以调用时不需要将用户态数据拷贝到内核态,效率较高
  • select()和poll()用户处理就绪事件的时间复杂度为O(n),epoll为O(1)
  • select()和poll()内核采用的是轮询的方式检测是否事件发生,而epoll的内核采用的是回调的方式监听是否有事件发生
    轮询:适用于关注的事件多发生的事件也多
    回调:适用于关注的事件多但是发生的事件少

epoll的工作模式:LT和ET

LT工作模式

默认的工作模式,这种模式下epoll相当于一个效率较高的poll

  • 当 epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序可以不立即处理该事件。这样,当应用程序下一次调用epoll_wait时,epoll_wait还会再次向应用程序通告此事件,直到该事件被处理
ET工作模式

当往 epoll内核事件表中注册一个文件描述符上的 EPOLLET事件时,epoll将以ET模式来操作该文件描述符,ET模式是epol的高效工作模式

  • 当 epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序必须立即处理该事件,因为后续的epoll_wait调用将不再向应用程序通知这一事件
  • 若采用ET模式之后,当客户端向服务器发送的数据服务器一次没有读完,那么剩余的数据会存储在服务器的接收缓冲区中,若接收缓冲区数据满了之后会将数据留在客户端发送缓冲区中,如果发送缓冲区再满了,send()将阻塞
  • ET模式在很大程度上降低了同一个epoll事件被重复触发的次数,因此效率要比LT模式高
两者的比较

测试代码见测试代码源码

LT

启动一个客户端和服务端,客户端向服务端发送数据,服务端接收并打印出来,为了显示效果,这里服务器每次只接收一个字符。我们会发现,当客户端输入字符后,服务器每接收一个字符,就会再次调用epoll_wait,效率较低
在这里插入图片描述

ET

这里服务器仍然是每次只接收一个字符,对比与LT,可以明显的发现调用epoll_wait的次数明显减少,效率大大提高。

需要注意的是,由于recv()接收数据时操作的连接套接字是阻塞的,所以在调用recv()之前需要将连接套接字设置为非阻塞的
在这里插入图片描述

EPOLLONESHOT

EPOLLONESHOT事件:一个 socket连接在任一时刻都只被一个线程处理

  • EPOLLONESHOT解决的问题:一个 socket上的某个事件可能被触发多次,这在并发程序中就会引起一个问题:比如一个线程(或进程,下同)在读取完某个 socket上的数据后开始处理这些数据,而在数据的处理过程中该 socket上又有新数据可读( EPOLLIN再次被触发),此时另外一个线程被唤醒来读取这些新的数据,于是就出现了两个线程同时操作一个socket的局面
  • 对于注册了 EPOLLONESHOT事件的文件描述符,操作系统最多触发其上注册的一个可读、可写或者异常事件,且只触发一次,除非我们使用 epoll ctl函数重置该文件描述符上注册的EPOLLONESHOT事件。这样,当一个线程在处理某个 socket时,其他线程是不可能有机会操作该 socket的。但反过来思考,注册了 EPOLLONESHOT事件的 socket一旦被某个线程处理完毕,该线程就应该立即重置这个 socket上的 EPOLLONESHOT事件,以确保这个socket下一次可读时,其 EPOLLIN事件能被触发,进而让其他工作线程有机会继续处理这socket
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值