CodingBison

Connectionless sockets do not require any explicit connection setup before sending and receiving data. For a connectionless server, all we need to do is to create a socket and bind it a known port; with that, we can start receiving data. For a client, we do not even have to bother with the bind step, simply create a socket and start sending data!

However, a lack of explicit connection means that each time we wish to send data, we now need to specify the address of the remote (receiver) socket. This also means that we can use one socket to send data to multiple sockets; thus, we can potentially change the address to a new socket endpoint for every send call.

Connectionless sockets use UDP (User Datagram Protocol) protocol and are characterized by sockets of type SOCK_DGRAM. Unlike TCP, UDP neither retransmits lost packets nor adjusts its sending-rate. In this regard, UDP is a rather simple protocol. This simplicity is not without its merit! One of the attractive features of UDP is that since it does not need to retransmit lost packets nor does it do any connection setup, sending data incurs less delay. This lower delay makes UDP an appealing choice for delay-sensitive applications like audio and video.

Let us now describe implementation of UDP-based sockets. More specifically, we discuss socket APIs that allow us to build a UDP server and a UDP client.

UDP Socket Server

We begin our discussion of a UDP socket server by listing various socket APIs that are needed by a server. Following that, we also provide an implementation of the same.

 int socket(int family, int type, int protocol); 
 int bind(int fd, const struct sockaddr *addr, socklen_t addrlen);
 int close(int fd);

Like the case of TCP sockets, the socket() call is the very first call for a server, or for that matter, a client. This call accepts three parameters. The first parameter is the address family that can be AF_INET (for IPv4) or AF_INET6 (for IPv6). The second parameter is the protocol type that can be SOCK_STREAM (for TCP) or SOCK_DGRAM (for UDP). The last parameter is the protocol and its value can be IPPROTO_TCP (for TCP) and IPPROTO_UDP (for UDP).

Thus, if we want to create an IPv4 UDP socket, then the params should be: AF_INET4, SOCK_DGRAM, and IPPROTO_UDP. For IPv6, they should be: AF_INET6, SOCK_DGRAM, and IPPROTO_UDP.

The return value of the socket() call is a file descriptor that is used to symbolically refer to this socket. If we have to do any operation on this socket, then we must pass this file descriptor. On error, this call returns -1 and sets the system errno variable. For debugging purposes, we should print the error for failed cases.

Unlike a TCP socket server, a UDP socket server neither uses listen() nor accept() call. Instead, it simply relies on a bind() call. The bind() call allows an application to specify a port number and an address on the local machine. Each host can have as much as 65,536 ports for each protocol; typically, port numbers in the range of 0 to 1024 are standard ports and are reserved for various applications. Ports outside this range are usually available for general use. For a detailed list of well-known ports, please visit Internet Assignment Numbers Authority (IANA) website.

The bind() calls takes three params: (a) the file descriptor of the socket that we need to bind, (b) a pointer to an address structure that holds the local port and local IP address, and (c) the length of the address buffer pointed by the address pointer. If successful, it returns 0, otherwise, this call returns -1 and also sets the errno variable. Once again, we should print the error number for error cases.

Binding is needed because this uniquely establishes the combination of port number and address for the UDP server in the whole Internet. Due to that, a remote socket client can send data the server as long as it knows the server port and the machine's address.

If we were to use the analogy of regular postage mail, then the bind() call would mean assigning (or using) a mailing address to a house. This mailing address has to be unique (throughout the world!) and it is this uniqueness that allows the post office to deliver all the mails destined to this house correctly.

One we are done with all the operations, we should call close() and pass the file descriptor of the socket that we wish to close. We should not forget to close the socket since this step releases all the resources held by the socket. On success it returns 0, else it returns -1 and sets the errno variable.

The next four functions allow a UDP socket server to send and receive data. A client socket can also uses these functions.

 ssize_t sendto(int fd, const void *buf, size_t len, int flags, 
                     const struct sockaddr *receiver_addr, socklen_t addrlen);
 ssize_t sendmsg(int fd, const struct msghdr *msg, int flags);
 ssize_t recvfrom(int fd, void *buf, size_t len, int flags,
                     struct sockaddr *sender_addr, socklen_t *addrlen);
 ssize_t recvmsg(int fd, struct msghdr *msg, int flags);

The first two calls sendto() and sendmsg() send data to the other end of the socket pipe; sending data is dependent upon application logic and any side can send data.

