select、poll 和 epoll 是处理多任务网络请求的三种常见方法。你可以想象它们是一个老师管理一群学生举手提问的场景,但它们的管理效率各不相同。
- Select:原始的问一圈
比喻:
老师每隔几秒就问全班同学:“有人要提问吗?”(遍历所有座位),然后再走到需要解答的同学座位旁回答问题。
问题:
1.数量限制:老师最多只能管理 1024 个学生(文件描述符数量限制)。
2.效率低:不管有没有人提问,老师都要每次问一圈(浪费资源)。
3.重复劳动:每次提问时,老师要重新记一遍全班座位(拷贝数据到内核)。
代码示例:
fd_set read_fds;
FD_ZERO(&read_fds); // 清空调査名单
FD_SET(socket1, &read_fds); // 将学生1加入名单
select(max_fd+1, &read_fds, NULL, NULL, NULL); // 挨个询问名单里的学生
2. Poll:去掉数量限制的升级版
比喻:
老师改成用笔记本记录要提问的学生名单,不再限制人数,但依然需要每次遍历整个名单。
改善:
无数量限制:理论上可以管理无限多的学生。
存储优化:笔记本按需记录(结构体链表代替位掩码)。
遗留问题:
效率依然低:每次还要看一遍所有名字(遍历所有文件描述符)。
代码示例:
struct pollfd fds[MAX];
fds[0].fd = socket1;
fds[0].events = POLLIN; // 记录学生1可能提问
poll(fds, 1, -1); // 遍历整张表査看谁举手
3. Epoll:智能响铃系统
比喻:
学生桌上有一个按钮,想提问时按按钮(触发事件),老师的屏幕会直接显示需要帮助的学生座位号,老师只需要看屏幕过去解答。
优点:
1.事件驱动:学生主动按按钮时才响应(只处理活跃请求)。
2.零遍历成本:无需遍历所有学生,直接获取就绪列表。
3.高效内存:初始注册后,内核会维护状态,无需反复拷贝数据。
代码示例:
int epoll_fd = epoll_create1(0); // 安装智能系统
struct epoll_event event;
event.events = EPOLLIN; // 监控可读事件
event.data.fd = socket1;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, socket1, &event); // 让学生1接入系统
struct epoll_event events[10];
int num = epoll_wait(epoll_fd, events, 10, -1); // 屏幕显示当前需要处理的学生
关键区别对比
方法 管理方式 容量限制 效率(活跃连接少时) 适用场景
select 轮询所有座位 1024 极低 低并发、可移植性要求
poll 轮询列表 无 低 少量连接的兼容场景
epoll 仅响应“按按钮”的座位 无 极高 高并发(如Web服务器)
Epoll 的两个工作模式
1.
水平触发(LT):
学生按着按钮不放,老师会一直看到他的请求,直到处理完毕。
代码建议:适合处理不完就继续提醒的简单场景。
2.
边缘触发(ET):
按钮按一次只能触发一次通知,老师处理到一半时需要确认是否还有遗留问题。
代码建议:必须一次性处理完数据,否则可能漏事件(高效但容易出错)。
总结
小班教学(少量连接)→ 随便用 select/poll。
万人讲座(高并发)→ 必须用 epoll/kqueue(其他系统类似)。
核心逻辑:Epoll 的“按需响应”机制,避免了无效遍历,极大提升了效率!