Networking in C · intermediate · ~20 min
Read and write without blocking, and survive the EAGAIN cycle.
Setting O_NONBLOCK on a socket makes read and write return immediately with EAGAIN/EWOULDBLOCK when they'd otherwise block. You combine this with epoll edge-trigger to drain available data, then return to the loop until the kernel signals more.
A single thread can serve thousands of slow clients only if no one operation blocks. Non-blocking sockets are the precondition; epoll is the dispatcher.
Setting non-blocking. fcntl(fd, F_SETFL, O_NONBLOCK) or pass SOCK_NONBLOCK to socket().
EAGAIN/EWOULDBLOCK. POSIX says they may be different values — always check both. On Linux they're the same.
Partial writes. A non-blocking write may write fewer bytes than requested. Loop. If it returns -1 with EAGAIN, queue the remainder and ask epoll for EPOLLOUT.
Pentester mindset. Slowloris-style attacks send one byte every 30s. A blocking server stalls a worker per attacker; non-blocking + read-timeouts shrugs it off.
Defensive coding habit. ALWAYS handle EAGAIN before treating a -1 as a real error. ALWAYS drain to EAGAIN in edge-triggered mode.
int flags = fcntl(sock, F_GETFL);
fcntl(sock, F_SETFL, flags | O_NONBLOCK);
A non-blocking socket returns from read/write immediately — with
data, or with EAGAIN/EWOULDBLOCK meaning 'try again later'. Combined with
epoll, this is how you handle thousands of slow clients on one thread.
fcntl(sock, F_SETFL, O_NONBLOCK);
ssize_t n = read(sock, buf, sizeof buf);
if (n < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
/* No data; come back when epoll says so. */
}
}
for (;;) {
ssize_t n = read(fd, buf, sizeof buf);
if (n > 0) { handle(buf, n); continue; }
if (n == 0) { close(fd); break; } /* EOF */
if (errno == EINTR) continue; /* signal — retry */
if (errno == EAGAIN || errno == EWOULDBLOCK) /* drained — wait for epoll */
break;
perror("read"); close(fd); break; /* real error */
}
strace -e read,write ./prog shows every short read and EAGAIN. If your server hangs, look for a blocking call you forgot to mark.
Buffer the unsent tail of partial writes. A common bug is to write 100 bytes, get a return of 70, and forget the other 30.
Every high-perf server. The Linux kernel's networking stack expects this pattern from user-space.
O_NONBLOCK = no blocking. Drain to EAGAIN. Treat EAGAIN as 'try later', not an error.