The sendto() calls takes six parameters. The first four are: (a) the file descriptor of the sending socket, (b) a pointer to a buffer that contains the data we wish to send, (c) number of bytes of data present in the buffer that we wish to send, and (d) a flag to modify the behavior of the sendto() call. The underlying socket/UDP layer copies this data into the outgoing send buffer; the underlying socket/UDP layer maintains a send buffer for every socket. The last two parameters allow the sender to pass the address and addrlen of the remote UDP receiver.

The sendmsg() call is similar to sendto() except that four of the sendto() parameters (buf, len, receiver_addr, and addrlen) are nicely tucked in a msghdr structure. Therefore, before we do anything else, let us look at the msghdr structure:

 struct msghdr {
     void         *msg_name;       /* Address of the other end */
     socklen_t     msg_namelen;    /* Size of address */
     struct iovec *msg_iov;        /* Scatter/gather array */
     size_t        msg_iovlen;     /* No of elements in msg_iov array */
     void         *msg_control;    /* Ancillary buffer */
     size_t        msg_controllen; /* Length of ancillary buffer */
     int           msg_flags;      /* flags on received message */
 };

 struct iovec {
     void         *iov_base;       /* Starting address */
     size_t        iov_len;        /* No of bytes at iov_base address */
 };

The msg_name/msg_namelen fields are equivalent of sendto() call's receiver_addr and addrlen.

The msg_iov is a scatter/gather array of struct iovec. Each element of the msg_iov array is a struct iovec and hence, contains a pointer to the start of a scatter data buffer and the length of each scatter data. Basically, instead of having one large buffer (like the sendto() call), we now have multiple smaller buffers scattered (in the form of iovecs) throughout the memory. The msg_iovlen field specifies the number of these iovec buffer elements. Thus, when handling msghdr, we would need to run a loop and collect (gather) all the scattered data. The underlying layer gathers all of these buffer in a single data packet before sending it out.

The last three fields (msg_control and msg_controllen and msg_flags are used for the receive side and so, we will skip their discussion for now). Since both sendto() and sendmsg() calls send data to the peer socket, if this is your first time writing a socket program, it would be okay if you choose to use sendto() since that is the simpler of the two! Once you have mastered sockets, you can use either of these, as you see fit.

For both of these calls, if the data to be sent is more than the space available in the underlying send buffer, then they can block. However, if the socket is non-blocking, then these calls would return -1 and set errno to EAGAIN or EWOULDBLOCK; when that happens, the application would have to retry later. On success, both of these call return the number of bytes sent and on error, they return -1.

The next call, recvfrom() enables an application to get received data. Like the sendto() call, this call also takes six params. The first four are: (a) file descriptor of the socket, (b) a pointer to a buffer where the underlying layer will copy received data, (c) number of bytes in the buffer, and (d) a flag to modify the behavior of the recvfrom() call. We pass buffer so that the underlying socket/TCP layer can copy received data (from the client) into this buffer.

The last two parameters are sender_addr and addrlen -- recvfrom() copies the address of the remote client into the src_addr buffer. This allows the application to know about the peer sitting on the other end of the UDP pipe. It is okay to pass sender_addr as NULL. In that case, the underlying UDP protocol would not return the address of sender.

The function recvmsg() is akin to recvfrom() except that the parameters, buffer pointer, the length of the buffer, sender_addr, and addrlen are clubbed together within a msghdr data structure. Fields msg_name and msg_namelen correspond to the params sender_addr and addrlen of recvfrom() call. When the msg_name field is not NULL and the msg_namelen is not zero, the underlying layer copies the sender's address and the address length.

What is really unique about msghdr for recvmsg() is that one can get miscellaneous ancillary data about the incoming data by specifying the fields msg_control and msg_controllen. The control messages are of yet another structure type, cmsghdr that holds level and type of the ancillary information.

Upon success, both recvfrom() and recvmsg() return the number of bytes of the received message that they copied in the passed buffer. Note that UDP is a datagram protocol and so messages are received in the same size as sent by the sender. So, if we receive a big message (k_rcvd) and the caller passes a smaller length value (k_passed), then UDP layer copies that many bytes (k_passed) from the message and discards the remaining bytes.

