Epoll wait 修改文件描述符集

Epoll wait modifies filedescriptor set

提问人:Akese315 提问时间:10/1/2023 更新时间:10/1/2023 访问量:47

问:

我目前正在用 c++ 构建一个服务器,所以当我没有任何请求时,我用epoll_wait来阻止。它运行良好,直到我决定在epollfd_set上添加一根管道。我不知道epoll_wait是否可以管理管道,但由于它是一个文件描述符,我认为它可以。于是我照做了,一件奇怪的事情发生了。当 epoll 停止阻塞时,我检查了epollfd_set,而不是像那样使用 fd 数组: [] = {1,2}, 我得到 [] = {2,2},第一个套接字消失了,已被 2 取代。我知道 epoll 应该只更改事件而不是 fd。你能告诉我发生了什么吗?

我在一个简单的主文件中重现了这个错误。

#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;
};


此外,要退出epoll_wait您必须使用像 netcat 这样的客户端在端口 2000 上发送某些内容。我希望这只是我的理解,这很糟糕,而不是真正的epoll_wait错误。

我希望你能够解决这个烦人的问题。提前致谢。

C++ 服务器 epoll

评论

0赞 Homer512 10/1/2023
“我知道 epoll 应该只改变事件而不是 fd”你在哪里读到的?第二个参数是纯输出。返回值指示设置了多少个描述符。返回值很可能是 1,因此数组中的第一个条目包含有效数据;表示解除阻止的描述符。epoll_wait
0赞 Homer512 10/1/2023
“此外,要退出epoll_wait您必须使用像 netcat 这样的客户端在端口 2000 上发送内容” 您要从哪里终止?SIGTERM 和键盘中断应该仍然有效。如果要从其他线程或信号处理程序中断轮询线程,则将其他管道添加到等待列表通常很有用。然后向它发送一个字节或关闭写入端(这将取消阻止,指示 EOF)

答:

4赞 Sam Varshavchik 10/1/2023 #1

显示的代码对事件的用法似乎是基于一种误解,即事件的功能类似于管理其文件描述符数组的方式。这是不正确的。epollepollpoll()

所示代码仔细地初始化 ,并将其馈送到 、 初始化 ,然后馈送到 。之后,显示的代码调用 ,并假定将具有第一个文件描述符的事件,并且将具有任何其他第二个文件描述符的事件。fds[0]EPOLL_CTL_ADDfds[1]EPOLL_CTL_ADDepoll_waitfds[0]fds[1]

这不是工作方式。对于初学者来说,可用于设置被 使用的两个文件描述符。 狼吞虎咽地吃掉你喂它的每一口,然后它被储存起来,以便安全地保存在内核中。epollfds[0]EPOLL_CTL_ADDEPOLL_CTL_ADD

然后,从 epoll 集中的所有文件描述符中抛出事件,一次一个事件epoll_wait

返回值 from 告诉您传递给 的 epoll 数组参数中有多少个事件。显示的代码期望始终有两个事件,将是 的事件,将是 的事件,将是 的事件:epoll_waitepoll_waitfds[0]pipefd[0]fds[1]sock

        if(fds[0].events == EPOLLIN)
        {
            std::cout << "pipe called\n";
        }
        if(fds[1].events == EPOLLIN)
        {
            std::cout << "listener called\n";
        }

这是错误的。 可能会返回 1。这意味着它包含一个事件。就是这样。 告诉您哪个文件描述符生成了事件,它可以是 EITHER 或 。epoll_waitfds[0]fds[0].data.fdpipefd[0]sock

如果返回 2 表示 和 都有事件,并且任何文件描述符都可以出现在 和 中。epoll_waitfds[0]fds[1]fds[0].data.fdfds[1].data.fd

此时, 你被引导去检查 epoll(7) 手册页中的 “建议用法示例”。这个例子让你大饱眼福,它用代码向你解释了如何正确地做到这一点:

        nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);

您必须从 中捕获返回值。不要忽视它。把它放在你面前,怀着崇敬和尊重的心情握住它。因为您需要使用它:epoll_wait

               for (n = 0; n < nfds; ++n) {

返回值 from 告诉您有多少个事件。epoll_wait

                   if (events[n].data.fd == listen_sock) {

然后检查每个事件的 ,它告诉您该事件是针对哪个文件描述符的,然后相应地继续。data.fd

评论

0赞 David C. Rankin 10/1/2023
“狼吞虎咽”和“盛宴”——你今晚一定饿了吧,山姆?:)