Skip to main content

Documentation Index

Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt

Use this file to discover all available pages before exploring further.

Network Programming

The Berkeley sockets API is the foundation of network programming on Unix-like systems. Every web server, database client, chat application, and distributed system you have ever used communicates through this API (or a thin wrapper around it). It was designed in the early 1980s at UC Berkeley and has barely changed since — a testament to how well the abstraction was designed.

Socket Fundamentals

┌─────────────────────────────────────────────────────────────────────────────┐
│                           TCP Connection Flow                                │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│        SERVER                                    CLIENT                      │
│     ┌─────────┐                              ┌─────────┐                    │
│     │ socket()│                              │ socket()│                    │
│     └────┬────┘                              └────┬────┘                    │
│          │                                        │                          │
│     ┌────▼────┐                                   │                          │
│     │  bind() │                                   │                          │
│     └────┬────┘                                   │                          │
│          │                                        │                          │
│     ┌────▼────┐                                   │                          │
│     │ listen()│                                   │                          │
│     └────┬────┘                                   │                          │
│          │                                        │                          │
│     ┌────▼────┐            connect()         ┌────▼────┐                    │
│     │ accept()│◄─────────────────────────────│connect()│                    │
│     └────┬────┘                              └────┬────┘                    │
│          │                                        │                          │
│          │              read()/write()           │                          │
│          │◄──────────────────────────────────────▶                          │
│          │                                        │                          │
│     ┌────▼────┐                              ┌────▼────┐                    │
│     │ close() │                              │ close() │                    │
│     └─────────┘                              └─────────┘                    │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

TCP Server

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PORT 8080
#define BACKLOG 10
#define BUFFER_SIZE 1024

int main(void) {
    int server_fd, client_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_len = sizeof(client_addr);
    char buffer[BUFFER_SIZE];
    
    // Create socket
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd < 0) {
        perror("socket");
        exit(1);
    }
    
    // SO_REUSEADDR: Without this, restarting a server immediately after stopping
    // it will fail with "Address already in use" for up to 2 minutes (the TCP
    // TIME_WAIT state). This is the #1 frustration for developers writing servers.
    // SO_REUSEADDR tells the kernel: "bind to this port even if it is in TIME_WAIT."
    int opt = 1;
    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    
    // Bind to address
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;  // All interfaces
    server_addr.sin_port = htons(PORT);
    
    if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("bind");
        exit(1);
    }
    
    // Listen marks the socket as passive (accepting connections) and sets the
    // backlog queue size. BACKLOG is the maximum number of pending connections
    // the kernel will queue before rejecting new ones. If your accept() loop is
    // slow and clients connect faster than you can accept, they get ECONNREFUSED.
    if (listen(server_fd, BACKLOG) < 0) {
        perror("listen");
        exit(1);
    }
    
    printf("Server listening on port %d\n", PORT);
    
    while (1) {
        // Accept connection
        client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
        if (client_fd < 0) {
            perror("accept");
            continue;
        }
        
        printf("Client connected: %s:%d\n",
               inet_ntoa(client_addr.sin_addr),
               ntohs(client_addr.sin_port));
        
        // Handle client.
        // PITFALL: This server is single-threaded and handles one client at a
        // time. While we are in this read loop, no new connections are accepted.
        // A real server needs threads, fork(), or I/O multiplexing (select/epoll).
        ssize_t bytes;
        while ((bytes = read(client_fd, buffer, sizeof(buffer) - 1)) > 0) {
            buffer[bytes] = '\0';
            printf("Received: %s", buffer);
            
            // Echo back
            write(client_fd, buffer, bytes);
        }
        
        printf("Client disconnected\n");
        close(client_fd);
    }
    
    close(server_fd);
    return 0;
}

TCP Client

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#define BUFFER_SIZE 1024