Both recvfrom() and recvmsg() are blocking. If the underlying UDP layer has no received data from the peer, then both of these calls would simply wait till the UDP layer socket receives some data. if the socket is non-blocking and if there is no received data, then both calls would return -1 and set the errno to EAGAIN or EWOULDBLOCK.

These two receive calls also accept a flag as a parameter. The flag field helps us fine-tune the behavior of these calls. Two of these flags are: MSG_DONTWAIT and MSG_PEEK. MSG_DONTWAIT specifies that if the underlying UDP has no data, then the calls should return immediately -- in that case, the returned value would be -1. With MSG_PEEK, these calls would return the data requested, but would not delete the data from the receive buffer since the goal is only to peek; we would need a subsequent recvfrom()/recvmsg() call with no MSG_PEEK flag to drain the data from the UDP receive buffer.

One last note. The calls, sendto(), sendmsg(), recvfrom(), and recvmsg() are not limited to connectionless sockets. We can call them even from a connection-oriented socket (e.g. TCP). When calling sendto() and sendmsg(), the underlying layer would ignore the sender's address and sender address length, since the socket is already connected and so it does not really need the peer's address.

With that, let us begin our implementation of a UDP server. We provide the example first and then explain its various pieces.

 #include <stdio.h>
 #include <errno.h>
 #include <netinet/in.h> 

 #define DATA_BUFFER 5000

 int main () {
     struct sockaddr_in saddr, new_addr;
     int fd, ret_val;
     char buf[DATA_BUFFER];
     socklen_t addrlen; 

     /* Step1: open a UDP socket */
     fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 
     if (fd == -1) {
         fprintf(stderr, "socket failed [%s]\n", strerror(errno));
         return -1;
     }
     printf("Created a socket with fd: %d\n", fd);

     /* Initialize the socket address structure */
     saddr.sin_family = AF_INET;         
     saddr.sin_port = htons(7000);     
     saddr.sin_addr.s_addr = INADDR_ANY; 

     /* Step2: bind the socket */
     ret_val = bind(fd, (struct sockaddr *)&saddr, sizeof(struct sockaddr_in));
     if (ret_val != 0) {
         fprintf(stderr, "bind failed [%s]\n", strerror(errno));
         close(fd);
         return -1;
     }

     /* Step3: Start receiving data. */
     printf("Let us wait for a remote client to send some data\n");
     ret_val = recvfrom(fd, buf, DATA_BUFFER, 0,
                 (struct sockaddr *)&new_addr, &addrlen);
     if (ret_val != -1) {
         printf("Received data (len %d bytes): %s\n", ret_val, buf);
     } else {
         printf("recvfrom() failed [%s]\n", strerror(errno));
     }

     /* Last step: close the socket */
     close(fd);
     return 0;
 }

The above program begins by creating a UDP socket using socket() call. After creating the socket, the program binds it to a port (in this case, 7000) using the bind() call. For address, we pass INADDR_ANY (equal to zero) that means the default local address on the server. Once the UDP socket is bound, we issue recvfrom() call that waits for any incoming UDP datagram. To keep things simple, once we receive a data, we close the socket server by using a close() call.

UDP Socket Client

Now that we have the example for UDP server ready, let us look at its counterpart: a UDP client. For that, we provide an example that implements a simple UDP client.

 #include <stdio.h>
 #include <errno.h>
 #include <string.h>
 #include <netinet/in.h> 
 #include <netdb.h> 

 #define DATA_BUFFER "Mona Lisa was painted by Leonardo da Vinci"

 int main () {
     struct sockaddr_in saddr;
     int fd, ret_val;
     struct hostent *host; /* need netdb.h for this  */

     /* Step1: open a UDP socket */
     fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 
     if (fd == -1) {
         fprintf(stderr, "socket failed [%s]\n", strerror(errno));
         return -1;
     }
     printf("Created a socket with fd: %d\n", fd);

     /* Next, initialize the server address */
     saddr.sin_family = AF_INET;         
     saddr.sin_port = htons(7000);     
     host = gethostbyname("127.0.0.1");
     saddr.sin_addr = *((struct in_addr *)host->h_addr);

     /* Step2: send some data */
     ret_val = sendto(fd,DATA_BUFFER, strlen(DATA_BUFFER) + 1, 0, 
         (struct sockaddr *)&saddr, sizeof(struct sockaddr_in));
     if (ret_val != -1) {
         printf("Successfully sent data (len %d bytes): %s\n", ret_val, DATA_BUFFER);
     } else {
         printf("sendto() failed [%s]\n", strerror(errno));
     }

     /* Last step: close the socket */
     close(fd);
     return 0;
 }

