Part 6 · 1 chapters · ~8 min

Sockets

socket, bind, listen, accept, connect, read and write, the backlog, TCP as a byte stream and message framing, non-blocking sockets, poll, epoll and kqueue, a tiny event loop, SO_REUSEADDR and SO_REUSEPORT, and closing correctly (shutdown, TIME_WAIT).

7

From socket() to an event loop

code
int ls = socket(AF_INET, SOCK_STREAM, 0);
int one = 1; setsockopt(ls, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one);   // restart without "address in use"
struct sockaddr_in a = { .sin_family = AF_INET, .sin_port = htons(8080), .sin_addr.s_addr = htonl(INADDR_ANY) };
bind(ls, (struct sockaddr *)&a, sizeof a); listen(ls, 128);

// an event loop with poll: one thread, many connections
struct pollfd fds[1024] = { { .fd = ls, .events = POLLIN } }; int n = 1;
for (;;) {
    poll(fds, n, -1);
    if (fds[0].revents & POLLIN) { int c = accept(ls, NULL, NULL); fcntl(c, F_SETFL, O_NONBLOCK); fds[n++] = (struct pollfd){ c, POLLIN, 0 }; }
    for (int i = 1; i < n; i++) if (fds[i].revents & POLLIN) {
        char buf[4096]; ssize_t r = read(fds[i].fd, buf, sizeof buf);
        if (r <= 0) { close(fds[i].fd); fds[i--] = fds[--n]; continue; }
        handle(fds[i].fd, buf, r);
    }
}
// poll is O(n) per call; epoll (Linux) and kqueue (BSD, macOS) report only ready descriptors
A TCP SERVER, CALL BY CALL
the calls under every HTTP framework
serverkernelclientsocket(AF_INET, SOCK_STREAM)bind(:8080), listen(backlog 128)
swipe the figure sideways, or tap expand for full screen
1/4
listen
socket creates an endpoint, bind attaches an address, listen marks it passive with a backlog queue for completed handshakes.
socket, bind, listenthe backlog queues connections