Frontend Labs
⋯

21. Sockets: a TCP echo server, then a tiny HTTP server

21.1 A TCP echo server (POSIX sockets, the API every language's networking library wraps)

// echo_server.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

int main(void) {
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    int opt = 1;
    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    struct sockaddr_in addr = {0};
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = htons(9999);          // host-to-network byte order — always for wire formats

    bind(server_fd, (struct sockaddr *)&addr, sizeof(addr));
    listen(server_fd, 10);
    printf("listening on :9999\n");

    while (1) {
        struct sockaddr_in client_addr;
        socklen_t len = sizeof(client_addr);
        int client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &len);
        if (client_fd < 0) continue;

        char buf[1024];
        ssize_t n;
        while ((n = read(client_fd, buf, sizeof buf)) > 0) {
            write(client_fd, buf, (size_t)n);   // echo it straight back
        }
        close(client_fd);
    }
    return 0;
}
clang -std=gnu17 -Wall -Wextra -o echo_server echo_server.c   # gnu17: BSD sockets are a POSIX
                                                                 # API, hidden under strict c17
./echo_server &
printf "hello\n" | nc localhost 9999

This one function call, accept(), is the reason the server above can only handle one client at a time — while it's blocked reading from client_fd, no other client can connect. Section 23's mini-project fixes exactly this with select(), and Section 34's Redis walkthrough shows the production answer (a single-threaded event loop).

21.2 A minimal HTTP/1.0 server on the same socket API

// http_server.c — serves one static string to every request
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

int main(void) {
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    int opt = 1;
    setsockopt(fd, 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(fd, (struct sockaddr *)&addr, sizeof addr);
    listen(fd, 16);
    printf("http://localhost:8080\n");

    while (1) {
        int client = accept(fd, NULL, NULL);
        char req[2048] = {0};
        read(client, req, sizeof req - 1);   // real servers must parse Content-Length, not assume one read is the whole request

        const char *body = "hello from a C http server\n";
        char response[256];
        int len = snprintf(response, sizeof response,
            "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\nContent-Length: %zu\r\n\r\n%s",
            strlen(body), body);
        write(client, response, (size_t)len);
        close(client);
    }
}
clang -std=gnu17 -Wall -Wextra -o http_server http_server.c
./http_server & curl -s http://localhost:8080

This is deliberately the simplest possible HTTP server to show the socket API mapping directly onto the protocol you already know from a browser — it's also deliberately not production-ready (single request at a time, no request-size limits, no timeout handling); Section 34's Redis walkthrough and Section 43's capstone show what closes those gaps.


Generated from real-life-c-guide.md · 8 Oct 2026 16:36