EPOLLRDHUP
在使用epoll的情况下:服务器端运行以下程序,当客户端调用close()关闭后,服务器端会执行满足 evs[i].events & EPOLLIN 条件的代码
for (;;)
{
int n = epoll_wait(epfd, evs, 20, -1);
for (int i = 0; i < n; i++)
{
if (evs[i].events & EPOLLIN)
{
//client close之后,这里收不到客户端终止的信号???
}
if (evs[i].events & EPOLLOUT)
{
}
if (evs[i].events & EPOLLERR)
{
}
if (evs[i].events & EPOLLHUP)
{
}
}
}
明明是客户端请求断开,系统却报告一个错误,但从用户角度来看请求的结果正常返回,没有任何问题。
通过查询资料,对这个现象深入分析后发现,这是一个基于 epoll 的连接池实现上的问题。
首先解释一下导致这个现象的原因:
在使用 epoll 时,对端正常断开连接(调用 close()),在服务器端会触发一个 epoll 事件。在低于 2.6.17 版本的内核中,这个 epoll 事件一般是 EPOLLIN,即 0x1,代表连接可读。连接池检测到某个连接发生 EPOLLIN 事件且没有错误后,会认为有请求到来,将连接交给上层进行处理。这样一来,上层尝试在对端已经 close() 的连接上读取请求,只能读到 EOF,会认为发生异常,报告一个错误。
因此在使用 2.6.17 之前版本内核的系统中,我们无法依赖封装 epoll 的底层连接库来实现对对端关闭连接事件的检测,只能通过上层读取数据时进行区分处理。
2.6.17 版本内核中增加了 EPOLLRDHUP 事件,代表对端断开连接。在使用 2.6.17 之后版本内核的服务器系统中,对端连接断开触发的 epoll 事件会包含 EPOLLIN | EPOLLRDHUP,即 0x2001。有了这个事件,对端断开连接的异常就可以在底层进行处理了,不用再移交到上层。
注意,在使用 2.6.17 之前版本内核的系统中,sys/epoll.h 的 EPOLL_EVENTS 枚举类型中是没有 EPOLLRDHUP 事件的,所以带 EPOLLRDHUP 的程序无法编译通过。