簡體   English   中英

帶epoll的多線程UDP服務器?

[英]Multithreading UDP server with epoll?

我想在C / Linux中開發一個多線程UDP服務器。 該服務在單個端口x上運行,因此只能將單個UDP套接字綁定到它。 為了在高負載下工作,我有n個線程(靜態定義),比如說每個CPU 1個線程。 可以使用epoll_wait將工作傳遞給線程,因此線程可以通過'EPOLLET |按需喚醒 EPOLLONESHOT”。 我附上了一個代碼示例:

static int epfd;
static sig_atomic_t sigint = 0;

...

/* Thread routine with epoll_wait */
static void *process_clients(void *pevents)
{
    int rc, i, sock, nfds;
    struct epoll_event ep, *events = (struct epoll_event *) pevents;

    while (!sigint) {
        nfds = epoll_wait(epfd, events, MAX_EVENT_NUM, 500);

        for (i = 0; i < nfds; ++i) {
           if (events[i].data.fd < 0)
                continue;

           sock = events[i].data.fd;

           if((events[i].events & EPOLLIN) == EPOLLIN) {
               printf("Event dispatch!\n");
               handle_request(sock); // do a recvfrom
           } else
               whine("Unknown poll event!\n");

           memset(&ep, 0, sizeof(ep));
           ep.events = EPOLLIN | EPOLLET | EPOLLONESHOT;
           ep.data.fd = sock;

           rc = epoll_ctl(epfd, EPOLL_CTL_MOD, sock, &ep);
           if(rc < 0)
               error_and_die(EXIT_FAILURE, "Cannot add socket to epoll!\n");
       }
    }

    pthread_exit(NULL);
}

int main(int argc, char **argv)
{
    int rc, i, cpu, sock, opts;
    struct sockaddr_in sin;
    struct epoll_event ep, *events;
    char *local_addr = "192.168.1.108";
    void *status;
    pthread_t *threads = NULL;
    cpu_set_t cpuset;

    threads = xzmalloc(sizeof(*threads) * MAX_THRD_NUM);
    events = xzmalloc(sizeof(*events) * MAX_EVENT_NUM);

    sock = socket(PF_INET, SOCK_DGRAM, 0);
    if (sock < 0)
        error_and_die(EXIT_FAILURE, "Cannot create socket!\n");

    /* Non-blocking */
    opts = fcntl(sock, F_GETFL);
    if(opts < 0)
        error_and_die(EXIT_FAILURE, "Cannot fetch sock opts!\n");
    opts |= O_NONBLOCK;
    rc = fcntl(sock, F_SETFL, opts);
    if(rc < 0)
        error_and_die(EXIT_FAILURE, "Cannot set sock opts!\n");

    /* Initial epoll setup */
    epfd = epoll_create(MAX_EVENT_NUM);
    if(epfd < 0)
        error_and_die(EXIT_FAILURE, "Error fetching an epoll descriptor!\n");

    memset(&ep, 0, sizeof(ep));
    ep.events = EPOLLIN | EPOLLET | EPOLLONESHOT;
    ep.data.fd = sock;

    rc = epoll_ctl(epfd, EPOLL_CTL_ADD, sock, &ep);
    if(rc < 0)
        error_and_die(EXIT_FAILURE, "Cannot add socket to epoll!\n");

    /* Socket binding */
    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = inet_addr(local_addr);
    sin.sin_port = htons(port_xy);

    rc = bind(sock, (struct sockaddr *) &sin, sizeof(sin));
    if (rc < 0)
        error_and_die(EXIT_FAILURE, "Problem binding to port! "
                      "Already in use?\n");

    register_signal(SIGINT, &signal_handler);

    /* Thread initialization */
    for (i = 0, cpu = 0; i < MAX_THRD_NUM; ++i) {
        rc = pthread_create(&threads[i], NULL, process_clients, events);
        if (rc != 0)
            error_and_die(EXIT_FAILURE, "Cannot create pthread!\n");

        CPU_ZERO(&cpuset);
        CPU_SET(cpu, &cpuset);

        rc = pthread_setaffinity_np(threads[i], sizeof(cpuset), &cpuset);
        if (rc != 0)
            error_and_die(EXIT_FAILURE, "Cannot create pthread!\n");

        cpu = (cpu + 1) % NR_CPUS_ON;
    }

    printf("up and running!\n");

    /* Thread joining */
    for (i = 0; i < MAX_THRD_NUM; ++i) {
        rc = pthread_join(threads[i], &status);
        if (rc != 0)
            error_and_die(EXIT_FAILURE, "Error on thread exit!\n");
    }

    close(sock);
    xfree(threads);
    xfree(events);

    printf("shut down!\n");

    return 0;
}

這是使用epoll處理這種情況的正確方法嗎? 函數_handle_request_應該盡可能快地返回,因為此時套接字的事件隊列被阻止了嗎?!

謝謝你的回復!

由於您只使用單個UDP套接字,因此使用epoll是沒有意義的 - 只需使用阻塞recvfrom即可。

現在,根據您需要處理的協議 - 如果您可以單獨處理每個UDP數據包 - 您實際上可以從多個線程(在線程池中)同時調用recvfrom。 操作系統將確保一個線程只接收UDP數據包。 然后,該線程可以執行handle_request中需要執行的任何操作。

但是,如果您需要按特定順序處理UDP數據包,那么您可能沒有那么多機會來對您的程序進行parallalise ...

不,這不會按你想要的方式工作。 要讓工作線程處理通過epoll接口到達的事件,您需要一個不同的架構。

示例設計(有幾種方法可以執行此操作) 用途:SysV / POSIX信號量。

  • 讓主線程產生n個子線程和信號量,然后阻止你的套接字(或其他)。

  • 讓每個子線程阻塞在信號量上。

  • 當主線程解除阻塞時,它將事件存儲在某個全局結構中,並在每個事件中將信號量提升一次。

  • 子線程解除阻塞,處理事件,當信號量返回0時再次阻塞。

您可以使用在所有線程之間共享的管道來實現與信號量非常相似的功能。 這將允許您阻止select()而不是信號量,您可以使用它來喚醒其他事件(超時,其他管道等)上的線程。

您還可以反轉此控件,並在其工作人員需要任務時喚醒主線程。 不過,我認為上述方法對你的情況更好。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM