2010-10-18 10 views
8

Ich möchte einen Multithread-UDP-Server in C/Linux entwickeln. Der Dienst läuft auf einem einzigen Port x, daher besteht nur die Möglichkeit, einen einzelnen UDP-Socket an ihn zu binden. Um unter hohen Lasten zu arbeiten, habe ich n Threads (statisch definiert), sagen wir 1 Thread pro CPU. Mit dem Befehl epoll_wait kann die Arbeit an den Thread übergeben werden, sodass die Threads bei Bedarf mit "EPOLLET |" geweckt werden EPOLLONESHOT '. Ich habe ein Codebeispiel angehängt:Multithreading-UDP-Server mit epoll?

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

Ist dies der richtige Umgang mit diesem Szenario mit Epoll? Soll die Funktion _handle_request_ so schnell wie möglich zurückkommen, weil für diese Zeit die Eventqueue für den Socket blockiert ist ?!

Dank für die Antworten!

Antwort

9

Wie Sie nur ein einziges UDP-Sockets verwenden, gibt es keinen Punkt epoll mit - nur anstelle eine Blockierung Recvfrom verwenden.

Nun, abhängig vom zu behandelnden Protokoll - wenn Sie jedes UDP-Paket einzeln verarbeiten können - können Sie recvfrom gleichzeitig von mehreren Threads aufrufen (in einem Thread-Pool). Das Betriebssystem wird dafür sorgen, dass genau ein Thread das UDP-Paket erhält. Dieser Thread kann dann alles tun, was in handle_request getan werden muss.

Wenn Sie jedoch die UDP-Pakete in einer bestimmten Reihenfolge verarbeiten müssen, werden Sie wahrscheinlich haben nicht so viele Möglichkeiten, Ihr Programm zu parallalise ...

+0

Genau das, was ich sagen wollte :) – MarkR

-1

Nein, das wird nicht funktionieren, wie Sie wollen. Damit Worker-Threads Ereignisse verarbeiten, die über eine Epoll-Schnittstelle eingehen, benötigen Sie eine andere Architektur.

Beispiel Design (es gibt mehrere Möglichkeiten, dies zu tun) Verwendung: SysV/POSIX Semaphore.

  • Haben Sie den Master-Thread laichen n subthreads und eine Semaphore, dann blockieren epolling Ihre Steckdosen (oder was auch immer).

  • Lassen Sie jeden Block auf subthread die Semaphore nach unten-ing.

  • Wenn der Master-Thread deblockiert, speichert er die Ereignisse in einigen globalen Struktur und ups die Semaphore einmal pro Ereignis.

  • Die subthreads entsperren, verarbeiten die Ereignisse, Block wieder, wenn die Semaphore wieder auf 0.

Sie ein Rohr unter allen Threads gemeinsam nutzen können sehr ähnliche Funktionalität wie die des Semaphore zu erreichen. Dies würde Sie auf select() statt der Semaphore blockieren, die Sie verwenden können, um die Threads auf einem anderen Ereignis (Timeouts, andere Rohre, etc.) aufwecken.

Sie können dieses Steuerelement auch umkehren, und haben den Master-Thread wache auf, wenn seine Arbeiter Aufgaben verlangen. Ich denke, der obige Ansatz ist jedoch besser für Ihren Fall.

+1

Ist das nicht ähnlich dem, was epoll intern tut? Es hat eine Art Ereigniswarteschlange und einen Ereignisverteiler. Threads werden bei Bedarf mit epoll_wait ?! Ich las diesen thead auf der LKML wo der PowerDNS Entwickler eine ähnliche Frage (http://www.gossamer-threads.com/lists/linux/kernel/1197050) hatte ... – Daniel

+0

Sie mich gemacht Zweifel gibt, ...Ich bin jedoch ziemlich sicher, dass mehrere Threads, die auf den gleichen Epoll-Deskriptor warten, das Thundering-Herd-Problem verursachen. Warte, ich bin mir jetzt nicht ganz sicher. Teufel noch mal. – slezica

+0

Sie erhalten nur dann eine donnernde Herde, wenn Sie nicht EPOLLET (flankengetriggert) verwenden. BTW, je nachdem, was Sie in handle_request tun, können Sie wahrscheinlich ohne EPOLLONESHOT wegkommen. Aber trotzdem macht epoll keinen Sinn, wenn Sie nur eine einzige Steckdose haben. – cmeerw

Verwandte Themen