Socket Programming

Sockets are the API for network communication. A socket is a file descriptor that represents one endpoint of a network connection. The same read/write syscalls that work on files work on sockets.

Why It Matters

Every networked application — web servers, databases, chat apps, game servers — uses sockets underneath. HTTP libraries, gRPC, Redis clients — they all wrap socket calls. Understanding the socket lifecycle and IO multiplexing is essential for writing high-performance network code.

TCP Socket Lifecycle

Server:                             Client:
socket()                            socket()
  ↓                                   ↓
bind(addr, port)                    connect(server_addr)
  ↓                                   ↓
listen(backlog)                     read/write
  ↓
accept() → new fd
  ↓
read/write
  ↓
close()                             close()

TCP Echo Server (Complete)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
 
int main(void) {
    int srv = socket(AF_INET, SOCK_STREAM, 0);
    if (srv < 0) { perror("socket"); exit(1); }
 
    // Allow port reuse (avoid "Address already in use" after restart)
    int opt = 1;
    setsockopt(srv, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
 
    struct sockaddr_in addr = {
        .sin_family = AF_INET,
        .sin_port = htons(8080),
        .sin_addr.s_addr = INADDR_ANY,  // bind to all interfaces
    };
    bind(srv, (struct sockaddr *)&addr, sizeof(addr));
    listen(srv, 128);  // backlog: max pending connections
    printf("Listening on :8080\n");
 
    while (1) {
        struct sockaddr_in client_addr;
        socklen_t len = sizeof(client_addr);
        int cli = accept(srv, (struct sockaddr *)&client_addr, &len);
        printf("Connection from %s:%d\n",
               inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
 
        char buf[4096];
        ssize_t n;
        while ((n = read(cli, buf, sizeof(buf))) > 0)
            write(cli, buf, n);  // echo back
        close(cli);
    }
}

TCP Client

int sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr = {
    .sin_family = AF_INET,
    .sin_port = htons(8080),
};
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
 
connect(sock, (struct sockaddr *)&addr, sizeof(addr));
write(sock, "hello", 5);
char buf[1024];
ssize_t n = read(sock, buf, sizeof(buf));
write(STDOUT_FILENO, buf, n);
close(sock);

IO Multiplexing

The single-threaded server above handles one client at a time. To handle many clients concurrently without threads, use IO multiplexing:

epoll (Linux, the standard for high-performance servers)

#include <sys/epoll.h>
 
int epfd = epoll_create1(0);
 
// Watch the server socket for new connections
struct epoll_event ev = {.events = EPOLLIN, .data.fd = srv};
epoll_ctl(epfd, EPOLL_CTL_ADD, srv, &ev);
 
struct epoll_event events[1024];
while (1) {
    int n = epoll_wait(epfd, events, 1024, -1);  // block until events
    for (int i = 0; i < n; i++) {
        int fd = events[i].data.fd;
        if (fd == srv) {
            // New connection
            int cli = accept(srv, NULL, NULL);
            ev.events = EPOLLIN;
            ev.data.fd = cli;
            epoll_ctl(epfd, EPOLL_CTL_ADD, cli, &ev);
        } else {
            // Data from existing client
            char buf[4096];
            ssize_t bytes = read(fd, buf, sizeof(buf));
            if (bytes <= 0) {
                epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
                close(fd);
            } else {
                write(fd, buf, bytes);
            }
        }
    }
}

Multiplexing Comparison

MethodMax fdsPer-call CostPlatformNotes
select()~1024 (FD_SETSIZE)O(n) scan all fdsEverywhereAncient, portable
poll()No fd limitO(n) scan all fdsPOSIXBetter than select, still linear
epoll()100k+O(ready) events onlyLinuxEdge/level triggered, the standard
kqueue()100k+O(ready) events onlyBSD/macOSSimilar to epoll

Rule of thumb: epoll on Linux, kqueue on macOS/BSD. Libraries like libevent/libev abstract the difference.

Important Socket Options

// Reuse address (avoid EADDRINUSE on restart)
setsockopt(srv, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int));
 
// Disable Nagle (for latency-sensitive protocols)
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &(int){1}, sizeof(int));
 
// Send/receive timeout
struct timeval tv = {.tv_sec = 5};
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
 
// Keep-alive (detect dead connections)
setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &(int){1}, sizeof(int));

Non-blocking Sockets

#include <fcntl.h>
int flags = fcntl(sock, F_GETFL, 0);
fcntl(sock, F_SETFL, flags | O_NONBLOCK);
// read() now returns -1 with errno=EAGAIN instead of blocking

Essential for event-loop servers (epoll + non-blocking). Also needed for connect() — non-blocking connect returns immediately, use epoll to wait for completion.