int main(int argc, char *argv[]) {
    if (argc != 3) {
        fprintf(stderr, "Usage: %s <host> <port>\n", argv[0]);
        exit(1);
    }
    
    const char *host = argv[1];
    int port = atoi(argv[2]);
    
    int sock_fd;
    struct sockaddr_in server_addr;
    char buffer[BUFFER_SIZE];
    
    // Create socket
    sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (sock_fd < 0) {
        perror("socket");
        exit(1);
    }
    
    // Resolve hostname
    struct hostent *he = gethostbyname(host);
    if (!he) {
        fprintf(stderr, "Could not resolve %s\n", host);
        exit(1);
    }
    
    // Connect to server
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    memcpy(&server_addr.sin_addr, he->h_addr_list[0], he->h_length);
    server_addr.sin_port = htons(port);
    
    if (connect(sock_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("connect");
        exit(1);
    }
    
    printf("Connected to %s:%d\n", host, port);
    
    // Send and receive data
    while (fgets(buffer, sizeof(buffer), stdin)) {
        write(sock_fd, buffer, strlen(buffer));
        
        ssize_t bytes = read(sock_fd, buffer, sizeof(buffer) - 1);
        if (bytes > 0) {
            buffer[bytes] = '\0';
            printf("Server: %s", buffer);
        }
    }
    
    close(sock_fd);
    return 0;
}

Modern Address Resolution (getaddrinfo)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netdb.h>

int connect_to_server(const char *host, const char *port) {
    struct addrinfo hints, *result, *rp;
    int sock_fd;
    
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;      // IPv4 or IPv6
    hints.ai_socktype = SOCK_STREAM;  // TCP
    
    int ret = getaddrinfo(host, port, &hints, &result);
    if (ret != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(ret));
        return -1;
    }
    
    // Try each address until we connect
    for (rp = result; rp != NULL; rp = rp->ai_next) {
        sock_fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
        if (sock_fd < 0) continue;
        
        if (connect(sock_fd, rp->ai_addr, rp->ai_addrlen) == 0) {
            break;  // Success
        }
        
        close(sock_fd);
    }
    
    freeaddrinfo(result);
    
    if (rp == NULL) {
        fprintf(stderr, "Could not connect\n");
        return -1;
    }
    
    return sock_fd;
}

// IPv6-ready server
int create_server(const char *port) {
    struct addrinfo hints, *result;
    int server_fd;
    
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_INET6;       // IPv6 (also accepts IPv4)
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;      // For bind()
    
    getaddrinfo(NULL, port, &hints, &result);
    
    server_fd = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
    
    int opt = 0;
    setsockopt(server_fd, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));  // Accept IPv4 too
    
    opt = 1;
    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    
    bind(server_fd, result->ai_addr, result->ai_addrlen);
    listen(server_fd, 10);
    
    freeaddrinfo(result);
    return server_fd;
}

UDP Server and Client

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PORT 8080
#define BUFFER_SIZE 1024

