Underneath, everything is packets. Whether the application sees those packet boundaries is a genuine design choice, and it hinges on one cost:

Preserving message boundaries forfeits the freedom to merge small writes and split large ones.

A protocol that must preserve boundaries can’t batch two small writes into one segment (that would fuse two messages) or split a large write without extra machinery. Efficiency comes precisely from that freedom — 1 byte of payload with 40 bytes of header is 2% efficient — so boundaries cost throughput.

TCP and UDP answer the same question oppositely:

  • TCP discards boundaries and buys the freedom. It presents a byte stream: two 100-byte writes may arrive as one read of 200, or 3 then 197, or any split. Its sequence numbers count bytes, not messages — the decision is visible in the header. TCP is not losing information it promised to keep.
  • UDP preserves boundaries by doing nothing. One sendto() = one datagram = one recvfrom(). Boundaries survive through the absence of interference.

So UDP is not “TCP minus reliability” — it’s “IP plus ports” (RFC 768: four fields — source port, destination port, length, checksum). That makes it the natural substrate for a custom transport: no structure to fight, and unlike raw IP it traverses NATs. (Fragmentation is not an exception: if a datagram exceeds the path MTU, IP fragments it but reassembles at the final destination before handing up one whole datagram — never a piece.)

See also

References

Questions

flashcards/software-engineering/networking

What is the cost of preserving message boundaries?::You forfeit the freedom to merge small writes and split large ones — the packaging freedom that gives throughput

How do TCP and UDP answer the “expose packet boundaries?” question?::Oppositely — TCP discards boundaries (a byte stream, counting bytes) to buy packaging freedom; UDP preserves them (one sendto = one recvfrom) by doing nothing

UDP is best described not as “TCP minus reliability” but as IP plus ports.

If a UDP datagram exceeds the path MTU, IP fragments it but reassembles at the final destination, so the app gets one whole datagram or nothing — never a piece.