The example begins by creating a UDP socket using socket() call. Following that, it calls sendto() to send some data. Since this socket is not a connection-oriented socket (UDP client does not use a connect() call), we do have to specify the address of the remote client for the sendto() call. In some ways, it is a trade-off!

To keep things simple, the example passes host address as the localhost since it runs both server and the client on the same machine. Of course, if it were to be created on a different machine, the sendto() step would require the IP address of the UDP server. Lastly, the client sends a single datagram as the UDP server is expecting only one datagram and then closes the socket.

Like the case of a TCP client, the UDP client also does not bother to do the bind() call because only one of the two endpoints need to sit at a well-known (or pre-communicated) port. If the other UDP server needs to communicate data to the client, then it can always retrieve the port and address of the client since the recvfrom() call provides an option to retrieve client's address. The server can use this information to send a reply back to the client. So, even though the client does not do an explicit bind, the server can still send data as long as the client sends the data first!

Making them talk!

Now that both the server and client examples are ready, let us run them together and have some fun! To see the client-server interaction, we need to run the server and the client on different terminals. The server should be run first so that it can wait for the client.

Here is the output from server. When we run the server, it creates a socket with file descriptor value as 3 and then starts to wait for incoming data. Once we start the client (on another terminal), the server prints the last output. Please note that we can run both the server and the client on the same terminal as well. For that, we can first run the server in the background mode as "./udp_server &" and then run the client.

 $ gcc udp-server.c -o udp_server
 $
 $ ./udp_server
 Created a socket with fd: 3
 Let us wait for a remote client to send some data
 Received data (len 43 bytes): Mona Lisa was painted by Leonardo da Vinci

Following is the output for the client. The client creates a socket (also with file descriptor value as 3) and then send data right away!

 $ gcc udp-client.c -o udp_client
 $.
 $ ./udp_client
 Created a socket with fd: 3
 Successfully sent data (len 43 bytes): Mona Lisa was painted by Leonardo da Vinci

When debugging UDP sockets, we can use ss tool on Linux and netstat tool on non-Linux (Windows or Mac OS) boxes to get additional information. Please note that the "netstat" tool is deprecated on Linux. If we were to run these tools after running the "udp_server" program, then we would find an entry for the UDP server, sitting on port 7000 and with a state of UCONN. The netstat shows the "State" column as UCONN or empty because UDP is a connectionless protocol and hence, lacks states similar to those of TCP.

To have a crisp output, we pass a set of options to both ss and netstat tools: "u" for udp only sockets, "p" for printing associated programs, "a" for printing all connections, and "n" for printing numeric addresses. Also, for these output, to access names of all programs, we must be logged as a root, else the "p" option would not print the programs associated with these sockets.

As mentioned earlier, we need to run the udp_server first and then get these outputs since if we run the udp_client, then the udp_server code would receive data and close the socket. Once the udp_server closes the socket, we would not be able to see any entry for the socket!

Here is the output for both ss and netstat tools.

 [root@codingbison]# ss -upan
 State    Recv-Q Send-Q   Local Address:Port   Peer Address:Port 
 UNCONN   0      0        *:48408              *:*   users:(("dhclient",22086,20))
 UNCONN   0      0        *:7000               *:*   users:(("udp_server",23994,3))
 UNCONN   0      0        *:68                 *:*   users:(("dhclient",22086,6))
 UNCONN   0      0        *:631                *:*   users:(("cupsd",1549,13))
 UNCONN   0      0        :::47798             :::*  users:(("dhclient",22086,21))
 [root@codingbison]# 
 [root@codingbison]# netstat -upan
 Active Internet connections (servers and established)
 Proto Recv-Q Send-Q Local Address    Foreign Address  State  PID/Program name   
 udp        0      0 0.0.0.0:48408    0.0.0.0:*               22086/dhclient      
 udp        0      0 0.0.0.0:7000     0.0.0.0:*               23994/./udp_server  
 udp        0      0 0.0.0.0:68       0.0.0.0:*               22086/dhclient      
 udp        0      0 0.0.0.0:631      0.0.0.0:*               1549/cupsd          
 udp        0      0 :::47798         :::*                    22086/dhclient      
 [root@codingbison]# 




comments powered by Disqus