1.概念
IO多路复用是一种通过一种机制使一个进程能同时监控多个文件描述符的状态变化的技术。它通过操作系统提供的机制,让单个线程能够有效管理多个IO操作,如读取、写入等,而不需要创建多个线程或进程来处理每个IO事件。这种技术在网络编程中特别有用,可以显著提高服务器的并发处理能力和响应速度。
在Linux系统中,常见的IO多路复用机制有select
、poll
和epoll
。其中,epoll
由于其高效的特性,成为了主流的选择。epoll是select和poll的升级版,相较于这两个前辈,epoll改进了工作方式,因此它更加高效。
epoll相较于select和poll的改进和优势
-
数据结构与管理方式:
- select 和 poll:使用线性数组来管理待检测的文件描述符集合,每次调用时需要遍历整个集合,效率随集合大小线性下降。
- epoll:通过红黑树(用于存储大量文件描述符)和就绪链表(用于存储准备好的事件),实现了高效的事件管理。红黑树的查找效率为O(log n),极大地提升了处理大量描述符时的效率。
-
事件通知机制:
- select 和 poll:使用轮询方式,应用程序需要在返回的就绪集合中逐个检查每个描述符的状态。
- epoll:通过回调机制,在文件描述符状态改变时直接通知应用程序,避免了反复检查的开销,提升了系统的响应速度。
-
内存管理和数据传输:
- select 和 poll:每次调用都涉及到内核和用户空间之间的数据复制,增加了系统开销。
- epoll:利用内存映射(mmap)技术,内核和用户空间共享一块内存区域,减少了数据传输的次数和开销,提升了效率。
-
可扩展性和限制:
- select 和 poll:对监视的文件描述符数量有限制,尤其在处理大量并发连接时表现较差。
- epoll:几乎没有对监视文件描述符数量的限制,能够轻松处理大规模的并发连接,适应了现代高性能服务器应用的需求。
-
适用场景:
- select 和 poll:适合于监视数量较少的文件描述符,对于少量连接和简单的IO场景仍然有其用处。
- epoll:特别适合于需要处理大量并发连接和高频IO操作的场景,如网络服务器,能够显著提升系统的性能和吞吐量。
2.相关操作函数
2.1 epoll_create
// 创建epoll实例,通过一棵红黑树管理待检测集合
int epoll_create(int size);
功能:创建一个epoll实例,返回一个epoll文件描述符。
参数:
size:表示epoll实例中能够监听的文件描述符数量的建议值。
返回值:
成功:返回一个新的epoll文件描述符。
失败:返回-1,并设置errno表示错误的类型。
2.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 添加到epoll实例中进行监听。
EPOLL_CTL_MOD:修改已经添加到epoll实例中的文件描述符 fd 的事件类型。
EPOLL_CTL_DEL:从epoll实例中删除文件描述符 fd。
fd:要进行操作的文件描述符。
event:指向 struct epoll_event 结构体的指针,用来描述事件类型和相关数据。
返回值:
成功:返回0。
失败:返回-1,并设置errno表示错误的类型。
2.3 epoll_wait
// 检测epoll树中是否有就绪的文件描述符
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
功能:等待epoll实例中的文件描述符上发生事件。
参数:
epfd:epoll实例的文件描述符,由epoll_create返回。
events:指向 struct epoll_event 数组的指针,用于存储发生事件的文件描述符和事件类型。
maxevents:events 数组的最大长度,即最多能够返回多少个事件。
timeout:等待事件发生的超时时间(以毫秒为单位),-1表示永久阻塞直到有事件发生。
返回值:
成功:返回就绪的文件描述符数量,如果超时时间到达而没有事件发生则返回0。
失败:返回-1,并设置errno表示错误的类型。
2.4 struct epoll_event 结构体
struct epoll_event {
uint32_t events; // 事件类型
epoll_data_t data; // 用户数据
};
typedef union epoll_data {
void *ptr;
int fd; // 通常情况下使用这个成员, 和epoll_ctl的第三个参数相同即可
uint32_t u32;
uint64_t u64;
} epoll_data_t;
成员变量:
events:表示文件描述符上的事件类型,可以是以下几种&