Linux C socket在使用epoll时如何防止2个forked进程接受同一个连接?

Linux C socket how to prevent 2 forked process from accepting the same connection when using epoll?

for (int t = 0; t < physicalCoreCount; t++) {
        int pid = fork();
        if (pid==0) {
            // setting up epoll    
            epoll_fd = epoll_create1(0);                
            event.data.fd = listenSocketfd;
            event.events = EPOLLIN | EPOLLET;
            ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listenSocketfd, &event);                
            events = (epoll_event*)calloc(LISTENQ, sizeof(event));
           
            //*****/
            while (1) {
                int ndfs = epoll_wait(epoll_fd, events, curfdCount, -1);
                if (ndfs==-1) {                        
                    continue;
                }
                for (int i=0; i < ndfs; i++) {
                    if (events[i].data.fd == listenSocketfd) { // original listener
                        int new_connfd = accept(events[i].data.fd, (sockaddr*)&clientaddr, &clientlen);
                        if (new_connfd==-1) {
                            if (errno==EAGAIN || errno==EWOULDBLOCK) {
                                continue;
                            }
                            else exitPerrorLog("accept()");
                        }

                        set_non_block(new_connfd);
                        event.events = EPOLLIN | EPOLLET;
                        event.data.fd = new_connfd;
                        if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, new_connfd, &event) < 0)
                            exitPerrorLog("epoll_ctl");

                        clientaddrOfFd[new_connfd] = new sockaddr_in();
                        memcpy(clientaddrOfFd[new_connfd], &clientaddr, sizeof(clientaddr));
                        curfdCount++;
                    }   
                    else {
                        process(events[i].data.fd, clientaddrOfFd[events[i].data.fd]);
                        //epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, &event);                           
                        if (curfdCount > 10000) curfdCount = 10000; //curfdCount--;
                    }
                }
            }
        }
    }

问题出现是因为我正在尝试实现持久连接(响应后不关闭)。但是,child 0 可以 accept() 一个套接字文件描述符 X,处理它,但后来 child 1 可以 accept() 相同的文件描述符。由于我没有关闭 child 0 上的连接(实现 HTTP1.1 keep-alive),现在 2 children 是 reading/writing 到同一个文件描述符。

有什么方法可以避免这个问题?谢谢。

编辑: 重要更新:所以主要问题是我认为 2 个相同的 FD 意味着它是相同的连接,我想避免这种情况,因为它会导致 2 children reading/writing 同理。如果这种情况没有发生(read/write 重叠),那么我认为问题就解决了。有人可以确认吗?

您正在创建多个 epoll 实例并在每个实例的侦听套接字上注册 edge-triggered 事件。自然地,当一个新的连接可以接受时,你会从每个连接中得到一个事件。但是,只有一个进程可以成功接受每个连接。正如评论中所观察到的,两个不同的 children 可能会接受在各自进程中分配了相同文件描述符编号的连接,但这并不意味着它们引用相同的套接字。

您有多种选择,但其中突出的是:

  • 使用单个 epoll 实例,由所有进程共享。您可以通过让 parent 在分叉任何 children 之前创建它来自动获得它。在这种情况下,只有一个 child 会收到每个 edge-triggered 事件。当然,如果 children 打算注册只应该由他们接收的事件,那么这将不会很好地工作。

  • 只接受(没有双关语意)多个进程将在连接可用时接收事件,并处理它。这似乎就是你现在正在做的(通过忽略 accept() 中的 EAGAINEWOULDBLOCK 错误),我看不出你不应该继续这样做的特别原因。