Two API shapes for two transports
In the previous guide you built a TCP server and client through the socket API, walking the server through socket, bind, listen, accept while the client did socket then connect. That ceremony exists because TCP is connection-oriented: the two ends must agree on a connection before any data moves. A UDP socket is a different animal, and its API is shorter precisely because there is no connection to set up. You create a socket, you bind it to a port if you want to receive, and then you simply send and receive — no listen, no accept, no connect required.
The key API difference flows straight from UDP being message-oriented. A TCP socket uses send and recv, which work on a stream of bytes with no notion of where one chunk stops. A UDP socket instead uses sendto and recvfrom, and each call carries an address: sendto says "deliver this datagram to that host and port," and recvfrom hands you one datagram and also tells you who sent it. Because there is no connection, every UDP message must name its destination, the way you write a full address on every postcard rather than once at the start of a phone call.
Picture the whole UDP echo server and client side by side. The server does socket, then bind to a known port, then a loop that calls recvfrom to receive one datagram (learning the sender's address) and sendto to bounce a reply straight back to that address. The client does socket, sendto the server's IP and port, then recvfrom for the reply — no bind and no connect required. Compare this with the previous guide's TCP skeleton: there is no accept loop spawning a fresh socket per connection, because one UDP socket handles every client at once and the address simply comes back attached to each datagram.
What UDP guarantees: not much, and that's the point
UDP keeps exactly one promise the application layer cares about: message boundaries are preserved. If you sendto a 200-byte datagram, the other side's recvfrom returns those 200 bytes as one unit, never split into two reads and never merged with the next message. This is the opposite of TCP, and it is why people say UDP is datagram-oriented. One send becomes one receive — a tidy property you will sorely miss when you go back to TCP later in this guide.
Everything else, UDP refuses to promise. A datagram can be lost outright with no error reported. Datagrams can arrive out of order, because two postcards mailed in sequence may take different paths. A datagram can even be duplicated. And UDP gives no flow control or congestion control: if you blast datagrams faster than the path can carry them, the surplus simply vanishes in a queue somewhere. The only built-in safety net is an optional checksum that lets the receiver detect a corrupted datagram and drop it — detection, never repair.
TCP has no messages — only a hose of bytes
Now the idea that trips up almost every beginner. TCP is the byte stream abstraction: when you write into a TCP connection, you are pouring bytes into a hose, and the bytes come out the other end in order with nothing lost — but with no markers showing where one message ended. TCP is free to chop your writes into segments however it likes, and it may merge several of your writes into one segment or split one write across several. The receiver sees only a stream.
Here is the bite in concrete terms. Suppose your client sends two messages, "HELLO" then "WORLD", with two separate writes. You might expect two reads on the server to return "HELLO" and "WORLD". They will not, reliably. A single recv might return "HELLOWORLD" (the two writes coalesced), or "HEL" then "LOWORLD" (one write split), or any other carving. The bytes and their order are always correct; the boundaries are entirely your problem. Believing one recv equals one message is the single most common networking bug beginners write, and it hides in testing because on a fast local link the messages often happen to arrive whole.
Framing: putting the boundaries back
Message framing is the application's job of marking where each message ends so the receiver can split the stream back into messages. UDP did this for you for free; over TCP you must do it yourself, in the bytes you send, as part of your own little protocol. There are two classic ways, and almost every real protocol uses one of them.
The first is a delimiter: pick a byte that cannot appear inside a message and put it at the end of each one. Many text protocols use a newline, so the receiver reads bytes into a buffer and cuts off a message every time it spots a newline. The catch is that the delimiter must never occur in the data, or you must escape it; that is fine for line-based text but awkward for arbitrary binary. The second, and most common in binary protocols, is a length prefix: before each message, send a fixed-size header giving the message's length in bytes. The receiver reads that header first, learns it must collect exactly N more bytes, and reads until it has all N before treating the message as complete.
Length-prefixed framing over a TCP byte stream
-----------------------------------------------
On the wire: [len=5][H E L L O][len=5][W O R L D]
^^^^^ 4-byte length, then exactly that many bytes
Receiver loop:
read exactly 4 bytes -> n = 5
read exactly n (=5) bytes -> "HELLO" (one whole message)
repeat
'read exactly k' must loop, because one recv may return fewer than k bytes.Notice the comment in that sketch: "read exactly k" must be a loop, not a single recv. Because one recv can return fewer bytes than you asked for (a partial read), you must keep calling it until you have collected all k. The mirror hazard exists when sending: one send may accept fewer bytes than you handed it (a partial write), so robust senders loop too, advancing past the bytes already accepted. Forgetting these loops is the second-most-common socket bug, right after assuming messages arrive whole.
Agreeing on the bytes: byte order in the header
There is one more trap hiding inside that length prefix. A length like 5 fits in a single byte, but a real header might use a 4-byte integer to allow large messages, and different computers store a multi-byte integer in different orders in memory. Some machines put the most significant byte first (big-endian), others put the least significant byte first (little-endian). If the sender writes its 4-byte length one way and the receiver reads it the other way, the number 5 can be misread as roughly 84 million, and your receiver will sit forever waiting for bytes that never come.
The fix is a shared convention. Network protocols agree to send multi-byte numbers in big-endian order, so widely that big-endian is called network byte order. The socket libraries give you small helper functions to convert a number from your host's native order into network order before sending, and back again after receiving. The rule is simple and absolute: any multi-byte number you put on the wire — a length, a port, a sequence counter — gets converted to network byte order on the way out and converted back on the way in. Get this right once in your framing code and you never think about it again.
Byte order is only about the header you design, not about the message payload itself. If your payload is plain text or already-serialized data, it travels as a sequence of bytes and endianness does not enter into it. The danger is specifically the multi-byte integers in your own framing and protocol fields. This is also why the TCP socket address structures you filled in last guide used a host-to-network-short helper for the port number — the same idea, applied to the kernel's own header fields.
UDP's own framing decisions
Going back to UDP closes the loop on framing. Because UDP preserves message boundaries, you do not need a length prefix or delimiter to find where a message ends — recvfrom already gives you one whole datagram. But you do still own two decisions. First, you must size your receive buffer to hold the largest datagram you expect, because if a datagram is bigger than the buffer you hand recvfrom, the excess is silently truncated and lost. Second, if your application needs ordering or reliability across many datagrams, you build that yourself, often by putting your own sequence number in each datagram — your own miniature protocol on top of UDP.
There is also a practical size limit worth knowing. A single UDP datagram can in principle be large, but if it exceeds the path's maximum transmission unit it must be fragmented by IP into pieces, and if any one fragment is lost the whole datagram is discarded — fragmentation multiplies your loss risk. So real UDP protocols keep each datagram comfortably small, typically well under about 1500 bytes, to fit in one packet on a normal Ethernet path. This is one more way UDP hands the steering wheel to you: it will carry whatever you give it, but the consequences of bad choices are yours to bear.