C语言 epoll()EPOLLET事件处理被跳过

bzzcjhmw  于 2023-04-05  发布在  其他
关注(0)|答案(1)|浏览(129)

我写了一个简单的客户端-服务器应用程序。当我开始测试时,我注意到当为套接字fd设置EPOLLET标志时,并非所有事件都能正确处理。
在循环中,我连接到服务器并向其发送了一些数据,为了测试,我进行了10,000次连接,从服务器端我统计了每个事件,无论是套接字描述符上的事件还是客户端的事件,并且总是根据日志服务器花费的时间比预期的要少。(大约10000次迭代(~ 9200)。我不明白这和什么有关。
我是否正确地处理了事件,或者我是否遗漏了什么?也许我的测试方法不太正确(每当套接字fd上发生什么事情时,我都会计算输出的行数)
Short compiled code
客户端c:

#include <assert.h>
#include <netdb.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

#define DEF_IP             "127.0.0.1"
#define DEF_PORT           "3940"
#define DEF_MESSAGES_COUNT 10000

typedef struct addrinfo addrinfo;

static int conn_init();

int main() {
    int      i, fd_socket;
    ssize_t  bytes_send;
    uint64_t num;

    for (i = 0; i < DEF_MESSAGES_COUNT; i++) {
        fd_socket = conn_init();

        num = htobe64(i);
        bytes_send = send(fd_socket, &num, sizeof(num), 0);
        assert(bytes_send > -1);

        printf("I! Client: sent [%d], bytes: [%ld]\n", i, bytes_send);
        close(fd_socket);
    }

    printf("sended: %d messages ([0] - [%d])\n", i, i - 1);
    return 0;
}

static int conn_init() {
    addrinfo  info_hints = {0};
    addrinfo *info_server;
    int       fd_socket, ret;

    info_hints.ai_family = AF_UNSPEC;
    info_hints.ai_socktype = SOCK_STREAM;

    ret = getaddrinfo(DEF_IP, DEF_PORT, &info_hints, &info_server);
    assert(ret == 0);

    fd_socket = socket(info_server->ai_family, info_server->ai_socktype, info_server->ai_protocol);
    assert(fd_socket > -1);

    ret = connect(fd_socket, info_server->ai_addr, info_server->ai_addrlen);
    assert(ret == 0);

    freeaddrinfo(info_server);
    return fd_socket;
}

server.c:

#include <assert.h>
#include <fcntl.h>
#include <netdb.h>
#include <stdbool.h>
#include <stdio.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

typedef struct addrinfo         addrinfo;
typedef struct epoll_event      epoll_event;
typedef struct sockaddr_storage sockaddr_storage;
typedef struct sockaddr         sockaddr;

#define DEF_IP         "127.0.0.1"
#define DEF_PORT       "3940"
#define DEF_MAX_EVENTS 1000
#define DEF_BACKLOG    1000

static int  conn_handle_socket(int fd_socket);
static void epoll_add(int fd_epoll, int fd, uint32_t flag);

int main() {
    int         fd_socket, fd_connect, fd_epoll, ret;
    epoll_event events[DEF_MAX_EVENTS];
    addrinfo    info_hints = {0};
    addrinfo *  info_server;

    info_hints.ai_family = AF_UNSPEC;      // IPv4 или IPv6
    info_hints.ai_socktype = SOCK_STREAM;  // TCP
    info_hints.ai_flags = AI_PASSIVE;      // use my IP

    ret = getaddrinfo(DEF_IP, DEF_PORT, &info_hints, &info_server);
    assert(ret == 0);

    fd_socket = socket(info_server->ai_family, info_server->ai_socktype, info_server->ai_protocol);
    assert(fd_socket > -1);

    ret = setsockopt(fd_socket, SOL_SOCKET, SO_REUSEADDR, (const char *)&(int){1}, sizeof(int));
    assert(ret == 0);

    ret = bind(fd_socket, info_server->ai_addr, info_server->ai_addrlen);
    assert(ret == 0);

    freeaddrinfo(info_server);

    ret = listen(fd_socket, DEF_BACKLOG);
    assert(ret == 0);

    // init epoll
    fd_epoll = epoll_create1(0);
    assert(fd_epoll > 0);

    // monitor socket fd
    epoll_add(fd_epoll, fd_socket, EPOLLET);

    printf("I! Server: is ready\n");

    while (1) {
        int num_events = epoll_wait(fd_epoll, events, DEF_MAX_EVENTS, -1);
        assert(num_events > -1);

        for (int i = 0; i < num_events; i++) {
            int fd_tmp = events[i].data.fd;

            if (fd_tmp == fd_socket) {
                printf("I! Server: SOCKET FD\n");
                fflush(stdout);

                // accept client
                fd_connect = conn_handle_socket(fd_socket);
                epoll_add(fd_epoll, fd_connect, EPOLLONESHOT);
            } else if (events[i].events & EPOLLIN) {
                printf("I! Server: CLIENT FD\n");
                fflush(stdout);

                // 'processing' clients
                assert(epoll_ctl(fd_epoll, EPOLL_CTL_DEL, fd_tmp, NULL) == 0);
                close(fd_tmp);
            }
        }
    }

    assert(epoll_ctl(fd_epoll, EPOLL_CTL_DEL, fd_socket, NULL) == 0);
    assert(epoll_ctl(fd_epoll, EPOLL_CTL_DEL, STDIN_FILENO, NULL) == 0);
    close(fd_socket);
    close(fd_epoll);
    return 0;
}

static int conn_handle_socket(int fd_socket) {
    int              fd_connect;
    sockaddr_storage addr_connected;
    socklen_t        sin_size;

    sin_size = sizeof(addr_connected);
    fd_connect = accept(fd_socket, (sockaddr *)&addr_connected, &sin_size);
    assert(fd_connect > -1);  // ignore EAGAIN || EWOULDBLOCK

    return fd_connect;
}

static void epoll_add(int fd_epoll, int fd, uint32_t flag) {
    int         flags, ret;
    epoll_event ev = {0};

    ev.events = EPOLLIN;
    if (flag == EPOLLET || flag == EPOLLONESHOT) {
        ev.events |= flag;
    }
    ev.data.fd = fd;

    ret = epoll_ctl(fd_epoll, EPOLL_CTL_ADD, fd, &ev);
    assert(ret == 0);

    flags = fcntl(fd, F_GETFL, 0);
    assert(flags > -1);

    flags |= O_NONBLOCK;

    ret = fcntl(fd, F_SETFL, flags);
    assert(ret > -1);
}
btxsgosb

btxsgosb1#

在ET(边缘触发)模式下,如果同一fd上生成多个事件,则仅触发一次。

for (int i = 0; i < num_events; i++) {
        int fd_tmp = events[i].data.fd;

        if (fd_tmp == fd_socket) {
            printf("I! Server: SOCKET FD\n");
            fflush(stdout);

            // accept client
            fd_connect = conn_handle_socket(fd_socket);
            epoll_add(fd_epoll, fd_connect, EPOLLONESHOT);
        } else if (events[i].events & EPOLLIN) {
            printf("I! Server: CLIENT FD\n");
            fflush(stdout);

            // 'processing' clients
            assert(epoll_ctl(fd_epoll, EPOLL_CTL_DEL, fd_tmp, NULL) == 0);
            close(fd_tmp);
        }
    }

对于每个epoll_wait返回,它只接受一个客户端。其他客户端仍然在listenfd的backlog中。
有两种解决方案:
1.使用LT(级别触发)。因为在LT模式下,epoll_wait将保持返回,只要FD有传入的事件要处理。
1.根据epoll的man(7)
使用EPOLLET标志的应用程序应该使用非阻塞文件描述符,以避免阻塞读取或写入使处理多个文件描述符的任务陷入饥饿。建议使用epoll作为边缘触发(EPOLLET)接口的方法如下:a)使用非阻塞文件描述符;以及B)通过仅在read(2)或write(2)返回EAGAIN之后等待事件。并且接受也是一种读取。

相关问题