epoll:指定EPOLLONESHOT时电平触发和边沿触发的区别

epoll: difference between level triggered and edge triggered when EPOLLONESHOT specified

当指定 EPOLLONESHOT 时,电平触发和边沿触发模式有什么区别?

已经有一个类似的问题 here。 "Crouching Kitten" 的答案似乎不正确(据我了解,另一个答案没有回答我的问题)。

我试过以下方法:

当指定 EPOLLONESHOT 时,LT 和 ET 之间有什么区别吗?

我认为底线答案是 "there is not difference"。

查看the code,fd 似乎记得在被一次性禁用之前最后设置的位。它记得是一枪,它记得是不是外星人。

这是徒劳的,因为 fd 被禁用直到被修改,下一次调用 EPOLL_CTL_MOD 将清除所有这些,并替换为新的 MOD 所说的内容。

话虽如此,我不明白为什么有人会同时想要 EPOLLETEPOLLONESHOT。对我来说,EPOLLET 的全部意义在于,在某些编程模型(即微线程)下,它完美地遵循语义。这意味着我可以在一开始就将 fd 添加到 epoll,然后再也不必执行另一个 epoll 相关的系统调用。

另一方面,

EPOLLONESHOT 被那些想要严格控制何时观看 fd 和何时不观看的人使用。根据定义,这与 EPOLLET 的用途相反。我只是认为这两者在概念上不兼容。

另一位发帖人说“我不明白为什么有人会同时想要 EPOLLETEPOLLONESHOT。”实际上,根据 epoll(7),有一个用例:

Since even with edge-triggered epoll, multiple events can be generated upon receipt of multiple chunks of data, the caller has the option to specify the EPOLLONESHOT flag, to tell epoll to disable the associated file descriptor after the receipt of an event with epoll_wait(2).

关键是EPOLL会不会对待EPOLLET的组合| EPOLLONESHOT 和 EPOLLLT | EPOLLONESHOT 作为特例。据我所知,事实并非如此。 EPOLL 只是分别关心它们。对于EPOLLET和EPOLLLT,不同之处仅在于函数ep_send_events,如果设置了EPOLLET,则函数会调用list_add_tailepitem添加到[=15]的就绪列表中=] 对象。 给EPOLLONESHOT,作用是禁用fd。所以我认为他们之间的区别就是ETLT之间的区别。你可以使用下面的代码检查结果我认为

// server.cc
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <pthread.h>

#define MAX_EVENT_NUMBER 1024

int setnonblocking(int fd)
{
    int old_option = fcntl(fd, F_GETFL);
    int new_option = old_option | O_NONBLOCK;
    fcntl(fd, F_SETFL, new_option);
    return old_option;
}

void addfd(int epollfd, int fd, bool oneshot)
{
    epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN | EPOLLET;
    if(oneshot)
        event.events |= EPOLLONESHOT;
    epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
    setnonblocking(fd);
}
// reset the fd with EPOLLONESHOT
void reset_oneshot(int epollfd, int fd)
{
    epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN | EPOLLET | EPOLLONESHOT;
    epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &event);
}

int main(int argc, char** argv)
{
    if(argc <= 2)
    {
        printf("usage: %s ip_address port_number\n", basename(argv[0]));
        return 1;
    }
    const char* ip = argv[1];
    int port = atoi(argv[2]);

    int ret = 0;
    struct sockaddr_in address;
    bzero(&address, sizeof(address));
    address.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &address.sin_addr);
    address.sin_port = htons(port);

    int listenfd = socket(PF_INET, SOCK_STREAM, 0);
    assert(listenfd >= 0);
    ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address));
    assert(ret != -1);

    ret = listen(listenfd, 5);
    assert(ret != -1);

    epoll_event events[MAX_EVENT_NUMBER];
    int epollfd = epoll_create(5);
    addfd(epollfd, listenfd, false);
    while(1)
    {
        printf("next loop: -----------------------------");
        int ret = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);
        if(ret < 0)
        {
            printf("epoll failure\n");
            break;
        }
        for(int i = 0; i < ret; i++)
        {
            int sockfd = events[i].data.fd;
            if(sockfd == listenfd)
            {
                printf("into listenfd part\n");
                struct sockaddr_in client_address;
                socklen_t client_addrlength = sizeof(client_address);
                int connfd = accept(listenfd, (struct sockaddr*)&client_address,
                     &client_addrlength);
                printf("receive connfd: %d\n", connfd);
                addfd(epollfd, connfd, true);
                // reset_oneshot(epollfd, listenfd);
            }
            else if(events[i].events & EPOLLIN)
            {
                printf("into linkedfd part\n");
                printf("start new thread to receive data on fd: %d\n", sockfd);
                char buf[2];
                memset(buf, '[=10=]', 2);
                // just read one byte, and reset the fd with EPOLLONESHOT, check whether still EPOLLIN event
                int ret = recv(sockfd, buf, 2 - 1, 0);

                if(ret == 0)
                {
                    close(sockfd);
                    printf("foreigner closed the connection\n");
                    break;
                }
                else if(ret < 0)
                {
                    if(errno == EAGAIN)
                    {
                        printf("wait to the client send the new data, check the oneshot memchnism\n");
                        sleep(10);
                        reset_oneshot(epollfd, sockfd);
                        printf("read later\n");
                        break;
                    }
                }
                else {
                    printf("receive the content: %s\n", buf);
                    reset_oneshot(epollfd, sockfd);
                    printf("reset the oneshot successfully\n");
                }
            }
            else 
                printf("something unknown happend\n");
        }
        sleep(1);
    }
    close(listenfd);
    return 0;
}

客户是

from socket import *
import sys
import time

long_string = b"this is a long content which need two time to fetch"

def sendOneTimeThenSleepAndClose(ip, port):
    s = socket(AF_INET, SOCK_STREAM);
    a = s.connect((ip, int(port)));
    print("connect success: {}".format(a));
    data = s.send(b"this is test");
    print("send successfuly");
    time.sleep(50);
    s.close();
sendOneTimeThenSleepAndClose('127.0.0.1', 9999)