Datagram Sockets

The operation of datagram sockets can be explained by analogy with the postal system:

  1. The socket() system call is the equivalent of setting up a mailbox. (Here, we assume a system like the rural postal service in some countries, which both picks up letters from and delivers letters to the mailbox.) Each application that wants to send or receive datagrams creates a datagram socket using socket().

  2. In order to allow another application to send it datagrams (letters), an application uses bind() to bind its socket to a well-known address. Typically, a server binds its socket to a well-known address, and a client initiates communication by sending a datagram to that address. (In some domains--notably the UNIX domain--the client may also need to use bind() to assign an address to its socket if it wants to receive datagrams sent by the server.)

  3. To send a datagram, an application calls sendto(), which takes as one of its arguments the address of the socket to which the datagram is to be sent. This is analogous to putting the recipient’s address on a letter and posting it.

  4. In order to receive a datagram, an application calls recvfrom(), which may block if no datagram has yet arrived. Because recvfrom() allows us to obtain the address of the sender, we can send a reply if desired. (This is useful if the sender’s socket is bound to an address that is not well known, which is typical of a client.) Here, we stretch the analogy a little, since there is no requirement that a posted letter is marked with the sender’s address.

  5. When the socket is no longer needed, the application closes it using close().

Just as with the postal system, when multiple datagrams (letters) are sent from one address to another, there is no guarantee that they will arrive in the order they were sent, or even arrive at all. Datagrams add one further possibility not present in the postal system: since the underlying networking protocols may sometimes retransmit a data packet, the same datagram could arrive more than once.

Figure 56-4 illustrates the use of the system calls employed with datagram sockets.

Overview of system calls used with datagram sockets

Figure 56-4. Overview of system calls used with datagram sockets

The recvfrom() and sendto() system calls receive and send datagrams on a datagram socket.

#include <sys/socket.h>

ssize_t recvfrom(int sockfd, void *buffer, size_t length, int flags,
                 struct sockaddr *src_addr, socklen_t *addrlen);

Note

Returns number of bytes received, 0 on EOF, or -1 on error

ssize_t sendto(int sockfd, const void *buffer, size_t length, int flags,
               const struct sockaddr *dest_addr, socklen_t addrlen);

Note

Returns number of bytes sent, or -1 on error

The return value and the first three arguments to these system calls are the same as for read() and write().

The fourth argument, flags, is a bit mask controlling socket-specific I/O features. We cover these features when we describe the recv() and send() system calls in Section 61.3. If we don’t require any of these features, we can specify flags as 0.

The src_addr and addrlen arguments are used to obtain or specify the address of the peer socket with which we are communicating.

For recvfrom(), the src_addr and addrlen arguments return the address of the remote socket used to send the datagram. (These arguments are analogous to the addr and addrlen arguments of accept(), which return the address of a connecting peer socket.) The src_addr argument is a pointer to an address structure appropriate to the communication domain. As with accept(), addrlen is a value-result argument. Prior to the call, addrlen should be initialized to the size of the structure pointed to by src_addr; upon return, it contains the number of bytes actually written to this structure.

If we are not interested in the address of the sender, then we specify both src_addr and addrlen as NULL. In this case, recvfrom() is equivalent to using recv() to receive a datagram. We can also use read() to read a datagram, which is equivalent to using recv() with a flags argument of 0.

Regardless of the value specified for length, recvfrom() retrieves exactly one message from a datagram socket. If the size of that message exceeds length bytes, the message is silently truncated to length bytes.

Note

If we employ the recvmsg() system call (The sendmsg() and recvmsg() System Calls), then it is possible to find out about a truncated datagram via the MSG_TRUNC flag returned in the msg_flags field of the returned msghdr structure. See the recvmsg(2) manual page for details.

For sendto(), the dest_addr and addrlen arguments specify the socket to which the datagram is to be sent. These arguments are employed in the same manner as the corresponding arguments to connect(). The dest_addr argument is an address structure suitable for this communication domain. It is initialized with the address of the destination socket. The addrlen argument specifies the size of addr.

Note

On Linux, it is possible to use sendto() to send datagrams of length 0. However, not all UNIX implementations permit this.

Even though datagram sockets are connectionless, the connect() system call serves a purpose when applied to datagram sockets. Calling connect() on a datagram socket causes the kernel to record a particular address as this socket’s peer. The term connected datagram socket is applied to such a socket. The term unconnected datagram socket is applied to a datagram socket on which connect() has not been called (i.e., the default for a new datagram socket).

After a datagram socket has been connected:

Note that the effect of connect() is asymmetric for datagram sockets. The above statements apply only to the socket on which connect() has been called, not to the remote socket to which it is connected (unless the peer application also calls connect() on its socket).

We can change the peer of a connected datagram socket by issuing a further connect() call. It is also possible to dissolve the peer association altogether by specifying an address structure in which the address family (e.g., the sun_family field in the UNIX domain) is specified as AF_UNSPEC. Note, however, that many other UNIX implementations don’t support the use of AF_UNSPEC for this purpose.

The obvious advantage of setting the peer for a datagram socket is that we can use simpler I/O system calls when transmitting data on the socket. We no longer need to use sendto() with dest_addr and addrlen arguments, but can instead use write(). Setting the peer is useful primarily in an application that needs to send multiple datagrams to a single peer (which is typical of some datagram clients).