本期我们接着上一篇,学习IO多路转接的内容
相关内容上传至gitee:楼田莉子/Linux学习
目录
[函数表达式(C 语法)](#函数表达式(C 语法))
[(1) epoll_create1 / epoll_create](#(1) epoll_create1 / epoll_create)
[(2) epoll_ctl](#(2) epoll_ctl)
[(3) epoll_wait](#(3) epoll_wait)
select
select 是传统的IO多路复用系统调用,可同时监视多个文件描述符(socket、普通文件、管道等),等待其中任意一个变为"就绪"状态(可读、可写或异常)。它允许单线程或单进程高效处理数十到数百个并发连接(现代高并发场景一般用 epoll 替代,但 select 仍是理解多路复用的基础)
作用
-
同时监听多个文件描述符的 可读 、可写 和 异常 事件。
-
阻塞直到有描述符就绪、超时或被信号中断。
-
配合非阻塞IO,可在不占用CPU的前提下实现高并发网络服务。
函数表达式(C 语法)
cpp
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
参数详解
-
nfds:需要监视的最大文件描述符值 + 1(即max_fd + 1)。内核只检查[0, nfds)范围内的描述符,提高效率。 -
readfds:指向可读事件的文件描述符集合。如果某个fd在集合中且数据可读(对于socket指接收缓冲区有数据,或连接关闭,或监听socket有新连接),select返回后该fd在集合中保留,否则会被清除。可设为nullptr表示不关心读事件。 -
writefds:指向可写事件的fd集合。对于socket,当发送缓冲区有空闲空间(或非阻塞connect完成)时可写。可设为nullptr。 -
exceptfds:指向异常事件的fd集合。通常用于带外数据(TCP紧急指针)等。可设为nullptr。 -
timeout:最大等待时间。-
timeout == nullptr:无限阻塞直到有事件。 -
timeout->tv_sec == 0 && timeout->tv_usec == 0:立即返回(非阻塞轮询)。 -
其他:等待指定时间后即使无事件也返回0。
-
返回值
-
成功:返回就绪的文件描述符总数(即三个集合中保留的fd个数)。
-
超时:返回 0(无就绪fd)。
-
失败 :返回 -1,并设置
errno(常见:EBADF、EINTR等)。
fd_set 操作宏:
FD_ZERO(fd_set *set):清空集合
FD_SET(int fd, fd_set *set):将fd加入集合
FD_CLR(int fd, fd_set *set):将fd从集合中移除
FD_ISSET(int fd, fd_set *set):测试fd是否在集合中(通常用于select返回后判断哪个fd就绪)
示例
epoll
作用
-
同时监视大量文件描述符(无上限 ,仅受系统内存限制)的 可读、可写、错误、挂断 等事件。
-
支持 水平触发(LT,Level Triggered) 和 边缘触发(ET,Edge Triggered) 两种工作模式。
-
采用事件驱动,每次调用
epoll_wait仅返回就绪的描述符,复杂度 O(1),非常适合高并发服务器(如 Nginx、Redis)。
函数表达式与参数详解
epoll 包含三个核心函数:
(1) epoll_create1 / epoll_create
cpp
#include <sys/epoll.h>
int epoll_create(int size);
int epoll_create1(int flags);
-
作用:创建一个 epoll 实例,返回一个文件描述符(epfd),后续操作均通过它进行。
-
参数:
-
size(已废弃,但必须 >0):提示内核需要监听的大小,内核会动态扩展。 -
flags(epoll_create1专用):可取0或EPOLL_CLOEXEC(执行 exec 时自动关闭 epfd)。
-
-
返回值 :成功返回 epfd(非负整数);失败返回 -1 并设置 errno(如
ENOMEM、EMFILE)。
(2) epoll_ctl
cpp
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
-
作用:控制 epoll 实例上的文件描述符事件(添加、修改、删除)。
-
参数:
-
epfd:epoll_create1返回的描述符。 -
op:操作类型,取以下宏:-
EPOLL_CTL_ADD:添加fd到 epoll 实例。 -
EPOLL_CTL_MOD:修改fd上监听的事件。 -
EPOLL_CTL_DEL:从 epoll 实例删除fd。
-
-
fd:要操作的目标文件描述符(socket、普通文件等)。 -
event:指向struct epoll_event的指针,描述关心的事件及用户数据。cppstruct epoll_event { uint32_t events; // 事件掩码(EPOLLIN, EPOLLOUT, EPOLLET...) epoll_data_t data; // 用户数据(通常存 fd 或指针) }; typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t;常用事件标志:
宏 含义 EPOLLIN可读事件(含对端关闭) EPOLLOUT可写事件 EPOLLRDHUP对端关闭连接(需内核 2.6.17+) EPOLLET边缘触发模式(默认水平触发) EPOLLONESHOT事件触发一次后自动禁用,需再次重新注册 EPOLLERR错误事件(自动监听,无需显式设置) EPOLLHUP挂断事件(自动监听)
-
-
返回值 :成功返回 0;失败返回 -1 并设置 errno(如
EBADF、EEXIST、ENOENT等)。
(3) epoll_wait
cpp
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
-
作用:等待 epoll 实例上的事件,返回就绪的事件列表。
-
参数:
-
epfd:epoll 实例描述符。 -
events:输出参数,指向一个epoll_event数组,由内核填充就绪的事件。 -
maxevents:events数组的大小(最多返回多少个事件)。 -
timeout:超时时间(毫秒)。-
-1:阻塞直到有事件。 -
0:立即返回(非阻塞轮询)。 -
>0:等待指定毫秒数。
-
-
-
返回值:
-
成功:返回就绪的文件描述符个数(0 表示超时)。
-
失败:返回 -1 并设置 errno(如
EBADF、EINTR等)。
-
水平触发(LT)与边缘触发(ET)的区别
| 模式 | 行为 | 适用场景 | 注意事项 |
|---|---|---|---|
| LT(默认) | 只要 fd 还有未处理的数据(如缓冲区非空),每次 epoll_wait 都会返回该 fd。 |
简单、安全,适合初学者和传统模型。 | 效率略低于 ET,但不易出错。 |
| ET | 仅在 fd 状态发生变化(从无数据变为有数据,或缓冲区从满变为有空闲)时返回一次。 | 高性能服务,需配合非阻塞 IO 使用。 | 必须循环读写直到返回 EAGAIN,否则会丢失事件。 |
源码
cpp
#include <iostream>
#include <sys/epoll.h>
#include <unistd.h>
#include <cerrno>
#include <cstring>
int main()
{
int epfd = epoll_create1(0);
if (epfd == -1) {
perror("epoll_create1");
return 1;
}
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = STDIN_FILENO;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &ev) == -1) {
perror("epoll_ctl");
close(epfd);
return 1;
}
struct epoll_event events[1];
std::cout << "epoll on stdin (type something and press Enter, or wait 5s for timeout)..." << std::endl;
while (true) {
int nfds = epoll_wait(epfd, events, 1, 5000);
if (nfds == -1) {
perror("epoll_wait");
break;
}
if (nfds == 0) {
std::cout << "Timeout: no data within 5s, polling again..." << std::endl;
continue;
}
if (events[0].events & EPOLLIN) {
char buf[1024];
ssize_t n = read(STDIN_FILENO, buf, sizeof(buf) - 1);
if (n == -1) {
perror("read");
break;
}
if (n == 0) {
std::cout << "EOF reached, exiting." << std::endl;
break;
}
buf[n] = '\0';
std::cout << "Read: " << buf;
}
if (events[0].events & (EPOLLERR | EPOLLHUP)) {
std::cerr << "epoll error/hangup on stdin" << std::endl;
break;
}
}
close(epfd);
return 0;
}
结果为:

本期内容到这里就结束了
封面图:
