水平触发模式下epoll多次触发EPOLLIN事件的原因排查
基于epoll实现状态机时,EPOLLIN持续触发导致read返回EAGAIN的问题
我用C语言基于epoll实现状态机,跟踪用于接收连接的TCP监听套接字文件描述符,以及已建立连接的文件描述符。目前遇到一个异常情况:首次读写事件完成后,epoll会多次触发本应阻塞的EPOLLIN事件,此时调用read会返回-1,错误码为EAGAIN或EWOULDBLOCK。
简化代码示例
主函数逻辑
int main() { // 省略socket、bind、listen、epoll_create相关初始化代码 int num = epoll_wait(epollfd, events, maxev, -1); for (int i = 0; i < num; i++) { struct epoll_event event = events[i]; if (event.data.fd == sfd) { // 接受连接并将套接字设置为非阻塞模式 int cfd = accept_connection(event.data.fd); struct epoll_event ee = { 0 }; ee.data.fd = cfd; ee.events = EPOLLIN; epoll_ctl(epollfd, EPOLL_CTL_ADD, cfd, &ee); } else { int fd = event.data.fd; if (event.events & EPOLLIN) { // 调用从fd读取数据的read_from_fd函数 event.events |= EPOLLOUT; epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &event); } if (event.events & EPOLLOUT) { // 调用向fd写入数据的函数 event.events |= EPOLLIN; epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &event); } } } return 0; }
注:当
read返回0(客户端关闭连接)时,会通过EPOLL_CTL_DEL将该fd从epoll实例中移除并关闭文件描述符。
读取数据函数
ssize_t read_from_fd(int fd) { ssize_t n; printf("read called\n"); n = read(fd, buf, MAX_BUF); if (n == -1) { if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) { printf("read blocked\n"); } return -1; } if (n == 0) { // 从epoll实例移除fd并关闭fd的逻辑 } return n; }
现象展示
启动服务器后,客户端执行连接、发送一条消息、读取一条消息后关闭连接,服务器输出如下:
$ ./server accepted connection <fd> read called write called read called read blocked read called closed connection with <fd>
注:有时会出现多条
read called和read blocked日志,数量从1到约25不等。
关联问题
如果移除写操作完成后调用的epoll_ctl修改事件的代码,epoll会多次触发EPOLLOUT事件,且客户端接收消息后关闭连接会导致程序触发SIGPIPE信号崩溃。
内容的提问来源于stack exchange,提问作者boreddad420
相关产品推荐
相关产品推荐

