Connectionless sockets (the User Datagram Protocol, or UDP) are your other option for transferring data between two networked devices. These are typically used in applications that require little overhead and that want to achieve higher network throughput, such as multimedia streaming protocols. Another advantage in using UDP is that it is capable of transmitting data to multiple endpoints simultaneously because a connection is not bound to a single address. Because UDP transfers datagrams (message packets) instead of a connected stream, these connections are considered unreliable and connectionless. However, don't mistake the term unreliable for low quality?unreliable in this context means only that the protocol does not guarantee that your data packets will ever arrive at your destination. Moreover, there is no sequenced order in which they are guaranteed to arrive, nor any notification if a packet never arrives.
You can compare using UDP datagrams to checking in several pieces of luggage (your packets) at the airport at the same time. Even though they boarded the plane in some order (the packets going out over the network), you're pretty sure that they'll arrive at their destination. Once you get off the plane and attempt to claim your luggage, you're not exactly sure what order they'll be unloaded in (packets arriving at the endpoint), but you can be relatively certain that they'll get there in one piece. Unfortunately, once in a while, something does get lost and never is seen again.
If you are planning to use UDP as your method to send data, it's probably a good idea to have your application either send some sort of acknowledgment that it received a datagram, or provide some way to reassemble packets in a predetermined order by using some sort of sequence?such as a packet number or timestamp?in your datagram message. This can ensure some amount of reliability with the protocol.
Figure 1.4 shows the process for creating both client and server UDP socket connections and how data flows between both network endpoints.
Before you can send or receive UDP packets, whether you are the client or the server, you need to create a socket to talk to using the socket() function, and pass the SOCK_DGRAM and IPPROTO_IDP parameters:
SOCKET sUDPSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
Once you have created your sockets, you also need to bind() the socket to the interface on which you want to receive data. This is done exactly the same way as using a TCP connection.
Now that we have our sockets ready, let's take a look at what is required to both send and receive datagram packets. Remember that even though we have bound the socket, we don't need to call the listen() or accept() functions, as we are going to be operating in a connectionless manner, which enables us to send or receive packets to any device on the network.
Once you have created your sockets, sending a packet over UDP is fairly straightforward. To send a message, you need to call the sendto() function, which is defined as follows:
int sendto (SOCKET s, const char *buf, int len, int flags, const struct sockaddr *to, int tolen);
You might notice that the parameters are similar to the send() function.
The s parameter is the socket on which we want to send data, which was created using the socket() function. The buf parameter is a pointer to a buffer that contains the data we want to send, and its length is specified in the len parameter. The flags parameter is used to affect the way the data is sent, and can be 0 or MSG_DONTROUTE, which specifies that the data should not be routed. Typically, this parameter will be set to 0. The to parameter contains a pointer to a SOCKADDR_IN address structure with the packet's destination address. You can also construct a broadcast packet (sending it to every machine on the network, which is usually not advised), and you can use the address INADDR_BROADCAST if you have set the socket option to broadcast mode (see the section "Socket Options"). Finally, the tolen parameter specifies the length of the to address.
The sendto() function will return the number of bytes it has transferred, or a SOCKET_ERROR if there was a problem sending the datagram.
The following example sends a UDP datagram:
// Create a connectionless socket SOCKET sUDPSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); // Check to see if we have a valid socket if(sUDPSocket == INVALID_SOCKET) { int iSocketError = WSAGetLastError(); return FALSE; } // Set up the target device address. For this sample, // we are assuming it is a machine at 192.168.0.1, and on // port 40040 SOCKADDR_IN sTargetDevice; memset(&sTargetDevice, 0, sizeof(SOCKADDR_IN)); sTargetDevice.sin_family = AF_INET; sTargetDevice.sin_port = htons(40040); sTargetDevice.sin_addr.s_addr = inet_addr("192.168.0.1"); // Send a datagram to the target device char cBuffer[1024] = "Test Buffer"; int nBytesSent = 0; int nBufSize = strlen(cBuffer); nBytesSent = sendto(sUDPSocket, cBuffer, nBufSize, 0, (SOCKADDR *) &sTargetDevice, sizeof(SOCKADDR_IN)); // Close the socket closesocket(sUDPSocket);
To have your application receive a UDP packet, you need to call the recvfrom() function, which will block until data has arrived from a client (or it can return immediately if in nonblocking mode; see "Socket Options"):
int recvfrom (SOCKET s, char *buf, int len, int flags, struct sockaddr *from, int *fromlen);
Notice that the parameters are very similar to those described for the recv() function. The first parameter, s, is the socket on which we want to receive data. Next, buf is a pointer to a buffer for the incoming data, and its size is specified by the len parameter. The flags parameter must be set to 0. Finally, the from parameter contains a pointer to the SOCKADDR_IN structure, which contains information about the device that sent the data. A pointer to its length is in the fromlen field.
If the packet is received successfully, recvfrom() will return a 0; otherwise, a SOCKET_ERROR will occur.
The following example shows how to receive a UDP datagram packet:
// Create a connectionless socket SOCKET sUDPSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); // Check to see if we have a valid socket if(sUDPSocket == INVALID_SOCKET) { int iSocketError = WSAGetLastError(); return FALSE; } // Setup a bind on the socket, telling us what port and // adapter to receive datagrams on. Assume we are listening // on port 40040 SOCKADDR_IN sReceiveFromAddr; memset(&sReceiveFromAddr, 0, sizeof(SOCKADDR_IN)); sReceiveFromAddr.sin_family = AF_INET; sReceiveFromAddr.sin_port = htons(40040); sReceiveFromAddr.sin_addr.s_addr = htonl(INADDR_ANY); if(bind(sUDPSocket, (SOCKADDR *)&sReceiveFromAddr, sizeof(SOCKADDR_IN)) == SOCKET_ERROR) { int iSocketError = WSAGetLastError(); return FALSE; } // Receive a datagram from another device char cBuffer[1024] = ""; int nBytesRecv = 0; int nBufSize = strlen(cBuffer); int nReceiveAddrSize = 0; // Get the datagram nBytesRecv = recvfrom(sUDPSocket, cBuffer, nBufSize, 0, (SOCKADDR *) &sReceiveFromAddr, &nReceiveAddrSize); // Close the socket closesocket(sUDPSocket); WSACleanup();
One final note regarding the sending and receiving of UDP data: You can also transfer data by using the connect(), send(), and recv() functions. Transmitting UDP data this way is considered a somewhat "directed" connectionless transfer, and should be used only if you plan to communicate with one other device during a session (i.e., all packets are sent to the same address). To do this, after your UDP socket has been created, call the connect() function with SOCKADDR_IN set to the machine you want to establish a session with. No real connection will be established, but you can use the send() and recv() functions to transfer data with the associated address.