views:

982

answers:

3

Hi there,

I have a problem in understanding what recv()/recvfrom() return from an non-blockig UDP socket.

A bit more specific and compared to TCP (please correct me if I'm wrong):

  • A blocking socket (either TCP or UDP) won't return from a recv() until there is some data in the buffer. This could be some number of bytes (TCP) or a complete datagram (UDP).

  • A non-blocking TCP socket either returns EWOULDBLOCK (linux) / WSAEWOULDBLOCK (windows) or the bytes that are currently in the buffer. As TCP data is a stream it doesn't matter how many bytes are returned.

Now the question:

  • A non-blocking UDP socket also returns WOULDBLOCK (linux) / WSAEWOULDBLOCK (windows) if there is no data available. But if there is data availabe, does a non-blocking UDP socket return just some bytes, which could mean you only get half of a datagram OR does an UDP socket always return complete datagrams??

Edit:

What I mean with "half of a datagram" is: what happens if I call recv() in just the moment when the socket is currently receiving a datagram. In that moment there are some bytes in the buffer but the datagram isn't complete yet.

Your explanations and comments are appreciated. Thanks!

A: 

I believe you get precisely one or zero datagrams. But I can't back this up at this moment. Perhaps someone else could provide a good reference?

Edit: I'm pretty sure you can't receive half a datagram. Either the datagram has arrived in the in buffer, or it hasn't.

Hans W
I believe that is correct. You can also get an error if it won't fit in the buffer you supplied, or if your mbufs aren't big enough (happens with large fragmented datagrams sometimes).
T.E.D.
mbufs are a BSD kernel data structure. They aren't exposed to userland.
Steve Madsen
A: 

Yes, UDP just returns what data got transmitted in that one datagram. UDP is not stream-oriented like TCP. Datagrams are discrete transmissions, and are not really tied to other datagrams in any way. That's the reason the socket option for TCP is SOCK_STREAM.

The bright side of this is that you can get a sense of separate transmissions, which isn't really easy to do with TCP.

T.E.D.
Thanks to both of you.I know the difference between stream oriented TCP (SOCK_STREAM) and packet oriented UDP (SOCK_DGRAM). I just wasn't sure whether a non-blocking UDP recv() would as well be packet oriented. To quote the recv() manpage:... If no messages are available at the socket, the receive calls wait for a message to arrive, unless the socket is non-blocking (see fcntl(2)), in which case the value -1 is returned and the external variable errno is set to EAGAIN or EWOULDBLOCK. **The receive calls normally return any data available,** ...
Uwe
+3  A: 

Finally, an excuse to dig out my Stevens books from my old office boxes.

Provided the buffer is large enough, the standard Berkeley sockets recv() and recvfrom() functions will never return a partial datagram. The datagram is not available to the application until the kernel has completely received and reassembled the datagram.

Interestingly, and this isn't much (any?) of an issue today, other network programming interfaces don't agree on the behavior when the provided buffer is too small:

The traditional Berkeley version of the sockets API truncates the datagram, discarding any excess data. Whether the application is notified depends on the version. (4.3BSD Reno and later can notify the application that the datagram was truncated.)

The sockets API under SVR4 (including Solaris 2.x) does not truncate the datagram. Any excess data is returned in subsequent reads. The application is not notified that multiple reads are being fulfilled from a single UDP datagram.

The TLI API does not discard the data. Instead a flag is returned indicating that more data is available, and subsequent reads by the application return the rest of the datagram.

(Stevens, TCP/IP Illustrated, Volume 1, p. 160)

Steve Madsen
It looks like it's possible to pass and receive a MSG_TRUNC flag to `recvmsg` in Linux. Documented in the manpage `recv(2)`. On another note, maybe I'm misreading, but I can only find the discarding behavior documented in the manpage for `socket(2)`, which only mentions it for `SOCK_SEQPACKET` sockets. I've never used those personally.
Shtééf
`MSG_TRUNC` as an argument to `recv(2)` isn't standard. It isn't available on either FreeBSD or Mac OS X (the systems I have access to at the moment; probably true for others). `MSG_TRUNC` is available on Linux, FreeBSD and Mac OS X in the `flags` member of the `struct msghdr` passed to `recvmsg(2)`. In any case, the datagram will be truncated if the passed buffer isn't big enough, even with `recv(2)` on Linux. The caller must check the return value and compare it to the buffer's size if `MSG_TRUNC` is used there. It'll know data is lost, but it's still lost.
Steve Madsen
Thanks! This means UDP is **really** packet oriented...
Uwe
Be careful with your terminology. UDP is datagram oriented. Packets are the "thing on the wire" and any time your UDP datagrams are bigger than the MTU, they will be fragmented at the IP layer, resulting in multiple IP packets. Reassembly happens at the receiver before anything is available to the application.
Steve Madsen
Yeah, you're right. To sum up:- If no data is available recv() returns EWOULDBLOCK.- Given a buffer too small for a full datagram, the datagram is truncated and everything not fitting into the buffer gets lost.- If the buffer is big enough recv() returns exactly one datagram.
Uwe