TCP gives you bytes, not messages

A TCP socket is a continuous byte stream, so two messages sent separately can arrive glued or split — prefix each message with its length so the receiver knows where it ends.

Previously

We said greet("Ada") travels as bytes over the wire — but the wire is a TCP stream that doesn't respect your message boundaries, so before anything else the receiver has to be told where one message stops.

Scene 02

TCP gives you bytes, not messages

  1. Watch
  2. Try it
  3. Predict
  4. Capture
CLIENTAdagreet("Ada")Bobgreet("Bob")fired back-to-backTCP BYTE STREAMno boundariesAdaBobSERVER RECV()recv() #1 → 3BAdarecv() #2 → 3BBobWHAT THE RECEIVER PARSESlength-prefix: OFFAdarecv() #1Bobrecv() #2GARBLED — parse changes with every chunking (2 reads)gRPC's real frame uses a 5-byte prefix: 1 compression flag + 4-byte big-endian length, then the payload.
What to watch for

Here's the bug every first network program hits. A TCP socket (the connection your two machines talk over) is not a stream of messages — it's a byte stream: a single, continuous run of bytes with no markers between one message and the next. The client fires two calls back-to-back, greet("Ada") then greet("Bob"). Watch the server's recv() (the call that reads bytes off the socket): one read can return "Ada" plus half of "Bob" — the boundary you sent is simply gone. The receiver needs a way to recover where each message ends; the scheme that does that is called framing — any rule that marks message boundaries on the wire. The simplest framing is length-prefix: write each message's length first, then its bytes, so the reader can read the length and then read exactly that many bytes.

Continue unlocks when the animation finishes.
Implementation

Highlighted lines are the ones running in the diagram right now.

Sender.frame
write the length ahead of each payload, then the bytes
def send(sock, payload):
# 4-byte big-endian length, then the bytes
header = len(payload).to_bytes(4, 'big')
sock.sendall(header + payload)
send(sock, b'Ada') # -> [00 00 00 03] Ada
send(sock, b'Bob') # -> [00 00 00 03] Bob
Receiver.recvOnce
the bug: treat one recv() as one message
def handle(sock):
# one read, however TCP chopped the stream
data = sock.recv(1024)
return parse(data) # whatever bytes landed
Receiver.readFrame
read the length, then read exactly that many bytes
def readN(sock, n):
buf = b''
while len(buf) < n: # loop across recv() calls
buf += sock.recv(n - len(buf))
return buf
def readFrame(sock):
header = readN(sock, 4) # the length prefix
length = int.from_bytes(header, 'big')
return readN(sock, length) # exactly one message

Where this sits in Build a gRPC-style RPC framework

Scene 02 of 14, in the The wire act — Bytes, frames, and the schema that gives them meaning.. TCP is a byte stream, not a message stream: two messages can arrive glued or split. A length prefix is what lets the receiver read exactly one message back.

Up next. Now we can recover one whole message — but it's still an opaque blob; the next job is agreeing on what the bytes inside the frame actually MEAN to both sides, in every language.

All 14 scenes in Build a gRPC-style RPC framework · Every curriculum

Built with Arqly
Every scene in Build a gRPC-style RPC framework builds on the one before it.All 14 Build a gRPC-style RPC framework scenes