Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 55

UDP Socket API for Client-Server Communication

Recommended resources

Introduction

Welcome back! In our last two lessons, you did an excellent job building a complete TCP client-server application. You've mastered the connection-oriented model, where connect(), listen(), and accept() work together to create a stable, reliable communication channel, much like a phone call.

Today, we shift gears to TCP's counterpart: UDP (User Datagram Protocol). UDP is connectionless and operates more like sending a postcard. You write the message, put the address on it, and drop it in the mail. There's no prior setup, and you don't get a confirmation that it arrived. This model is ideal for applications where speed is critical and losing an occasional packet isn't a disaster, such as in DNS lookups, online gaming, or video streaming.

Our goal in this lesson is to describe the socket API calls for a typical UDP client and server. We won't be writing the full code just yet; instead, we'll focus on understanding the what and why of the functions that make UDP's "fire-and-forget" model possible. This will lay the groundwork for our next lesson, where you'll implement these concepts in C.


The UDP Model: Connectionless Communication

In the TCP world, you established a connection first, and then you could simply send() and recv() data through that established pipe. The operating system handled addressing for you after the initial setup.

UDP is different. Since there is no persistent connection, every single datagram (the UDP equivalent of a packet) must be individually addressed. This fundamentally changes the API calls we use.

Let's look at a diagram that contrasts the two models.

UDP Client-Server Socket API Flow
This diagram shows the sequence of API calls for a UDP client and server. Notice the absence of `listen()` and `accept()` on the server and `connect()` on the client. The core of the communication revolves around `sendto()` and `recvfrom()`.

The key takeaways from this model are:

  • No Handshake: The client doesn't connect() and the server doesn't listen() or accept(). They just start sending and receiving.
  • New Functions: The generic send() and recv() are replaced by sendto() and recvfrom(). As their names imply, they include parameters for address information.
  • Server bind() is still crucial: The server still needs to bind() to a specific, well-known port so clients know where to send their datagrams.

The C API for UDP

Let's dive into the specifics of the C functions. We'll watch a video that walks through building a UDP sender (client) and receiver (server) in C. This will give you a practical look at the API calls in action.

How to Send and Receive UDP packets (in C)

The video 'How to Send and Receive UDP packets (in C)' by Jacob Sorber is an excellent, practical walkthrough. It demonstrates exactly the API calls we are discussing and shows how they fit into a working program.

Watch the entire video (around 23 minutes). As you watch, focus on identifying the purpose of these key functions: Sender (Client) Side: socket() with SOCK_DGRAM Preparing the server's sockaddr_in struct The sendto() function and its arguments Receiver (Server) Side: bind() to a specific port The recvfrom() function and how it provides the sender's address Using the sender's address to send a reply back

Now that you've seen the code in action, let's break down the API calls for the server and client methodically.


The UDP Server: socket, bind, recvfrom, sendto

A UDP server's job is to wait at a known port, receive a datagram from any client, process it, and send a reply back to that specific client.

  1. socket(): The server first creates its communication endpoint.

    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    

    The only difference from your TCP code is SOCK_DGRAM. This tells the operating system to create a socket that sends and receives connectionless datagrams using UDP.

  2. bind(): The server then claims a port.

    struct sockaddr_in servaddr;
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(9800);
    
    bind(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr));
    

    This is identical to the TCP server's bind() call. It's how a client can find the server—by sending a packet to the server machine's IP address at port 9800.

  3. recvfrom(): This is the heart of the UDP server. It replaces the accept()/recv() combination from TCP.

    char buffer[1024];
    struct sockaddr_in cliaddr;
    socklen_t len = sizeof(cliaddr);
    
    int n = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *) &cliaddr, &len);
    

    The recvfrom function does two critical things:

    • It blocks and waits for a datagram to arrive at the bound port (9800).
    • When a datagram arrives, it copies the data into buffer and, crucially, it fills the cliaddr struct with the IP address and port of the client who sent it.
  4. sendto(): To send a reply, the server uses the address information it just obtained from recvfrom.

    sendto(sockfd, "Reply message", 13, 0, (struct sockaddr *) &cliaddr, len);
    

    The server uses the same socket (sockfd) to send the reply. It provides the client's address (cliaddr) as the destination, ensuring the reply goes back to the correct sender.

This request-reply sequence often happens inside a while(1) loop, allowing the server to handle datagrams from many different clients, one after another.

For a concise, text-based summary of these functions, you can refer to the following resource.

C/C++ -> Sockets Tutorial

The 'Sockets Tutorial' webpage provides a clear, text-based explanation of the differences in a datagram socket server.

Read the sections explaining the differences in the UDP server code. Focus on the description of the recvfrom() and sendto() system calls and their arguments. This will reinforce what you saw in the video.


The UDP Client: socket, sendto, recvfrom

A UDP client's job is simpler. It doesn't need to establish a connection; it just prepares a message and sends it directly to the server's known address.

  1. socket(): The client creates a datagram socket, just like the server.

    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    
  2. bind() (Optional): Unlike the server, the client typically does not call bind(). When the client calls sendto() for the first time, the operating system will automatically assign a temporary, unused port number (an ephemeral port) to its socket. This is sufficient because the server will learn this temporary address via recvfrom().

  3. sendto(): This is the client's primary action.

    struct sockaddr_in servaddr;
    // ... code to fill servaddr with the server's IP and port ...
    
    char *message = "Hello, Server!";
    sendto(sockfd, message, strlen(message), 0, (struct sockaddr *) &servaddr, sizeof(servaddr));
    

    The client must know the server's address ahead of time and populate the servaddr struct accordingly. It then calls sendto() to fire off the datagram.

  4. recvfrom(): After sending its request, the client usually wants a reply.

    char buffer[1024];
    int n = recvfrom(sockfd, buffer, sizeof(buffer), 0, NULL, NULL);
    

    The client calls recvfrom() on its own socket to wait for the server's response. Notice that the last two arguments can be NULL if the client doesn't care about re-confirming which address the reply came from (it assumes it's from the server it just contacted).

This flow perfectly mirrors the "postcard" analogy: create a message, address it to the server, send it, and then wait for a postcard in return.


Conclusion

You have now surveyed the landscape of UDP socket programming. By contrasting it with your existing knowledge of TCP, the unique, connectionless nature of UDP becomes much clearer.

Key Takeaways:

  • UDP is connectionless: There is no connect(), listen(), or accept() to establish a session.
  • Sockets are SOCK_DGRAM: This type is specified in the socket() call to use the UDP protocol.
  • Communication uses sendto() and recvfrom(): These functions are essential because address information for the destination (in sendto) or source (in recvfrom) must be handled with every packet.
  • Server bind() is mandatory: A UDP server must bind() to a well-known port to be discoverable by clients.
  • Client bind() is optional: A UDP client typically lets the OS assign an ephemeral port on the first sendto() call.

Preview of the Next Lesson:

Theory is one thing, but practice is another. Now that you have a solid conceptual map of the UDP API, you're ready for the next step. In our next lesson, you will implement a simple UDP echo client-server program in C. You'll write the code for the functions we've discussed today and see your own UDP application come to life.