UDP โ The Speed Demon of Transport Protocols
UDP (User Datagram Protocol) is one of the two main transport-layer protocols. It wraps your data in a very thin envelope and sends it immediately โ no setup, no waiting for acknowledgments.
Think of UDP like sending a postcard โ you write it, drop it in the box, and move on. You do not know if it arrived. You do not wait. That is perfect when speed matters more than guaranteed delivery, like video calls, online gaming, or DNS lookups.
UDP’s entire header is just 8 bytes. Compare that to TCP’s 20+ byte header. This simplicity is exactly why UDP is fast โ minimal overhead, maximum speed.
| Source Port (16 bits) | Destination Port (16 bits) |
|---|---|
| Length (16 bits) header + data in bytes |
Checksum (16 bits) error detection (optional in IPv4) |
| Data (variable length) | |
โข Source Port โ which port the sender is using (optional for UDP โ can be 0 if no reply needed)
โข Destination Port โ which port on the receiver should get this
โข Length โ total size of UDP header + data (minimum 8 bytes = header only)
โข Checksum โ basic error detection (mandatory in IPv6, optional in IPv4)
UDP just sends. No 3-way handshake. No “are you there?” check. First packet goes immediately. Great when you have many short transactions (like DNS: 1 question, 1 answer).
Packet can be silently dropped. No retransmit. No ACK. Application must handle lost packets itself if it cares.
Packets may arrive out of order. UDP delivers them as they come. Application must reorder if needed (e.g., RTP for video streaming adds its own sequence numbers).
Each sendto() produces exactly one datagram and one recvfrom(). Unlike TCP (a byte stream), UDP preserves message boundaries. If you send 100 bytes, you receive exactly 100 bytes in one call.
UDP can send one packet to all hosts on a subnet (broadcast) or to a group of interested hosts (multicast). TCP cannot do this (it is point-to-point only).
IP handles host-to-host delivery. UDP adds just two things on top of raw IP:
Port Numbers
IP tells packets which machine to go to.
Ports tell the machine which process to hand it to.
Port 53 โ DNS, Port 67 โ DHCP, Port 123 โ NTP
Checksum
Detects bit errors in the datagram (header + data).
If checksum fails, the datagram is silently discarded. No error is sent to the application.
That’s it! UDP is literally just “IP + port numbers + optional checksum.” Very thin layer.
| Application | Why UDP? | Protocol |
|---|---|---|
| DNS Lookup | 1 question โ 1 answer. No need for connection setup overhead. | Port 53 |
| Video Streaming | Slight frame loss is OK. Latency matters more than perfection. | RTP over UDP |
| Online Gaming | Game state must arrive ASAP. Old state is useless anyway. | Custom UDP |
| DHCP | Client has no IP yet โ must use broadcast. TCP is unicast only. | Port 67/68 |
| SNMP (Network Monitoring) | Simple polls. Lightweight. Overhead of TCP not worth it. | Port 161 |
| QUIC (HTTP/3) | Modern protocol builds reliability on top of UDP for better control. | QUIC over UDP |
A complete UDP server that echoes back whatever you send:
/* ============ udp_server.c ============ */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#define PORT 9090
#define BUFLEN 1024
int main(void) {
int sockfd;
struct sockaddr_in srv_addr, cli_addr;
socklen_t cli_len;
char buf[BUFLEN];
ssize_t n;
/* 1. Create UDP socket */
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) { perror("socket"); exit(1); }
/* 2. Bind to port */
memset(&srv_addr, 0, sizeof(srv_addr));
srv_addr.sin_family = AF_INET;
srv_addr.sin_port = htons(PORT);
srv_addr.sin_addr.s_addr = INADDR_ANY; /* listen on all interfaces */
if (bind(sockfd, (struct sockaddr *)&srv_addr, sizeof(srv_addr)) < 0) {
perror("bind"); exit(1);
}
printf("UDP server listening on port %d...\n", PORT);
/* 3. Receive loop โ no accept() needed for UDP! */
for (;;) {
cli_len = sizeof(cli_addr);
n = recvfrom(sockfd, buf, BUFLEN - 1, 0,
(struct sockaddr *)&cli_addr, &cli_len);
if (n < 0) { perror("recvfrom"); continue; }
buf[n] = '\0';
char cli_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &cli_addr.sin_addr, cli_ip, sizeof(cli_ip));
printf("Received from %s:%d โ \"%s\"\n",
cli_ip, ntohs(cli_addr.sin_port), buf);
/* 4. Echo back to sender */
if (sendto(sockfd, buf, n, 0,
(struct sockaddr *)&cli_addr, cli_len) < 0) {
perror("sendto");
}
}
close(sockfd);
return 0;
}
/* ============ udp_client.c ============ */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#define SERVER_IP "127.0.0.1"
#define PORT 9090
#define BUFLEN 1024
int main(void) {
int sockfd;
struct sockaddr_in srv_addr;
char buf[BUFLEN];
ssize_t n;
/* 1. Create UDP socket */
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) { perror("socket"); exit(1); }
memset(&srv_addr, 0, sizeof(srv_addr));
srv_addr.sin_family = AF_INET;
srv_addr.sin_port = htons(PORT);
inet_pton(AF_INET, SERVER_IP, &srv_addr.sin_addr);
/* 2. Send a message โ no connect() needed! */
const char *msg = "Hello UDP Server!";
if (sendto(sockfd, msg, strlen(msg), 0,
(struct sockaddr *)&srv_addr, sizeof(srv_addr)) < 0) {
perror("sendto"); exit(1);
}
/* 3. Receive echo */
n = recvfrom(sockfd, buf, BUFLEN - 1, 0, NULL, NULL);
if (n < 0) { perror("recvfrom"); exit(1); }
buf[n] = '\0';
printf("Echo received: \"%s\"\n", buf);
close(sockfd);
return 0;
}
/* Build and run:
gcc -o udp_server udp_server.c && ./udp_server &
gcc -o udp_client udp_client.c && ./udp_client
Server output: Received from 127.0.0.1:XXXXX โ "Hello UDP Server!"
Client output: Echo received: "Hello UDP Server!"
*/
โข Use
SOCK_DGRAM instead of SOCK_STREAMโข Server does NOT call
listen() or accept()โข Use
sendto() / recvfrom() instead of send() / recv()โข Each
sendto() is one independent datagramโข No connection means any client can send to the server at any time
A: UDP adds two things to IP: (1) Port numbers โ allowing multiple processes on the same machine to send/receive independently. (2) An optional checksum for error detection of the datagram. That’s all. UDP is essentially a thin wrapper around IP.
A: Several reasons: (1) Speed โ no connection setup, no ACK waiting, lower latency. (2) Real-time โ for video/audio streaming, old data is useless; better to drop than delay. (3) Broadcast/multicast โ TCP is unicast only; UDP can send to multiple receivers at once. (4) Less overhead โ 8-byte header vs TCP’s 20+ bytes. (5) Application-controlled reliability โ application can implement exactly the reliability it needs (e.g., QUIC does this on top of UDP).
A: SOCK_DGRAM creates a UDP socket โ connectionless, unreliable, message-boundary-preserving, uses sendto/recvfrom. SOCK_STREAM creates a TCP socket โ connection-oriented, reliable, ordered, byte-stream (no message boundaries), uses send/recv.
A: Yes. This is a key difference from TCP. Each sendto() produces exactly one datagram, and each recvfrom() reads exactly one datagram. If you send 100 bytes and 200 bytes in two calls, the receiver gets two separate recvfrom() returns of 100 and 200 bytes โ never merged together. TCP, being a byte stream, does NOT preserve message boundaries.
A: Yes. Calling connect() on a UDP socket does NOT create a real connection. It just records the peer address so you can use send()/recv() instead of sendto()/recvfrom(). It also causes the kernel to only receive datagrams from that peer address (filters others). The socket remains connectionless โ no handshake occurs. You can call connect() multiple times to change the peer or use AF_UNSPEC to disconnect.
A: The theoretical maximum is 65,507 bytes (65,535 bytes max UDP datagram size minus 8 bytes UDP header minus 20 bytes IP header). However, in practice, datagrams larger than the path MTU (usually ~1472 bytes for Ethernet after headers) will be fragmented at the IP layer, which is inefficient. Applications that care about performance keep UDP payloads under ~1472 bytes.
