
TCP is a byte stream
TCP gives an application an ordered stream of bytes. The segments it cuts that stream into are its own business, and they are not the application's messages.
An application writes to TCP in calls: forty bytes now, two hundred a moment later. TCP does not keep those calls. It appends the bytes to one stream, numbers every byte, and decides on its own how to cut the stream into segments. RFC 9293 calls the service a reliable, in-order byte stream.
Three sets of boundaries
There are three, and they do not have to agree. The application's writes are where each call ended. TCP's segments are where it cut the stream, mostly by size: on an Ethernet link, with IPv4 and no options, a segment carries up to 1460 bytes of data. On an ESP32 the limit is a little lower, about 1440, and it is set when the firmware is built. The application's reads are however much each call asked for and found waiting.
RFC 9293 says it plainly: TCP guarantees no correlation between the boundaries of the segments sent and received, and the boundaries of the read and write buffers of the application.
What comes out
Two writes can come out as one read, because both had arrived before the program asked. One write can come out as two reads, because it was cut in two, or because the program only asked for part of it. A message that fitted in one segment today can arrive in two tomorrow, when the path is different.
Nothing is lost in all this. The bytes are all there, in the order they were written, and TCP holds back any that arrive too early so that the program never sees them out of order. What is gone is only the marks between writes.
Marking the ends yourself
If the data is made of messages, the messages carry their own ends. Two ways are common:
- A length first. Send a number that says how many bytes follow, then read exactly that many, however many reads it takes.
- A terminator. End each message with a newline, and read until one turns up. This works only if the newline cannot appear inside a message.
HTTP uses both ideas: a header ends with a blank line, and the body is as long as Content-Length says (or is sent in marked chunks).
Common mistakes
- Reading once and treating the result as one message. It may be half of one, or one and a half.
- Assuming a short write is a short segment. TCP may hold small writes back and send them together, or send them at once.
- Parsing before the newline has arrived. Keep what you have read, and wait for the rest.
- Sizing a buffer to the write. Size it to the largest message the protocol allows.
Edit this page — content/fundamentals/tcp-udp/tcp-byte-stream.mdx
Discuss this article
Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.