The implementation of UDP (User Datagram Protocol) sockets in the C language provide a good solution for network communication. UDP operates on top of the IP address and it is commonly used for applications where low latency and efficiency are essential. Let us discuss about how to implement the UDP sockets in the C language. We will explain both on the server-side and client-side. Using the socket functions, address configuration, and data sending or receiving methods, we can establish reliable and efficient communication channels between the server and client applications.
Definition of UDP
UDP (User Datagram Protocol) is a transport layer protocol that offers a straightforward and lightweight mechanism to transmit and receive the datagrams across a network. As a connection less and unreliable protocol, UDP prioritizes simplicity and efficiency over reliability and error recovery mechanisms provided by other protocols like TCP.
UDP is often used in situations where real-time and low-latency communication is important. The simplicity of UDP is one of its primary characteristics. UDP does not initially create a connection between the sender and recipient as TCP does. However, it sees each datagram as a separate entity which allows for the possibility of packets arriving out of sequence, being lost, or being duplicated. As a result, UDP cannot ensure the data delivery or sequencing.
As we know, UDP is lightweight and it is frequently used for various applications and protocols. For example, DNS (Domain Name System) relies on UDP to quickly resolve the domain names to IP addresses. DHCP (Dynamic Host Configuration Protocol) use UDP to assign the IP addresses and network configuration to devices. Additionally, multimedia streaming applications such as video, and audio streaming services such as Zoom and Google Meet use UDP to provide a real-time data with low latency.
Let us see the following diagram to understand the implementation of UDP:
UDP implementation diagram
Implement the UDP Sockets in C
Programming Example 1: Server-Side Program
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#define SERVER_PORT 7070
#define BUFFER_SIZE 1024
int main ()
{
int serverSocket;
struct sockaddr_in serverAddress, clientAddress;
char buffer[BUFFER_SIZE];
// Create socket
serverSocket = socket(AF_INET, SOCK_DGRAM, 0);
if (serverSocket < 0) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
// Bind socket to port
memset(&serverAddress, 0, sizeof(serverAddress));
serverAddress.sin_family = AF_INET;
serverAddress.sin_addr.s_addr = htonl(INADDR_ANY);
serverAddress.sin_port = htons(SERVER_PORT);
if (bind (serverSocket, (struct sockaddr *) & serverAddress, sizeof(serverAddress)) < 0) {
perror ("Binding failed");
exit (EXIT_FAILURE);
}
// Receive and process data
while (1) {
socklen_t clientAddressLength = sizeof(clientAddress);
ssize_t dataLength = recvfrom(serverSocket, buffer, BUFFER_SIZE, 0, (struct sockaddr *)&clientAddress, &clientAddressLength);
if (dataLength < 0) {
perror ("Error in receiving data");
exit (EXIT_FAILURE);
}
// Process received data
printf("Server received: %s\n", buffer);
// reply back to client server
sendto(serverSocket, "Server response", strlen("Server response"), 0, (struct sockaddr *)&clientAddress, clientAddressLength);
}
close (serverSocket);
return 0;
}
Programming Example 2: Client-Side Program
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define SERVER_PORT 7070
#define BUFFER_SIZE 1024
int main () {
int clientSocket;
struct sockaddr_in serverAddress;
char buffer[BUFFER_SIZE];
const char *message = "this is a message";
// Create socket
clientSocket = socket (AF_INET, SOCK_DGRAM, 0);
if (clientSocket < 0) {
perror ("Socket creation failed");
exit (EXIT_FAILURE);
}
// Configure server address
memset (&serverAddress, 0, sizeof (serverAddress));
serverAddress.sin_family = AF_INET;
serverAddress.sin_port = htons (SERVER_PORT);
if (inet_pton(AF_INET, "127.0.0.1", &(serverAddress.sin_addr)) <= 0) {
perror ("Invalid address");
exit (EXIT_FAILURE);
}
// Send message to the server
if (sendto(clientSocket, message, strlen (message), 0, (struct sockaddr *) & serverAddress, sizeof (serverAddress)) < 0) {
perror ("Error in sending data");
exit (EXIT_FAILURE);
}
// Receive response from the server
ssize_t responseLength = recvfrom(clientSocket, buffer, BUFFER_SIZE, 0, NULL, NULL);
if (responseLength < 0) {
perror ("Error in receiving response");
exit (EXIT_FAILURE);
}
// Process the received response
buffer [responseLength] = '\0';
printf ("Client received: %s\n", buffer);
close (clientSocket);
return 0;
}
Output:
$ gcc ser.c -o ser
$ ./ser
Server received: this is a message
** client-side output **
gcc cli.c -o cli
./cli
Client received: Server response
Explanation:
We first use the “socket()” function in the UDP server-side and client-side program to implement a UDP socket. After that, we set the address of the client for the client-side code. We define the server address for the server-side code. The socket is then enabled to receive the data using the “bind()” command to connect it to the server’s address. Here in the program, the client uses the “sendto()” function to send the data to the server. After that, only the server uses the “recvfrom()” function to receive the data from the client and keeps it in a buffer. The “recvfrom()” function uses the client to get the server’s response. At last, the program displays it in the terminal or command prompt.
Conclusion
The implementation of UDP sockets in the C language provides us with a very simple process to communicate between the server and client applications. UDP allows us for the exchange of datagrams over a network. UDP also makes it suitable for scenarios where low latency is preferred. Using the UDP socket functions, address configuration, and data sending operations, we can get better communication between the server and the client.