dsign
dsign

Reputation: 12700

Correct order of socket calls for pre-fork server

What happens if I do (pseudo-code below):

s = socket
bind s 
fork .... 
   (... at child ...)
   listen s 
   conn = accept s

? Should I use instead:

s = socket 
bind s 
listen s 
fork .... 
   conn = accept s 

?

Which one is correct? Also, do I need to set any options on the socket for this particular scenario?

Upvotes: 2

Views: 469

Answers (2)

Jonas Schäfer
Jonas Schäfer

Reputation: 20738

On Linux (since 3.9) (and Mac OS and FreeBSD and possibly others), you also have the option of using SO_REUSEPORT.

// _DEFAULT_SOURCE for htobe16 for the port number, you may need _BSD_SOURCE instead
#define _DEFAULT_SOURCE
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <endian.h>

int main() {
    struct sockaddr_in6 sa;
    int v = 1;

    // prepare ipv6 address [::]:1345 
    memset(&sa, 0, sizeof(sa));
    sa.sin6_family = AF_INET6;
    sa.sin6_port = htobe16(1345);

    int s = socket(AF_INET6, SOCK_STREAM, 0);
    perror("socket");

    // the key point: enable SO_REUSEPORT
    setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &v, sizeof(v));
    perror("setsockopt");

    // from this point on just plain old socket use
    bind(s, (struct sockaddr*)&sa, sizeof(sa));
    perror("bind");
    listen(s, 0);
    perror("listen");

    while (1) {
        int conn = accept(s, NULL, NULL);
        perror("accept");
        close(conn);
        perror("close");
    }

    return 0;
}

The advantage of this approach is that no parent/child relationship is required between the processes. Also, the manpage (setsockopt(7)) suggests that this has performance improvements over traditional approaches.

In this scenario, you can fork before the call to socket. The only requirement is that all involved processes set SO_REUSEPORT on their socket and share the same effective UID.

Upvotes: 1

pilcrow
pilcrow

Reputation: 58681

Which one is correct [, calling listen() before or after fork()]?

Calling listen() before the fork() is correct. The effect of listen() is to mark the underlying socket as ready for connections with a connection backlog. It need only be called once.

I'd label the other approach "incorrect" from the perspective of code quality, in that it's redundant and confusing.

While it's not harmful to call listen() repeatedly, it's quite dubious. The specification does not say what happens when a subsequent call is made, merely that a connection-oriented socket shall "maintain a queue of outstanding connection indications", that is, a backlog of pending connections. Would a subsequent call be able to change the size of the backlog? Would you want it to? Indeed, not all operating systems even let you query the size of the backlog (FreeBSD has, e.g., SO_LISTENQLIMIT).

Upvotes: 2

Related Questions