blocking versus non-blocking sockets and timeouts
Imagine waiting at a counter where you are told 'help yourself only when food is ready'. A blocking approach means you stand frozen at the counter until food appears, doing nothing else meanwhile. A non-blocking approach means you peek, and if food is not ready yet you walk away and do other things, checking back later. Sockets work the same way: by default a socket blocks - a call like recv() puts your program to sleep until data arrives - but you can switch it to non-blocking, where the call returns immediately even if nothing is ready.
By default sockets are blocking: recv() on a quiet connection simply waits, accept() waits for a client, connect() waits for the handshake, and send() waits if the outgoing buffer is full. That is simple and fine for a program serving one thing at a time. A non-blocking socket (set with fcntl() and the O_NONBLOCK flag) instead returns right away: if there is nothing to do, recv() returns -1 with errno set to EAGAIN or EWOULDBLOCK, meaning 'try again later, I had nothing for you'. This lets one thread juggle many sockets without freezing on any one. A middle ground is a timeout: you can ask a blocking call to wait but give up after, say, 5 seconds (via setsockopt() with SO_RCVTIMEO, or by using select()/poll()), so a dead peer cannot hang you forever.
Why it matters: blocking is easy to reason about but a single slow or silent peer can freeze your whole program. Non-blocking and timeouts are how real servers stay responsive and how they avoid hanging forever on a connection that went away. The honest subtlety: with non-blocking sockets, EAGAIN is not an error to log and abort on - it is the normal 'nothing yet' answer, and you must loop or wait for readiness and retry. Deep event-loop machinery (epoll and friends) builds on exactly this idea but is covered later.
On a non-blocking socket: ssize_t n = recv(fd, buf, len, 0); if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) { /* nothing ready yet - not an error, try later */ } To add a 5-second receive timeout to a blocking socket instead: setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv);
EAGAIN/EWOULDBLOCK on a non-blocking socket means 'nothing yet', not failure - retry, do not abort.
Without a timeout, a blocking recv() on a peer that vanished silently can wait essentially forever - the kernel does not always notice the other end is gone. Always bound your waits when a hung peer would be a problem.