4.3.28 · D2Computer Networks

Visual walkthrough — Socket programming — TCP server - client, UDP server - client in Python

1,850 words8 min readBack to topic

We start with nothing but a row of numbers. Every symbol is earned before use.


Step 1 — What a "byte" is, and what "sending" means

WHAT. A byte is just one small whole number between and . That's it. The letter A is stored as the byte ; B is ; C is . When your program "sends text", it is really pushing a line of these little numbers out of a hole in your computer called a socket (the endpoint from the parent idea of IP + port).

WHY start here. The whole TCP-vs-UDP confusion comes from forgetting that the network moves numbers, not words. If we picture bytes as beads, the rest is obvious.

PICTURE. Look at the figure: three letters become three little red numbers, then a straight line of beads flowing rightward into the socket. There is no wall between the beads yet — remember that.

Figure — Socket programming — TCP server - client, UDP server - client in Python

Step 2 — The one difference that changes everything: boundary vs no boundary

WHAT. There are two ways the network can carry your beads:

  • Keep the beads in labelled bags — bag 1 = [65], bag 2 = [66], bag 3 = [67]. The receiver opens one bag at a time. This is a datagram — the unit of UDP.
  • Pour all beads onto one long conveyor belt[65, 66, 67, ...] with no dividers. The receiver scoops up "some beads" but has no idea where one send ended and the next began. This is a stream — the unit of TCP.

WHY this is THE picture. Almost every socket bug in the parent note ("one send ≠ one recv") is really "I thought I had bags but I had a belt." Once you can see the belt, the bug is obvious, not surprising.

PICTURE. Top row (red bags) = UDP: three sealed packets. Bottom row = TCP: one uninterrupted red belt of the same beads. Same data, different packaging.

Figure — Socket programming — TCP server - client, UDP server - client in Python

Step 3 — UDP up close: sendto seals a bag, recvfrom opens exactly one

WHAT. In UDP each sendto(data, addr) seals one bag and writes the destination on the outside. Each recvfrom(1024) opens exactly one bag and tells you the return address. Three sends → up to three receives, each a whole message.

WHY sendto/recvfrom and not send/recv? Because there is no set-up call (no handshake), the address must ride on every bag. So the call itself carries the address — that's literally why UDP uses these two names (from the parent's "Why no listen/accept" note).

PICTURE. Watch the three red bags leave sendto on the left and pop out one-per-recvfrom on the right. The green count "3 in → 3 out" only holds if nothing is lost — we handle loss in Step 6.

Figure — Socket programming — TCP server - client, UDP server - client in Python

Step 4 — TCP up close: send pours beads, recv scoops beads

WHAT. In TCP, sendall(b"A"), sendall(b"B"), sendall(b"C") pour beads onto the belt. On the other side recv(1024) says only: "give me up to 1024 beads that are ready right now." It might hand back b"ABC" (all three, coalesced) or b"A" then b"BC" (split). You do not control which.

WHY does this happen? The OS batches beads for efficiency, and the network can deliver them in chunks of any size. Nothing in TCP records "here a send ended". The belt has no walls (Step 1's warning pays off).

PICTURE. Three colored sends enter the belt; on the right a single recv scoops a red lump "ABC". The dashed lines showing where each send used to begin are grey and ghostly — the receiver can't see them.

Figure — Socket programming — TCP server - client, UDP server - client in Python

Step 5 — The fix: put your OWN walls back on the belt (framing)

WHAT. If TCP won't keep boundaries, we add them. The classic trick: before each message write its length as a fixed-size header, then the bytes. To read: first read the length , then keep scooping until you have exactly beads.

WHY a length prefix and not a delimiter like "\n"? A delimiter breaks if the message itself contains that character; a length number never appears "by accident" inside the payload because we read a fixed count first. This is the length-prefix protocol.

PICTURE. Same belt as Step 4, but now each message is preceded by a small red box holding its length (). The reader uses those boxes as saw-cuts to slice the belt back into A, B, C.

Figure — Socket programming — TCP server - client, UDP server - client in Python

Step 6 — Edge & degenerate cases (the ones exams love)

Every scenario the reader might hit, each with its own panel in the figure:

Case A — TCP recv returns b'' (empty). This is not "no data yet"; it is EOF: the peer called close. The belt has been switched off. Stop your loop.

Case B — UDP datagram is lost. A bag falls off the truck. UDP tells no one. "3 in" may become "2 out". If you need reliability you add sequence numbers/acks yourself (see UDP vs TCP tradeoffs).

Case C — UDP datagrams reorder. Bags A, B, C may arrive A, C, B. The network is free to reroute each bag independently. Only your own sequence numbers can restore order.

Case D — TCP send sends part of the data. send may push only some beads; sendall loops until every bead is on the belt. Use sendall unless you enjoy tracking counts.

Case E — zero-length UDP send. sendto(b"", addr) is a legal, empty but real datagram; recvfrom returns (b"", addr). Empty here means "an empty bag arrived", not EOF — the opposite of Case A. This asymmetry trips everyone.

Figure — Socket programming — TCP server - client, UDP server - client in Python

The one-picture summary

Everything above on one canvas: bytes become beads (Step 1); UDP bags them and keeps boundaries (Steps 3), TCP pours them on a wall-less belt so messages merge (Step 4); we saw the belt into frames with length headers (Step 5); and the edge cases — EOF, loss, reorder, empty datagram — hang off the sides (Step 6).

Figure — Socket programming — TCP server - client, UDP server - client in Python
Recall Feynman retelling — say it to a 12-year-old

Imagine sending letters. UDP puts each note in its own envelope with an address on it — the mail carrier hands your friend whole envelopes, one at a time, but a few might get lost or arrive out of order and nobody warns you. TCP is different: it's a moving belt that just pours your letters' ink onto one long strip with no gaps, so your friend sees one giant smear of words and can't tell where "hi" ended and "bye" began. To fix that, before each note you scribble "this next bit is 5 letters long"; your friend reads that number, counts off 5 letters, and knows exactly where the note ends. And two warnings: on TCP, when the belt suddenly stops giving anything (b""), it means your friend hung up — the call is over. On UDP, an empty envelope is still a real envelope, not a hang-up. Same beads, totally different packaging — that's the whole chapter.


Test yourself

Why can three TCP sends show up as one recv?
TCP is a byte stream with no message boundaries; the OS/network may coalesce the bytes into one chunk.
What does recv() returning b'' mean in TCP?
The peer closed the connection (EOF) — stop the loop.
Does UDP preserve message boundaries?
Yes — each recvfrom returns exactly one whole datagram.
Why prefix a length instead of using a delimiter?
A delimiter can appear inside the payload; a fixed-size length header is read first and never clashes with data.
What is an empty UDP datagram vs an empty TCP recv?
UDP (b"", addr) is a real empty message; TCP b"" is EOF (peer closed).