// UDP Server
void udp_server(void) {
    int sock_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_len = sizeof(client_addr);
    char buffer[BUFFER_SIZE];
    
    sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
    
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(PORT);
    
    bind(sock_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
    
    printf("UDP server listening on port %d\n", PORT);
    
    while (1) {
        ssize_t bytes = recvfrom(sock_fd, buffer, sizeof(buffer) - 1, 0,
                                  (struct sockaddr*)&client_addr, &client_len);
        if (bytes < 0) continue;
        
        buffer[bytes] = '\0';
        printf("From %s:%d: %s",
               inet_ntoa(client_addr.sin_addr),
               ntohs(client_addr.sin_port),
               buffer);
        
        // Echo back
        sendto(sock_fd, buffer, bytes, 0,
               (struct sockaddr*)&client_addr, client_len);
    }
}

// UDP Client
void udp_client(const char *host, int port) {
    int sock_fd;
    struct sockaddr_in server_addr;
    char buffer[BUFFER_SIZE];
    
    sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
    
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = inet_addr(host);
    server_addr.sin_port = htons(port);
    
    // UDP is connectionless, but connect() sets default destination
    connect(sock_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
    
    while (fgets(buffer, sizeof(buffer), stdin)) {
        send(sock_fd, buffer, strlen(buffer), 0);
        
        ssize_t bytes = recv(sock_fd, buffer, sizeof(buffer) - 1, 0);
        if (bytes > 0) {
            buffer[bytes] = '\0';
            printf("Server: %s", buffer);
        }
    }
    
    close(sock_fd);
}

Non-Blocking I/O

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>

void set_nonblocking(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}

void nonblocking_server(void) {
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    set_nonblocking(server_fd);
    
    // ... bind and listen ...
    
    while (1) {
        int client_fd = accept(server_fd, NULL, NULL);
        if (client_fd < 0) {
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                // No pending connections
                usleep(1000);
                continue;
            }
            perror("accept");
            continue;
        }
        
        set_nonblocking(client_fd);
        // Handle client...
    }
}

I/O Multiplexing with select()

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define MAX_CLIENTS 100
#define BUFFER_SIZE 1024

void select_server(int port) {
    int server_fd;
    int client_fds[MAX_CLIENTS];
    int max_fd;
    fd_set read_fds;
    char buffer[BUFFER_SIZE];
    
    // Initialize client array
    for (int i = 0; i < MAX_CLIENTS; i++) {
        client_fds[i] = -1;
    }
    
    // Create and setup server socket
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    
    struct sockaddr_in addr = {
        .sin_family = AF_INET,
        .sin_addr.s_addr = INADDR_ANY,
        .sin_port = htons(port)
    };
    
    int opt = 1;
    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    bind(server_fd, (struct sockaddr*)&addr, sizeof(addr));
    listen(server_fd, 10);
    
    printf("Select server on port %d\n", port);
    
    while (1) {
        FD_ZERO(&read_fds);
        FD_SET(server_fd, &read_fds);
        max_fd = server_fd;
        
        // Add client sockets
        for (int i = 0; i < MAX_CLIENTS; i++) {
            if (client_fds[i] > 0) {
                FD_SET(client_fds[i], &read_fds);
                if (client_fds[i] > max_fd) {
                    max_fd = client_fds[i];
                }
            }
        }
        
        // Wait for activity
        int activity = select(max_fd + 1, &read_fds, NULL, NULL, NULL);
        if (activity < 0) {
            perror("select");
            continue;
        }
        
        // Check for new connections
        if (FD_ISSET(server_fd, &read_fds)) {
            int new_fd = accept(server_fd, NULL, NULL);
            if (new_fd >= 0) {
                printf("New connection: fd %d\n", new_fd);
                
                for (int i = 0; i < MAX_CLIENTS; i++) {
                    if (client_fds[i] < 0) {
                        client_fds[i] = new_fd;
                        break;
                    }
                }
            }
        }
        
        // Check client sockets
        for (int i = 0; i < MAX_CLIENTS; i++) {
            int fd = client_fds[i];
            if (fd > 0 && FD_ISSET(fd, &read_fds)) {
                ssize_t bytes = read(fd, buffer, sizeof(buffer) - 1);
                
                if (bytes <= 0) {
                    printf("Client disconnected: fd %d\n", fd);
                    close(fd);
                    client_fds[i] = -1;
                } else {
                    buffer[bytes] = '\0';
                    printf("From fd %d: %s", fd, buffer);
                    write(fd, buffer, bytes);  // Echo
                }
            }
        }
    }
}

I/O Multiplexing with epoll (Linux)

select() has a fatal flaw: it scans ALL file descriptors every time, making it O(n) per call. With 10,000 connections, most of which are idle, this wastes enormous CPU time. epoll solves this by only returning the file descriptors that actually have events — making it O(active_connections) instead of O(total_connections). This is why nginx and Redis use epoll and can handle hundreds of thousands of concurrent connections on a single thread.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define MAX_EVENTS 1024
#define BUFFER_SIZE 4096

void set_nonblocking(int fd) {
    fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
}

void epoll_server(int port) {
    int server_fd, epoll_fd;
    struct epoll_event ev, events[MAX_EVENTS];
    char buffer[BUFFER_SIZE];
    
    // Create server socket
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    set_nonblocking(server_fd);
    
    struct sockaddr_in addr = {
        .sin_family = AF_INET,
        .sin_addr.s_addr = INADDR_ANY,
        .sin_port = htons(port)
    };
    
    int opt = 1;
    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    bind(server_fd, (struct sockaddr*)&addr, sizeof(addr));
    listen(server_fd, SOMAXCONN);
    
    // Create epoll instance
    epoll_fd = epoll_create1(0);
    
    // Add server socket to epoll
    ev.events = EPOLLIN;
    ev.data.fd = server_fd;
    epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &ev);
    
    printf("Epoll server on port %d\n", port);
    
    while (1) {
        int n = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
        
        for (int i = 0; i < n; i++) {
            if (events[i].data.fd == server_fd) {
                // New connection
                while (1) {
                    int client_fd = accept(server_fd, NULL, NULL);
                    if (client_fd < 0) break;
                    
                    set_nonblocking(client_fd);
                    
                    // EPOLLET = Edge-Triggered mode: epoll notifies you only ONCE
                    // when new data arrives, not every time you call epoll_wait.
                    // You MUST read all available data in a loop until EAGAIN,
                    // or you will miss events. Level-triggered (default) is safer
                    // but edge-triggered gives better performance at scale.
                    ev.events = EPOLLIN | EPOLLET;  // Edge-triggered
                    ev.data.fd = client_fd;
                    epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &ev);
                    
                    printf("New connection: fd %d\n", client_fd);
                }
            } else {
                // Client data
                int fd = events[i].data.fd;
                
                while (1) {
                    ssize_t bytes = read(fd, buffer, sizeof(buffer));
                    
                    if (bytes < 0) {
                        if (errno != EAGAIN) {
                            printf("Read error on fd %d\n", fd);
                            close(fd);
                        }
                        break;
                    }
                    
                    if (bytes == 0) {
                        printf("Client disconnected: fd %d\n", fd);
                        close(fd);
                        break;
                    }
                    
                    write(fd, buffer, bytes);  // Echo
                }
            }
        }
    }
}

