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
Next Up
Build a Shell
Apply your knowledge by building a Unix shell