Introduction
What is a Datagram Socket?
Creating a datagram socket is similar to creating a stream socket.
The main difference is that datagram sockets use UDP instead of TCP, which means that the client and server do not need to establish a connection before sending data.
This is useful for applications that do not need to maintain a connection beacuse the client and the server can send and receive data without having to establish a connection first.
For example, the DNS server uses datagram sockets to send and receive DNS queries and responses.

Source: UDP Server-Client implementation in C
Server - Listener
How Listener Works
The listener works by creating a socket, binding it to a port, and waiting for incoming messages.
The listener creates a struct addrinfo called hints. This struct is used to specify what kind of address the listener wants to use. In this case, the listener wants to use IPv6 and UDP.
The listener calls the getaddrinfo function to get a list of addresses that match the hints struct. The listener then loops through the list of addresses and tries to create a socket for each one. If the socket creation succeeds, the listener breaks out of the loop.
The listener calls the bind function to bind the socket to the specified port. This allows the listener to receive messages sent to that port.
Once the socket is bound, the program will listen for incoming messages from the recvfrom function. This recvfrom function will blocks the program until a message is received, and then stores the message in the buf buffer and the sender’s address information in their_addr.
The program prints the message and the sender’s IP address to the console.
The program will then close the socket and exit.
Listener Code Example
/*
** listener.c -- a datagram sockets "server" demo
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#define MYPORT "4950" // the port users will be connecting to
#define MAXBUFLEN 100 // max number of bytes we can get at once
// get IP address from sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa)
{
// check if IPv4 or IPv6
if (sa->sa_family == AF_INET)
{
// return IPv4 address
return &(((struct sockaddr_in *)sa)->sin_addr);
}
// return IPv6 address
return &(((struct sockaddr_in6 *)sa)->sin6_addr);
}
// listener main function
int main(void)
{
int sockfd; // socket file descriptor
struct addrinfo hints; // hints for getaddrinfo(), to specify what kind of address we want
struct addrinfo *servinfo; // results from getaddrinfo()
struct addrinfo *p; // used to loop through results
int rv; // return value from getaddrinfo(), used to check for errors
int numbytes; // number of bytes received
struct sockaddr_storage their_addr; // connector's address information
char buf[MAXBUFLEN]; // buffer to store received data
socklen_t addr_len; // length of connector's address
char s[INET6_ADDRSTRLEN]; // string to store IP address
memset(&hints, 0, sizeof hints); // initialize hints to 0
hints.ai_family = AF_INET6; // set to AF_INET6 to use IPv6
hints.ai_socktype = SOCK_DGRAM; // set to SOCK_DGRAM to use UDP
hints.ai_flags = AI_PASSIVE; // use my IP
// get address information and store in servinfo
if ((rv = getaddrinfo(NULL, MYPORT, &hints, &servinfo)) != 0)
{
// gai_strerror() returns a string describing the error
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
return 1;
}
// loop through all the results and bind to the first we can
for (p = servinfo; p != NULL; p = p->ai_next)
{
// create socket
if ((sockfd = socket(p->ai_family, p->ai_socktype,
p->ai_protocol)) == -1)
{
perror("listener: socket");
continue;
}
// bind socket to port
if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1)
{
close(sockfd);
perror("listener: bind");
continue;
}
break;
}
// check if p is NULL, if so, no address was bound
if (p == NULL)
{
fprintf(stderr, "listener: failed to bind socket\n");
return 2;
}
// free servinfo
freeaddrinfo(servinfo);
printf("listener: waiting to recvfrom...\n");
// addr_len is used to store the size of the address
addr_len = sizeof their_addr;
if ((numbytes = recvfrom(sockfd, buf, MAXBUFLEN - 1, 0,
Improved (struct sockaddr *)&their_addr, &addr_len)) == -1)
{
perror("recvfrom");
exit(1);
}
// inet_ntop() converts IP address to string
printf("listener: got packet from %s\n",
inet_ntop(their_addr.ss_family,
get_in_addr((struct sockaddr *)&their_addr),
s, sizeof s));
// print number of bytes received and the data
printf("listener: packet is %d bytes long\n", numbytes);
// add null terminator to end of string
buf[numbytes] = '\0';
// print received data
printf("listener: packet contains \"%s\"\n", buf);
// close socket
close(sockfd);
return 0;
}
Client - Talker
How Talker Works
The talker works by setting up a socket, sending a message to the listener, and then closing the socket.
The talker first creates a struct addrinfo called hints, which is used to specify what kind of address we want. The talker sets the ai_family field to AF_INET6 to use IPv6, the ai_socktype field to SOCK_DGRAM to use UDP, and the ai_flags field to AI_PASSIVE to use my IP.
The talker then calls the getaddrinfo() function to get a list of addresses that match the criteria specified in the hints struct. The talker then loops through the list of addresses and tries to create a socket using the socket() function. If the socket is successfully created, the talker breaks out of the loop.
After the socket established, the talker calls the sendto() function to send the message though the socket,and returns the number of bytes sent if successful, -1 if an error occurred.
Finally, the talker closes the socket using the close() function and exits the program.
Talker Code Example
/*
** talker.c -- a datagram "client" demo
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#define SERVERPORT "4950" // the port users will be connecting to
int main(int argc, char *argv[])
{
int sockfd; // socket descriptor
struct addrinfo hints; // hints for getaddrinfo(), to specify what kind of address we want
struct addrinfo *servinfo; // servinfo will point to the results from getaddrinfo()
struct addrinfo *p; // used to loop through all the results and connect to the first we can
int rv; // rv will store the return value from getaddrinfo(), used to check if there is an error
int numbytes; // numbytes will store the number of bytes sent
// check if the number of arguments is correct
if (argc != 3)
{
fprintf(stderr, "usage: talker hostname message\n");
exit(1);
}
memset(&hints, 0, sizeof hints); // initialize hints to 0
hints.ai_family = AF_INET6; // set to AF_INET6 to use IPv6
hints.ai_socktype = SOCK_DGRAM; // set to SOCK_DGRAM to use UDP
// get address information and store in servinfo
if ((rv = getaddrinfo(argv[1], SERVERPORT, &hints, &servinfo)) != 0)
{
// gai_strerror() returns a string describing the error
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
return 1;
}
// loop through all the results and make a socket
for (p = servinfo; p != NULL; p = p->ai_next)
{
// create a socket
if ((sockfd = socket(p->ai_family, p->ai_socktype,
p->ai_protocol)) == -1)
{
perror("talker: socket");
continue;
}
break;
}
// if p is NULL, it means that we have looped through all the results and none of them worked
if (p == NULL)
{
fprintf(stderr, "talker: failed to create socket\n");
return 2;
}
// send the message
if ((numbytes = sendto(sockfd, argv[2], strlen(argv[2]), 0,
p->ai_addr, p->ai_addrlen)) == -1)
{
perror("talker: sendto");
exit(1);
}
// free the servinfo linked list
freeaddrinfo(servinfo);
// print the number of bytes sent
printf("talker: sent %d bytes to %s\n", numbytes, argv[1]);
// close the socket
close(sockfd);
return 0;
}
Compiling and Running the Code
Compiling
gcc -o talker talker.c
gcc -o listener listener.c
Running and output
./listener
listener: waiting to recvfrom...
# after running talker
listener: got packet from ::1
listener: packet is 5 bytes long
listener: packet contains "hello"
# In another terminal
./talker localhost hello
talker: sent 5 bytes to localhost
Conclusion
Summary
Run the listener program first, then run the talker program. The listener program will wait for a message from the talker program. When the talker program is run, it will send a message to the listener program, which will print the message.
If no listener program is running, the talker program will sned the message and then exit. Those messages will be lost, since there is no program to receive them.
The UDP protocol is a simple, lightweight protocol that is easy to use. However, it does not provide any error checking or recovery and not guaranteed to deliver messages to the destination, so it is not suitable for applications that require reliable communication.
References
Last modified on 2023-01-16
Thank you for your support!