Socket Options

#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>

void configure_socket(int fd) {
    int opt;
    struct timeval tv;
    struct linger ling;
    
    // Reuse address (avoid "Address already in use")
    opt = 1;
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    
    // Reuse port (multiple processes on same port)
    opt = 1;
    setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
    
    // Keep-alive
    opt = 1;
    setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof(opt));
    
    // Send/receive buffer sizes
    opt = 256 * 1024;
    setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &opt, sizeof(opt));
    setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt));
    
    // Receive timeout
    tv.tv_sec = 30;
    tv.tv_usec = 0;
    setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
    
    // Linger on close
    ling.l_onoff = 1;
    ling.l_linger = 5;  // seconds
    setsockopt(fd, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling));
    
    // TCP-specific options
    
    // Disable Nagle's algorithm for low-latency applications (games, trading).
    // Nagle's algorithm batches small writes into larger TCP segments to improve
    // throughput, but adds up to 200ms of delay. For interactive protocols where
    // every message matters, this delay is unacceptable. For bulk transfers
    // (file copy, streaming), leave Nagle ON -- it significantly reduces overhead.
    opt = 1;
    setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt));
    
    // Quick ACK
    opt = 1;
    setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, &opt, sizeof(opt));
}

HTTP Server Example

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define PORT 8080
#define BUFFER_SIZE 4096

const char *response_template =
    "HTTP/1.1 200 OK\r\n"
    "Content-Type: text/html\r\n"
    "Content-Length: %zu\r\n"
    "Connection: close\r\n"
    "\r\n"
    "%s";

const char *html_template =
    "<!DOCTYPE html>\n"
    "<html><head><title>C HTTP Server</title></head>\n"
    "<body><h1>Hello from C!</h1><p>Path: %s</p></body></html>\n";

void handle_request(int client_fd) {
    char buffer[BUFFER_SIZE];
    char path[256] = "/";
    char html[1024];
    char response[BUFFER_SIZE];
    
    // Read request
    ssize_t bytes = read(client_fd, buffer, sizeof(buffer) - 1);
    if (bytes <= 0) return;
    buffer[bytes] = '\0';
    
    // Parse path (very simple)
    sscanf(buffer, "GET %255s", path);
    
    printf("Request for: %s\n", path);
    
    // Generate response
    snprintf(html, sizeof(html), html_template, path);
    snprintf(response, sizeof(response), response_template, strlen(html), html);
    
    // Send response
    write(client_fd, response, strlen(response));
}

int main(void) {
    int server_fd;
    struct sockaddr_in addr;
    
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    
    int opt = 1;
    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = htons(PORT);
    
    bind(server_fd, (struct sockaddr*)&addr, sizeof(addr));
    listen(server_fd, 10);
    
    printf("HTTP server on http://localhost:%d\n", PORT);
    
    while (1) {
        int client_fd = accept(server_fd, NULL, NULL);
        if (client_fd >= 0) {
            handle_request(client_fd);
            close(client_fd);
        }
    }
    
    return 0;
}

Exercises

1

Chat Server

Build a multi-client chat server where messages are broadcast to all connected clients.
2

File Transfer

Implement a simple file transfer protocol with upload/download commands.
3

HTTP Client

Build an HTTP client that can make GET requests and handle chunked transfer encoding.
4

High-Performance Server

Build a server using epoll that can handle 10,000+ concurrent connections.

Next Up

Build a Shell

Apply your knowledge by building a Unix shell