Why a TCP read must loop and a UDP read must not
◈ 10 cardsChoose SOCK_STREAM or SOCK_DGRAM for a task and write the correct read pattern for each; a stream has no message boundaries, so one write is not one read.
Two service types, two contracts
socket()'s second argument picks the contract you are working under, and the two contracts differ in more than reliability.
SOCK_STREAM gives you TCP: connection-oriented, reliable, in-order, error-checked — and a byte stream. A stream has no structure. The kernel guarantees that every byte you wrote arrives, exactly once, in the order you wrote it. It guarantees nothing whatsoever about how those bytes are grouped when the receiver reads them.
SOCK_DGRAM gives you UDP: connectionless and best-effort — a datagram may be lost, duplicated, or delivered out of order — but each datagram is a message with boundaries. If one arrives, it arrives whole.
The book draws a neat correlation worth carrying: connection-oriented traffic is usually boundary-less, and connectionless traffic usually has boundaries. That is not a coincidence. A connection is a long-lived pipe with no natural framing; a datagram is a unit of delivery, so its edges are the framing.
Worked example — four writes, one read
A client sends a 128-byte request as four separate 32-byte write() calls on a connected SOCK_STREAM socket:
write(s, msg1, 32); /* 32 A's */
write(s, msg2, 32); /* 32 B's */
write(s, msg3, 32); /* 32 C's */
write(s, msg4, 32); /* 32 D's */
What does the server's first read(c, buf, 128) return? The honest answer is: anything from 1 to 128. Some plausible outcomes, all legal:
- one read of 128 — the four writes were coalesced in the send buffer and travelled in one segment;
- four reads of 32 — the pleasant case that makes people think boundaries exist;
- five reads of 4, 15, 78, 10 and 21 — path MTU, socket buffer space, and the timing of the reader all conspired.
Which one you get depends on network delay, the datagram size the intranet uses, and how much socket buffer space is free at the moment. None of it is under your control, and none of it is stable enough to test against — a program that assumes 32-byte reads will pass on a loopback interface and fail across a network.
So the rule is unconditional: read a stream in a loop until you have the number of bytes you expect. You must know the expected count from somewhere — a fixed-size header, a length prefix, or a terminator such as a newline you scan for. "Read until it looks complete" is not a protocol.
This is the same short-read discipline as reading a plain file with read(), and the reason is now stronger rather than weaker. On a file a short read is unusual. On a socket it is the normal case.
The datagram side — and the loop you must not write
Send the same four messages over UDP with four sendto() calls and the receiver's picture is entirely different. Each recvfrom() returns exactly one whole datagram, or nothing. Never half of one; never two glued together. If the buffer you supplied is smaller than the datagram, the excess is discarded — not held back for the next call, which is a trap of its own.
So the loop is wrong here. recvfrom() in a while-loop accumulating into one buffer would concatenate distinct messages and destroy exactly the framing UDP gave you for free. One recvfrom(), one message, done.
Half-closing: telling the peer you are finished
close() tears the socket down in both directions and releases the descriptor. Sometimes that is too blunt. A client that has sent its last request wants to say no more requests are coming while still being able to read the final response.
That is shutdown(s, SHUT_WR). It closes one direction. The peer's next read() returns 0 — end of file — so a server looping on read() learns the request stream is over and can send its answer and close. SHUT_RD closes the read direction, and SHUT_RDWR closes both without releasing the descriptor.
The pattern is worth recognising: a request stream terminated by EOF is how a great many simple protocols avoid needing a length prefix at all.