UDP Protocol
Intermediate
4 of 5
What is UDP?
UDP (User Datagram Protocol) is the simpler of the two main transport protocols in TCP/IP. It sits on top of IP and adds only two things: port numbers (to identify which application gets the data) and a checksum (to detect errors in transit). Everything else that TCP provides — connection setup, reliability, ordering, flow control — UDP does not provide.
Like IP, UDP is connectionless: each datagram is sent independently, with no prior handshake and no guarantee of delivery, ordering, or duplicate avoidance. Despite this, UDP is preferred for many applications because of its low overhead and speed.
IP already handles routing packets across networks. UDP adds just two features on top of IP:
| IP Layer ✅ Routing across networks ✅ Best-effort delivery ❌ No port numbers ❌ No error detection |
+ | UDP Adds ✅ Port numbers ✅ Checksum (error detection) ❌ No connection setup ❌ No delivery guarantee |
= | UDP / IP Fast, low-overhead Good for: DNS, games, video streaming, VoIP |
Because UDP does so little, it is very fast and has very low latency. There is no connection to set up, no acknowledgments to wait for, and no retransmission of lost data. If reliability is needed, it must be implemented in the application layer.
The UDP header is only 8 bytes long (very small compared to TCP’s 20+ byte header):
| Source Port (16 bits) |
Destination Port (16 bits) |
||
| Length (16 bits — header + data) |
Checksum (16 bits) |
||
| Data (payload) — variable length | |||
| Total UDP header = 8 bytes. Minimum UDP payload = 0 bytes. | |||
The checksum covers the UDP header and data. It detects corruption during transit. However, checksums are not infallible — busy internet servers see roughly one undetected transmission error every few days. Applications needing stronger guarantees should use SSL/TLS or implement their own error control.
Every network link has a Maximum Transmission Unit (MTU) — the largest packet it can carry without splitting. Common Ethernet MTU is 1500 bytes. If a UDP datagram (including IP header) exceeds the MTU, the IP layer fragments it into multiple smaller packets.
| Large UDP Datagram 3000 bytes total (exceeds MTU of 1500) |
→ | IP splits into 2 fragments: Fragment 1: 1500 bytes Fragment 2: 1500+ bytes If any fragment lost → whole datagram lost |
Unlike TCP, UDP does not have mechanisms to avoid fragmentation. The problem with fragmentation is that if even one fragment is lost, the entire original datagram is lost — and UDP does not retransmit.
Safe UDP Datagram Size
To be safe and avoid IP fragmentation, UDP-based applications keep datagrams under the IPv4 minimum reassembly buffer size of 576 bytes:
| IP Header 20 bytes |
UDP Header 8 bytes |
UDP Payload (your data) 548 bytes available (Many apps use only 512 bytes to be safe) |
| Total: 576 bytes = IPv4 minimum reassembly buffer (guaranteed to work across all routers) | ||
In practice, many UDP-based applications (like DNS) use a still more conservative limit of 512 bytes for the data portion to ensure no fragmentation occurs on any path.
UDP uses SOCK_DGRAM sockets with sendto() and recvfrom() instead of connect()/send()/recv():
UDP Echo Server
/* udp_echo_server.c */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define PORT 9000
#define BUFSIZE 512 /* Stay well under 576-byte safety limit */
int main() {
int sockfd;
char buf[BUFSIZE];
struct sockaddr_in server_addr, client_addr;
socklen_t client_len = sizeof(client_addr);
ssize_t n;
/* SOCK_DGRAM = UDP socket */
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd == -1) { perror("socket"); return 1; }
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(sockfd, (struct sockaddr *)&server_addr,
sizeof(server_addr)) == -1) {
perror("bind"); return 1;
}
printf("UDP echo server ready on port %d\n", PORT);
for (;;) {
/*
* recvfrom() blocks until a datagram arrives.
* client_addr is filled with the sender's address.
* No connection is needed — each datagram is independent.
*/
n = recvfrom(sockfd, buf, BUFSIZE, 0,
(struct sockaddr *)&client_addr, &client_len);
if (n == -1) { perror("recvfrom"); continue; }
printf("Received %zd bytes from %s:%d\n",
n,
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port));
/* Echo the datagram back to the sender */
sendto(sockfd, buf, n, 0,
(struct sockaddr *)&client_addr, client_len);
}
close(sockfd);
return 0;
}
UDP Echo Client
/* udp_echo_client.c */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define SERVER_IP "127.0.0.1"
#define PORT 9000
#define BUFSIZE 512
int main() {
int sockfd;
char send_buf[] = "Hello, UDP Server!";
char recv_buf[BUFSIZE];
struct sockaddr_in server_addr;
socklen_t server_len = sizeof(server_addr);
ssize_t n;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd == -1) { perror("socket"); return 1; }
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr);
/*
* No connect() needed for UDP.
* sendto() specifies the destination each time.
* The OS auto-assigns an ephemeral source port.
*/
n = sendto(sockfd, send_buf, strlen(send_buf), 0,
(struct sockaddr *)&server_addr, server_len);
printf("Sent %zd bytes\n", n);
/*
* recvfrom() waits for any reply.
* CAUTION: No guarantee the reply is from our server —
* application must validate sender's address if security matters.
*/
n = recvfrom(sockfd, recv_buf, BUFSIZE - 1, 0,
(struct sockaddr *)&server_addr, &server_len);
if (n > 0) {
recv_buf[n] = '\0';
printf("Echo received: %s\n", recv_buf);
}
close(sockfd);
return 0;
}
Key differences from TCP: No listen(), no accept(), no connect() (though connect() can be used with UDP to set a default destination). Each sendto() is a self-contained datagram.
| Scenario | Why UDP Works Better | Real-World Examples |
|---|---|---|
| Low latency matters more than reliability | No handshake delay; no retransmit wait | Online games, VoIP, live video streaming |
| Small, frequent queries | No connection overhead; fits in one datagram | DNS lookups, SNMP monitoring |
| Broadcast or multicast needed | UDP supports one-to-many; TCP is one-to-one | mDNS, DHCP, network discovery |
| App handles its own reliability | Custom retransmit logic more efficient than TCP | QUIC protocol, game state sync |
connect(), listen(), or accept() involved. Each datagram is sent independently, possibly taking different network paths. The sender doesn’t wait for the receiver to be ready, and does not know if the datagram arrived. This makes UDP faster but unreliable.
sendto(sockfd, buf, len, flags, dest_addr, addrlen) — you specify the destination address on every call because there is no persistent connection. For receiving: recvfrom(sockfd, buf, len, flags, src_addr, addrlen) — it fills in the sender’s address so you can reply. Alternative: call connect() on a UDP socket to set a fixed peer, then use regular send()/recv().
connect() on a UDP socket does not initiate a handshake (there is none in UDP). Instead, it records the remote address as the default destination. After connect(), you can use send()/recv() instead of sendto()/recvfrom(). It also causes the kernel to filter incoming datagrams — only those from the connected address are delivered. This is sometimes called a “connected UDP socket.”
Continue Learning
Next: TCP — reliable, connection-oriented, stream-based communication with endpoints and buffers.
