You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的输出缓冲区分开,不要复用同一个数组:

  1. 单独定义用于接收活跃事件的数组,比如struct epoll_event active_fds[255];
  2. 循环中调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 17:46:05