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
| Method | Max fds | Per-call Cost | Platform | Notes |
|---|---|---|---|---|
select() | ~1024 (FD_SETSIZE) | O(n) scan all fds | Everywhere | Ancient, portable |
poll() | No fd limit | O(n) scan all fds | POSIX | Better than select, still linear |
epoll() | 100k+ | O(ready) events only | Linux | Edge/level triggered, the standard |
kqueue() | 100k+ | O(ready) events only | BSD/macOS | Similar 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 blockingEssential for event-loop servers (epoll + non-blocking). Also needed for connect() — non-blocking connect returns immediately, use epoll to wait for completion.
Related
- TCP Protocol — connection lifecycle and flow control
- UDP Protocol —
SOCK_DGRAMfor UDP sockets - File IO in C — sockets are file descriptors
- TLS and Encryption — TLS wraps sockets for encrypted communication
- Processes and Threads — alternative to multiplexing: thread-per-connection