0

I wrote a simple client-server application. When I started testing, I noticed that not all events are processed properly when the EPOLLET flag is set for socket fd.

In the loop, I connected to the server and sent some data to it. For the test, I made 10,000 connections, and from the server side I counted each event, whether it was an event on a socket descriptor or a client one. And always according to the logs, the server took less than expected. (for 10000 iterations approximately (~ 9200). And I don’t understand what this could be connected with.

Am I handling the events correctly or am I missing something? Maybe my approach to testing is not quite right (I counted output of lines every time something happened on socket fd)

Short compiled code

client.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);
}

1 Answers1

1

In ET(edge-triggered) mode, if multiple events generated on the same fd, it will only trigger once.

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

it only accepts one client for each epoll_wait returns. The other clients are still in the backlog of listenfd.

There are two solutions:

  1. Use LT (level triggered). Because in LT mode, epoll_wait will keep returning as long as FDs have incoming events to handle.
  2. As per man(7) of epoll

An application that employs the EPOLLET flag should use nonblocking file descriptors to avoid having a blocking read or write starve a task that is handling multiple file descriptors. The suggested way to use epoll as an edge-triggered (EPOLLET) interface is as follows: a) with nonblocking file descriptors; and b) by waiting for an event only after read(2) or write(2) return EAGAIN. And accept is a kind of read as well.

Zwy
  • 353
  • 1
  • 10
  • Accept() until I got EAGAIN solved my problem. Thanks for the explanations. reading man about epoll I did not understand this moment clearly about EPOLLET. The question arises, how to make the server more efficient? Should I dedicate a child process or thread to process the accept() function, which will continuously call the function until an EAGAIN is received if something has passed on the socket fd? Maybe you can recommend something to read. There is so much information on the Internet, but it is all superficial. – vsezanatodazheeto Mar 29 '23 at 17:33
  • Whether optimize it or not depends on bottleneck of your program. Normally, client reading and writing can be dispatched to child threads or processes and the main thread accept the incoming fd. If you are interested in this area, you can search source code of nginx. I believe there are a bunch of posts on the internet explaining the philosophy of nginx. – Zwy Mar 29 '23 at 19:18
  • By the way, i redid event handling. When something happens on socket fd, i call accept() in a loop until i get errno == EAGAIN, but i still 'missing' some events, albeit less – vsezanatodazheeto Mar 29 '23 at 23:19
  • could you post your new code in the question? – Zwy Mar 30 '23 at 07:55