UDP sockets are sockets that use the User Datagram Protocol. They are distinct from TCP (Transmission Control Protocol) sockets, the more commonly used type of socket in many networking interfaces. However, UDP sockets offer unique use cases and advantages that TCP and other socket protocols do not. In this article, the fundamentals of the UDP protocol will be discussed, along with showing how to implement a server and client using the windows socket library, WinSock.
UDP does not use any concept of a connection while sending or receiving data. This means in terms of networking, there is no handshake going on prior to the transmission of data, nor is there acknowledgement of a request. However, when a client sends datagrams to a UDP server, the server receives the client’s address info, and can send a message back. Yet, this can only happen if the client chooses to receive a message back on the same socket it sent a message with. See the diagram below:
Although the UDP server binds a socket to a port, it does not listen for, nor accept connections. When making a call to function named recvfrom , the running thread will block and wait for an incoming packet on the port the socket is bound to. When it receives that packet, it can send back another packet to the address it received along with the first packet. However, the UDP server does not need to do this. This is why some have dubbed UDP with the nick name “unreliable datagram protocol” . Out of the box, UDP doesn’t have a way of relaying to the client it received the packets intended, but it can be setup to do that with a little extra work.
The benefit of UDP, is that, you don’t need to establish a connection, you can just send pure packets. It gives you a more raw, versatile interface of socket programming.
On Windows, sockets work very similarly like they do on unix systems. In fact, most of the core socket functions have the same name on both types of operating system. Nearly all of the core concepts, like converting between hosts and addresses, socket types, are all the same. A few differences are:
- Windows allow asynchronous socket operations.
- The WinSock library is packaged as a DLL, which requires initialization and cleanup.
Which brings me to the next point; in order to use the WinSock functions and API, it needs to be properly linked to an executable. That can be done in two ways, the first is linking through a build system cmake:
Or, it can be done through the pragma system as a comment, such as one in a header file:
Next, once the WinSock library has been properly linked, it needs to be initialized in the executable. This can be done by calling the WSAStartup function. This function is responsible for retrieving details of the winsock implementation that got linked into the executable.
Now, we can start actually using and playing with sockets. First, the socket to use for the UDP Server has to be created. That can potentially fail with an error, so we will need to check for that too.
You’ll notice here the use of the socket() function. This works identically to the socket function on UNIX systems. The first parameter is the socket family. A socket family is the style and type of address the socket uses. In many cases, that is either IPv4 or IPv6, but a few others also exist. AF_INET means IPv4. The next parameter is the socket type, determining what kind of packet it can receive. Since UDP sockets deal with datagrams, SOCK_DGRAM is used here.
The last parameter is the protocol. The protocol relates much more closely to the socket type than it does the socket family. For example, the UDP protocol can be used with either IPv4 or IPv6 families, but must be used with the datagram socket type. While TCP sockets, must be used with the SOCK_STREAM type.
Binding a Socket
In order to bind a socket, a socket has to be given more information about the address it lives at, as well as it’s port. Since we are build a server, we have to assign it a port explicitly, so client’s can accurately chose which port they will send datagrams to. Similarly, we also need to specify an address for our server to live at. To keep that simple, I selected the most common default address for what is considered “localhost”, 127.0.0.1. The socket and it’s address information struct can be used to bind the socket to a port, like so:
You’ll notice a few new things here, and some familiar ones. The htons() function converts a 16-bit short integer to a network ordered 16bit integer. This is done to ensure that integers are read in the proper endian format used on the network. In the case of WinSock, that’s always big endian.
We also need to give an address to our socket. This can be done in one of two ways. An explicit, IPv4 address contained in a C-string literal can be used, or INADDR_ANY can be specified. If you don’t care about the address used by the server, the INADDR_ANY macro can be specified. The inet_addr converts a IPv4 literal string into an unsigned 32 bit integer in network ordering to use as the address, while htonl converts an unsigned long into a networked ordered 32 bit unsigned integer.
Note: On Windows, the long type is 32 bits in size, the same size as int.
If the bind is not successful, the last error code for WinSock can be retrieved and printed. This can happen for a multitude of reasons, such as a lack of permissions to bind the socket, or a socket being bound to an already used port.
The next role of the UDP server is accepting actual datagrams. Think of datagrams as portions of data, or rather, C-strings. A UDP client will pass data via the sendto function, that is received by the server through the recvfrom function.
The recvfrom function is a blocking call, it will halt the calling thread until it receives a message on the port the socket is bound to. A buffer and relevant size of buffer is also needed to accept the incoming messages. Lastly, a separate address struct is needed to store incoming information from who sent the datagram. The original address struct used for the socket cannot be also utilized here because it would overwrite that existing, vital information.
Once recvfrom returns, we have to check if the call produced an error. If not, terminate the buffer used with the recvfrom call with a null character. At this point, the client does not know we received the packet. It just knows it was sent. We also don’t know if the client is waiting for a response back. At least not unless a specific protocol was used in the message we received to indicate the client is waiting for a response.
For this purpose of this article, let’s assume that the client is configured to wait to receive a message back after sending one to the server. In that case, the server can then use the sendto function to send a message back.
The UDP client uses the same core functions, recvfrom and sendto, just in the reverse order. However, it does not need to bind a socket to a port. It just fills out an address struct with information about where it is sending a datagram to, and sends the message
The UDP server and client relationship can be extended and enhanced. A loop can be performed between the client and server, continuously waiting to receive and sending messages between each other. Another way is to send a “request”, that expects just one response. There are many opportunities to use the UDP protocol in different applications.