epoll 事件全集、每个事件的含义、哪些事件在实际服务器中最常见、哪些会组合出现
一、epoll 事件总览(最常用的 10 个)
epoll 返回的 events 是一个 bitmask(位图),可能包含多个事件组合。
下面按使用频率从高到低列出来:
| 事件名 | 含义 | 是否常见 |
|---|---|---|
| EPOLLIN | fd 可读(有数据到达 / 对端关闭) | ★★★★★ |
| EPOLLOUT | fd 可写(发送缓冲区有空位置) | ★★★★ |
| EPOLLERR | socket 出错(RST 等) | ★★★★★ |
| EPOLLHUP | 对端关闭(FIN) | ★★★★★ |
| EPOLLRDHUP | 半关闭(对方调用了 shutdown(SHUT_WR)) | ★★★★ |
| EPOLLET | 边缘触发模式 | ★★★★★(高性能服务器必须用) |
| EPOLLONESHOT | 一次性事件,处理完需手动重置 | ★★★★ |
| EPOLLPRI | 紧急数据(TCP OOB) | ★★ |
| EPOLLEXCLUSIVE | 避免惊群(监听 socket 用) | ★★★ |
| EPOLLWAKEUP | 系统唤醒(安卓) | 少见 |
二、最常见 & 必须处理的事件组合
epoll 最典型的 four events:
EPOLLIN → 有数据可读
EPOLLOUT → 可以写数据
EPOLLERR → 错误,必须关闭
EPOLLHUP → 对方挂断,必须关闭
大多数情况下会处理的就是这四个。
三、事件组合出现的情况(epoll 特点)
epoll 一次返回的 events 可能包含多个标志位,例如:
1. 正常收发数据:
events = EPOLLIN
2. 写入数据阻塞后,现在可写:
events = EPOLLOUT
3. 对端关闭 + 有未读完的数据:
events = EPOLLIN | EPOLLRDHUP
4. 错误 + 可读(错误最高优先级)
events = EPOLLIN | EPOLLERR
5. 边缘触发写法常见:
events = EPOLLIN | EPOLLOUT | EPOLLET
四、生产服务器具体会遇到哪些组合?
实际开发中出现频率最高的组合如下:
① 客户端发送数据 → 服务器读事件:
EPOLLIN
② 客户端断开 → 两种情况:
正常断开:
EPOLLHUP | EPOLLIN
异常断开:
EPOLLERR
半关闭(常见于 HTTP/1.1):
EPOLLIN | EPOLLRDHUP
③ 写缓冲区满后又能写了:
EPOLLOUT
④ 边缘触发(ET)+ 写事件:
EPOLLIN | EPOLLET
⑤ epoll 已经出错:
EPOLLERR
这种必须马上 close。
五、事件含义详细讲解
EPOLLIN(最常见)
套接字可读
read() 不会阻塞
包含:对端关闭时 read=0 也会触发 IN
EPOLLOUT
套接字可写
send() 不会阻塞
EPOLLERR(非常关键)
底层 TCP 出错(如对方 RST)
必须马上关闭 fd
如果不处理,会造成 CPU 100%
EPOLLHUP(非常关键)
对端关闭(FIN)
必须关闭 fd,不要再读写
EPOLLRDHUP(高质量服务器必须用)
对端半关闭(如 shutdown)
HTTP keep-alive 场景很常见
比 EPOLLHUP 更准确
EPOLLET:边缘触发
高性能服务器必用(比如 nginx)
必须一次性读完所有数据,否则不会再触发
EPOLLONESHOT:一次性事件
用于多线程模型,防止多个线程抢同一个 fd
六、一个真实服务器事件判断模板
uint32_t ev = events;// 优先处理错误
if (ev & (EPOLLERR | EPOLLHUP | EPOLLRDHUP)) {closeConn(fd);return;
}// 可读事件
if (ev & EPOLLIN) {handleRead(fd);
}// 可写事件
if (ev & EPOLLOUT) {handleWrite(fd);
}
这就是 nginx、muduo、libevent 的标准写法。
