epoll_wait修改文件描述符集合异常问题排查求助
问题
用C++开发服务器,基于epoll_wait实现无请求时阻塞,原本运行正常。添加pipe文件描述符到epoll集合后出现异常:epoll_wait结束阻塞后,文件描述符集合原本应为{pipefd[0], sock},却变成了{sock, sock},原pipe的文件描述符“消失”。已知epoll仅应修改事件而非文件描述符,附上复现代码(需用netcat连接2000端口触发epoll_wait),请求排查原因。
#include <iostream> #include <sys/epoll.h> #include <fcntl.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/types.h> bool loop; struct epoll_event fds[255]; int main() { int pipefd[2]; //create a pipe, pipefd[0] is the read fd and pipefd[1] is the write one. if(pipe(pipefd) == -1) { std::cout<<"erreur"<<std::endl; } int epollfd = epoll_create1(0); if (epollfd == -1) { std::cout<<"erreur"<<std::endl; } if (fcntl(pipefd[0], F_SETFL, O_NONBLOCK) == -1) { std::cout<<"erreur"<<std::endl; } //adding it to the fds set fds[0].events = EPOLLIN| EPOLLET; fds[0].data.fd = pipefd[0]; if (epoll_ctl(epollfd, EPOLL_CTL_ADD, pipefd[0], &fds[0]) == -1) { std::cout <<"erreur"<<std::endl; } int on = 1; //Bind socket to adress and add some parameters sockaddr_in bindParams; bindParams.sin_family =AF_INET; bindParams.sin_port = htons(2000); int sock = socket(AF_INET,SOCK_STREAM,0); inet_aton("127.0.0.1",&bindParams.sin_addr); bind(sock,(struct sockaddr *)&bindParams,(socklen_t)sizeof(bindParams)); setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,(char *)&on,(socklen_t)sizeof(on)); fcntl(sock, F_SETFL, O_NONBLOCK); //add it to the fds set fds[1].events = EPOLLIN | EPOLLET; fds[1].data.fd = sock; if(epoll_ctl(epollfd, EPOLL_CTL_ADD, sock, &fds[1]) == -1) { std::cout<<"erreur"<<std::endl; } listen(fds[1].data.fd, 5); std::cout << "this is the first fd : " <<fds[0].data.fd<<std::endl; std::cout << "this is the second fd : " <<fds[1].data.fd<<std::endl; std::cout <<"before epollwait"<<std::endl; do { int fdr = epoll_wait(epollfd, fds, 255, -1); std::cout <<"after epollwait"<<std::endl; std::cout << "this is the first fd : " <<fds[0].data.fd<<std::endl; std::cout << "this is the second fd : " <<fds[1].data.fd<<std::endl; if(fds[0].events == EPOLLIN) { std::cout << "pipe called\n"; } if(fds[1].events == EPOLLIN) { std::cout << "listener called\n"; } }while(true); return 0; };
原因分析
核心问题是复用了fds数组作为epoll_wait的输出缓冲区:
- 初始化阶段,你用
fds[0]和fds[1]存储pipe和socket的注册信息,调用epoll_ctl加入epoll实例。 - 进入循环后,直接把同一个
fds数组传给epoll_wait,而epoll_wait的行为是覆盖传入数组的内容,只填充有事件触发的fd及其事件,不会保留未触发事件的原有条目。 - 当socket的EPOLLIN事件触发时,
epoll_wait会将事件信息写入fds数组的起始位置(fds[0]),此时pipe无事件,fds[1]的内容是之前的残留或被覆盖,你错误认为fds[0]和fds[1]始终对应最初注册的两个fd,才会出现“原pipe fd消失、重复显示sock fd”的错觉。
解决方案
将注册用的事件结构和epoll_wait的输出缓冲区分开,不要复用同一个数组:
- 单独定义用于接收活跃事件的数组,比如
struct epoll_event active_fds[255]; - 循环中调用
epoll_wait时传入新数组,根据返回的触发事件数量fdr遍历结果,而非固定访问原数组的索引。
修改后的关键代码片段:
// 新增独立的活跃事件缓冲区 struct epoll_event active_fds[255]; do { int fdr = epoll_wait(epollfd, active_fds, 255, -1); std::cout <<"after epollwait"<<std::endl; // 遍历所有触发的事件,而非固定访问原数组索引 for(int i=0; i<fdr; i++){ std::cout << "触发事件的fd: " << active_fds[i].data.fd << std::endl; if(active_fds[i].events & EPOLLIN){ if(active_fds[i].data.fd == pipefd[0]){ std::cout << "pipe called\n"; } else if(active_fds[i].data.fd == sock){ std::cout << "listener called\n"; // ET模式下需循环处理完所有待接受连接 int client_fd; while((client_fd = accept(sock, nullptr, nullptr)) != -1){ // 处理客户端连接逻辑... } } } } }while(true);
额外提醒:
- 必须根据
epoll_wait的返回值fdr遍历结果,不要假设数组固定位置对应初始注册的fd。 - 使用EPOLLET边缘触发模式时,要一次性处理完fd上的所有数据/事件(比如accept循环调用直到返回EAGAIN),否则后续不会再触发事件。
listen(fds[1].data.fd, 5);可直接替换为listen(sock, 5);,代码更清晰。
内容的提问来源于stack exchange,提问作者Akese315
相关产品推荐
相关产品推荐

