TCP foundations
Treat TCP as a byte stream
Stop assuming one write becomes one data event and inspect how application bytes can be split or combined.
10 minute lesson
TCP looks like it sends messages. It does not. TCP is a byte stream: it preserves byte order, not message boundaries.
One socket.write() may arrive through several data events, and several writes may arrive together in one. The kernel and the network decide how bytes get grouped, based on timing, buffer sizes, and packet boundaries you don’t control.
Make the server send three pieces:
socket.write('one')
socket.write('two')
socket.end('three')
On the client, log each chunk in hexadecimal and as text:
socket.on('data', chunk => {
console.log(chunk.length, chunk.toString('hex'), JSON.stringify(chunk.toString()))
})
On loopback you’ll often see all three writes arrive as a single chunk:
11 6f6e6574776f7468726565 "onetwothree"
Repeat the run. Add a small setTimeout between the writes, or run the pair over a real network, and the grouping changes: sometimes "one" then "twothree", sometimes three separate events. Chunk boundaries are an implementation detail, not your protocol.
Why this bites people
The classic mistake is code that works on a laptop:
socket.on('data', chunk => {
const command = chunk.toString() // assumes one event = one command
handle(command)
})
This passes every local test, because small writes on loopback usually arrive whole. Then in production a command arrives split across two events, JSON.parse() throws on half a document, and you’re debugging a “random” crash that only shows up under load or on slow links.
The fix is framing: your protocol must define where one message ends and the next begins. The two common tools are a delimiter like \n and an explicit length prefix. That’s what the next module builds.
Never parse one data event as one complete command unless the protocol itself guarantees a fixed length you enforce. If you take one thing from this course, take this lesson.
Lesson completed