【无标题】I/O复用(epoll)三者区别▲
一、SOCKET-IO复用技术
定义:SOCKET - IO
复用技术是一种高效处理多个套接字(socket
)的手段,能让单个线程同时监听多个文件描述符(如套接字)上的I/O
事件(像可读、可写、异常),进而提高程序的并发处理能力,避免为每个套接字创建一个线程或进程带来的资源开销(像一个管家)
是由函数select、poll和 Epoll 支持的
1.select、poll和 Epoll三者的区别:
select:轮回机制,存储容器数组,固定大小
poll:轮回机制,存储容器链表,动态扩展
epoll:事件驱动机制
2. 数据结构与扩展性
机制 | 存储容器类型 | 最大连接数限制 | 动态扩展能力 |
---|---|---|---|
select | 固定大小的位掩码数组 | 通常为 1024(FD_SETSIZE) | ❌ 无法扩展 |
poll | 动态链表(struct pollfd) | 无硬性限制(取决于系统资源) | ✅ 动态添加 |
epoll | 内核红黑树 + 就绪链表 | 无硬性限制(仅受内存约束) | ✅ 自动管理 |
- select:使用固定大小的
fd_set
(位掩码)存储文件描述符,需手动管理位操作,扩展性差。 - poll:使用链表
struct pollfd
存储文件描述符,可动态添加,突破了select
的限制。 - epoll:使用内核红黑树高效管理所有待监控的文件描述符,自动扩容。
3. 工作机制
机制 | 事件触发方式 | 轮询方式 | 性能特性 |
---|---|---|---|
select | 水平触发(Level Triggered) | 遍历所有文件描述符 | O (n) 时间复杂度 |
poll | 水平触发 | 遍历所有文件描述符 | O (n) 时间复杂度 |
epoll | 边缘触发(Edge Triggered)或水平触发 | 仅遍历就绪链表 | O (1) 时间复杂度 |
- 水平触发(LT):只要文件描述符就绪(如可读),就会持续通知。
- 边缘触发(ET):仅在文件描述符状态变化(如从不可读到可读)时通知一次,需立即处理,否则数据可能丢失。
4. 性能对比
场景 | select/poll 表现 | epoll 表现 |
---|---|---|
连接数少且活跃 | 效率较高 | 优势不明显 |
连接数多但不活跃 | 性能急剧下降(轮询所有连接) | 性能稳定(仅处理就绪连接) |
大量并发连接 | 不适用(受 FD_SETSIZE 限制) | 非常高效 |
二、Epoll函数
基于以上三种方法对比,所以我们选用epoll进行使用较为合适
epoll两种模式的区别:
LT逻辑简单,但效率低,ET反之
核心数据结构是:1个红黑树和1个链表
1.创建
int epoll_create(int size);
参数:size表明内核要监听的描述符数量。调用成功时返回一个epoll句柄描述符,失败时返回-1
2.注册要监听的事件类型
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
- 参数:用于控制 epoll 实例对文件描述符的监听。
epfd
是由epoll_create
函数返回的 epoll 实例的文件描述符。op
参数指定操作类型,常见的值有EPOLL_CTL_ADD
(将文件描述符fd
添加到 epoll 实例的监听列表中)、EPOLL_CTL_MOD
(修改文件描述符fd
的监听事件)和EPOLL_CTL_DEL
(从 epoll 实例的监听列表中删除文件描述符fd
)。fd
是要进行操作的文件描述符。event
是一个指向struct epoll_event
结构体的指针,用于指定要监听的事件类型以及关联的数据。
- 返回值:成功时返回 0;失败时返回 -1,并设置
errno
以指示错误原因。
3.等待事件的就绪
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
- 功能:等待 epoll 实例所监听的文件描述符上有事件发生。
epfd
是 epoll 实例的文件描述符。events
是一个struct epoll_event
类型的数组,用于存储发生事件的文件描述符及其相关事件信息。maxevents
指定了events
数组的大小,即最多能返回的事件数量。timeout
指定等待的超时时间,以毫秒为单位。如果设置为 -1,则表示无限期等待,直到有事件发生;如果设置为 0,则表示立即返回,不进行等待。
- 返回值:成功时返回发生事件的文件描述符数量;如果超时则返回 0;失败时返回 -1,并设置
errno
以指示错误原因。
#include "epollServer.h"epollServer::epollServer(int port)
{this->server = new TCPServer(port);init_epoll();
}void epollServer::init_epoll()
{// 创建epollepoll_fd = epoll_create(10);if (epoll_fd < 0) {perror("epoll_create error");return;}// 添加epoll关注事件//struct epoll_event epoll_event;epoll_event.data.fd = this->server->getServerfd();epoll_event.events = EPOLLIN;epoll_ctl(epoll_fd, EPOLL_CTL_ADD, this->server->getServerfd(), &epoll_event);cout << "epoll初始化完成..." << endl;
}void epollServer::start()
{struct epoll_event event_array[10] = { 0 };int event_num = 0;this->thread_pool = new ThreadPool2(5);// 主循环while (1) {cout << "epoll wait..." << endl;event_num = epoll_wait(epoll_fd, event_array, 10, -1);// cout<<event_arrayif (event_num < 0) {perror("epoll_wait error");continue;}for (int i = 0; i < event_num; i++) {if (event_array[i].data.fd == this->server->getServerfd()) {//处理连接请求cout << "有新客户端连接请求" << endl;int client_fd = accept(this->server->getServerfd(), NULL, NULL);if (client_fd < 0) {continue;}// 将新的客户端连接添加到epoll关注列表epoll_event.data.fd = client_fd;epoll_event.events = EPOLLIN;epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &epoll_event);}else if (event_array[i].events & EPOLLIN) {//处理请求监听的事件}}}
}