What is UDP?
UDP (User Datagram Protocol) is a connectionless, lightweight transport layer protocol that sends data packets (datagrams) without establishing a connection or guaranteeing delivery. Unlike TCP’s reliable, ordered delivery, UDP prioritizes speed and low latency, making it ideal for real-time applications where occasional packet loss is acceptable.
UDP vs TCP
Protocol Comparison
interface TransportProtocolComparison {
udp: {
connection: 'Connectionless';
reliability: 'Best-effort (no guarantees)';
ordering: 'No guaranteed order';
overhead: 'Minimal (8-byte header)';
speed: 'Very fast';
flowControl: 'None';
congestionControl: 'None';
useCases: ['Gaming', 'Live streaming', 'DNS', 'VoIP', 'IoT'];
advantages: ['Low latency', 'Fast', 'Simple', 'No handshake delay'];
disadvantages: ['No reliability', 'Packet loss', 'No ordering', 'No congestion control'];
};
tcp: {
connection: 'Connection-oriented';
reliability: 'Guaranteed delivery';
ordering: 'Sequential ordering';
overhead: 'Higher (20+ byte header)';
speed: 'Slower (overhead for reliability)';
flowControl: 'Yes';
congestionControl: 'Yes';
useCases: ['HTTP', 'File transfer', 'Email', 'Database connections'];
advantages: ['Reliable', 'Ordered', 'Error checking', 'Flow control'];
disadvantages: ['Slower', 'Higher overhead', 'Connection setup time'];
};
}
UDP Packet Structure
Datagram Format
UDP Datagram Structure (8 bytes header):
0 15 16 31
+-----------------------+-----------------------+
| Source Port | Destination Port |
+-----------------------+-----------------------+
| Length | Checksum |
+-----------------------+-----------------------+
| |
| Data (Payload) |
| |
+-----------------------------------------------+
Fields:
- Source Port: 16 bits (optional, can be 0)
- Destination Port: 16 bits
- Length: 16 bits (header + data)
- Checksum: 16 bits (optional in IPv4, mandatory in IPv6)
UDP in Node.js
Basic UDP Server
import dgram from 'dgram';
class UDPServer {
private socket: dgram.Socket;
private port: number;
constructor(port: number) {
this.port = port;
this.socket = dgram.createSocket('udp4');
}
start() {
this.socket.on('message', (msg, rinfo) => {
console.log(`Received from ${rinfo.address}:${rinfo.port}: ${msg.toString()}`);
// Echo back to client
this.socket.send(msg, rinfo.port, rinfo.address, (err) => {
if (err) {
console.error('Send error:', err);
}
});
});
this.socket.on('listening', () => {
const address = this.socket.address();
console.log(`UDP server listening on ${address.address}:${address.port}`);
});
this.socket.on('error', (err) => {
console.error('Server error:', err);
this.socket.close();
});
this.socket.bind(this.port);
}
send(message: string, port: number, address: string) {
const buffer = Buffer.from(message);
this.socket.send(buffer, port, address, (err) => {
if (err) {
console.error('Send error:', err);
} else {
console.log(`Sent to ${address}:${port}: ${message}`);
}
});
}
close() {
this.socket.close();
}
}
// Usage
const server = new UDPServer(41234);
server.start();
UDP Client
import dgram from 'dgram';
class UDPClient {
private socket: dgram.Socket;
constructor() {
this.socket = dgram.createSocket('udp4');
this.socket.on('message', (msg, rinfo) => {
console.log(`Response from ${rinfo.address}:${rinfo.port}: ${msg.toString()}`);
});
this.socket.on('error', (err) => {
console.error('Client error:', err);
this.socket.close();
});
}
send(message: string, port: number, address: string): Promise<void> {
return new Promise((resolve, reject) => {
const buffer = Buffer.from(message);
this.socket.send(buffer, port, address, (err) => {
if (err) {
reject(err);
} else {
console.log(`Sent: ${message}`);
resolve();
}
});
});
}
async sendWithResponse(
message: string,
port: number,
address: string,
timeout: number = 5000
): Promise<string> {
return new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => {
this.socket.removeAllListeners('message');
reject(new Error('Response timeout'));
}, timeout);
this.socket.once('message', (msg) => {
clearTimeout(timeoutId);
resolve(msg.toString());
});
this.send(message, port, address).catch(reject);
});
}
close() {
this.socket.close();
}
}
// Usage
const client = new UDPClient();
try {
const response = await client.sendWithResponse(
'Hello UDP Server',
41234,
'localhost'
);
console.log('Response:', response);
} finally {
client.close();
}
UDP Use Cases
DNS Queries (UDP Port 53)
import dgram from 'dgram';
import { Buffer } from 'buffer';
class SimpleDNSClient {
private socket: dgram.Socket;
constructor() {
this.socket = dgram.createSocket('udp4');
}
async query(domain: string, dnsServer: string = '8.8.8.8'): Promise<string[]> {
// Build DNS query packet
const query = this.buildDNSQuery(domain);
// Send query to DNS server (port 53)
this.socket.send(query, 53, dnsServer);
// Wait for response
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('DNS query timeout'));
}, 5000);
this.socket.once('message', (response) => {
clearTimeout(timeout);
try {
const addresses = this.parseDNSResponse(response);
resolve(addresses);
} catch (error) {
reject(error);
}
});
});
}
private buildDNSQuery(domain: string): Buffer {
// Simplified DNS query builder
// Real implementation would be more complex
const query: number[] = [];
// Header (12 bytes)
query.push(0x12, 0x34); // Transaction ID
query.push(0x01, 0x00); // Flags (standard query)
query.push(0x00, 0x01); // Questions: 1
query.push(0x00, 0x00); // Answer RRs: 0
query.push(0x00, 0x00); // Authority RRs: 0
query.push(0x00, 0x00); // Additional RRs: 0
// Question
const labels = domain.split('.');
for (const label of labels) {
query.push(label.length);
for (let i = 0; i < label.length; i++) {
query.push(label.charCodeAt(i));
}
}
query.push(0x00); // End of domain name
query.push(0x00, 0x01); // Type: A (IPv4)
query.push(0x00, 0x01); // Class: IN (Internet)
return Buffer.from(query);
}
private parseDNSResponse(response: Buffer): string[] {
// Simplified DNS response parser
// Real implementation would be more complex
const addresses: string[] = [];
// Skip header and question sections
// Parse answer section for A records
// Example: extract IPv4 addresses
// This is a placeholder - real DNS parsing is complex
return addresses;
}
close() {
this.socket.close();
}
}
// Usage
const dnsClient = new SimpleDNSClient();
const addresses = await dnsClient.query('example.com');
console.log('IP addresses:', addresses);
dnsClient.close();
Real-Time Gaming
interface GamePacket {
playerId: string;
timestamp: number;
position: { x: number; y: number; z: number };
action: string;
}
class GameServer {
private socket: dgram.Socket;
private players: Map<string, {
address: string;
port: number;
lastUpdate: number;
position: any;
}>;
constructor(port: number) {
this.socket = dgram.createSocket('udp4');
this.players = new Map();
this.socket.bind(port);
this.socket.on('message', (msg, rinfo) => {
try {
const packet: GamePacket = JSON.parse(msg.toString());
this.handleGamePacket(packet, rinfo);
} catch (error) {
console.error('Invalid packet:', error);
}
});
// Remove inactive players
setInterval(() => {
this.cleanupInactivePlayers();
}, 10000);
}
private handleGamePacket(packet: GamePacket, rinfo: dgram.RemoteInfo) {
const { playerId, position, action } = packet;
// Update player state
this.players.set(playerId, {
address: rinfo.address,
port: rinfo.port,
lastUpdate: Date.now(),
position
});
// Broadcast to other players
this.broadcastState(playerId);
}
private broadcastState(excludePlayer?: string) {
const state = {
timestamp: Date.now(),
players: Array.from(this.players.entries())
.filter(([id]) => id !== excludePlayer)
.map(([id, data]) => ({
id,
position: data.position
}))
};
const message = Buffer.from(JSON.stringify(state));
// Send to all players
for (const [playerId, player] of this.players.entries()) {
if (playerId !== excludePlayer) {
this.socket.send(message, player.port, player.address);
}
}
}
private cleanupInactivePlayers() {
const now = Date.now();
const timeout = 30000; // 30 seconds
for (const [playerId, player] of this.players.entries()) {
if (now - player.lastUpdate > timeout) {
console.log(`Removing inactive player: ${playerId}`);
this.players.delete(playerId);
}
}
}
}
// Game client
class GameClient {
private socket: dgram.Socket;
private playerId: string;
private serverAddress: string;
private serverPort: number;
constructor(playerId: string, serverAddress: string, serverPort: number) {
this.socket = dgram.createSocket('udp4');
this.playerId = playerId;
this.serverAddress = serverAddress;
this.serverPort = serverPort;
this.socket.on('message', (msg) => {
const state = JSON.parse(msg.toString());
this.updateGameState(state);
});
}
sendPosition(x: number, y: number, z: number, action: string) {
const packet: GamePacket = {
playerId: this.playerId,
timestamp: Date.now(),
position: { x, y, z },
action
};
const message = Buffer.from(JSON.stringify(packet));
this.socket.send(message, this.serverPort, this.serverAddress);
}
private updateGameState(state: any) {
// Update local game state with other players' positions
console.log('Game state update:', state);
}
}
VoIP (Voice over IP)
class VoIPConnection {
private socket: dgram.Socket;
private remoteAddress: string;
private remotePort: number;
constructor(remoteAddress: string, remotePort: number) {
this.socket = dgram.createSocket('udp4');
this.remoteAddress = remoteAddress;
this.remotePort = remotePort;
this.socket.on('message', (msg) => {
this.handleAudioPacket(msg);
});
}
sendAudioChunk(audioData: Buffer) {
// Add RTP header (simplified)
const rtpPacket = this.encodeRTP(audioData);
// Send immediately - no waiting for ACK
this.socket.send(rtpPacket, this.remotePort, this.remoteAddress);
}
private encodeRTP(audioData: Buffer): Buffer {
// RTP (Real-time Transport Protocol) header
const header = Buffer.alloc(12);
header.writeUInt8(0x80, 0); // Version 2, no padding, no extension
header.writeUInt8(0x00, 1); // Payload type
header.writeUInt16BE(0, 2); // Sequence number
header.writeUInt32BE(Date.now(), 4); // Timestamp
header.writeUInt32BE(0, 8); // SSRC identifier
return Buffer.concat([header, audioData]);
}
private handleAudioPacket(packet: Buffer) {
// Parse RTP header and extract audio data
const audioData = packet.slice(12); // Skip 12-byte RTP header
// Play audio
this.playAudio(audioData);
}
private playAudio(audioData: Buffer) {
// Send to audio output device
console.log(`Playing audio chunk: ${audioData.length} bytes`);
}
}
UDP with SOCKS5 Proxy
SOCKS5 UDP Association
class SOCKS5UDPClient {
private controlSocket: any; // TCP control connection
private udpSocket: dgram.Socket;
private relayAddress: string = '';
private relayPort: number = 0;
async connect(
proxyHost: string,
proxyPort: number,
username?: string,
password?: string
): Promise<void> {
// 1. Establish TCP control connection
// 2. Authenticate
// 3. Send UDP ASSOCIATE command
// 4. Receive relay address/port
console.log('UDP relay configured:', this.relayAddress, this.relayPort);
// Create UDP socket for actual data
this.udpSocket = dgram.createSocket('udp4');
this.udpSocket.on('message', (msg, rinfo) => {
// Parse SOCKS5 UDP packet
this.handleUDPResponse(msg);
});
}
sendUDPPacket(data: Buffer, targetHost: string, targetPort: number) {
// Encapsulate in SOCKS5 UDP format
const socks5Packet = this.encapsulateUDP(data, targetHost, targetPort);
// Send to SOCKS5 relay
this.udpSocket.send(socks5Packet, this.relayPort, this.relayAddress);
}
private encapsulateUDP(data: Buffer, host: string, port: number): Buffer {
// SOCKS5 UDP packet format:
// +----+------+------+----------+----------+----------+
// |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA |
// +----+------+------+----------+----------+----------+
const header = Buffer.alloc(10);
header.writeUInt16BE(0, 0); // Reserved
header.writeUInt8(0, 2); // Fragment number
header.writeUInt8(0x03, 3); // ATYP: Domain name
header.writeUInt8(host.length, 4); // Domain length
const hostBuffer = Buffer.from(host);
const portBuffer = Buffer.alloc(2);
portBuffer.writeUInt16BE(port, 0);
return Buffer.concat([
header.slice(0, 5),
hostBuffer,
portBuffer,
data
]);
}
private handleUDPResponse(packet: Buffer) {
// Parse SOCKS5 UDP response
// Extract actual data
const data = packet.slice(10); // Skip SOCKS5 header
console.log('Received UDP data:', data);
}
}
Best Practices
For Application Developers
interface UDPBestPractices {
packetSize: {
recommendation: 'Keep packets under 1472 bytes';
reason: 'Avoid fragmentation (MTU - IP header - UDP header)';
implementation: 'Chunk large data';
};
retransmission: {
recommendation: 'Implement application-level retries';
reason: 'UDP does not guarantee delivery';
implementation: 'Sequence numbers and ACKs';
};
ordering: {
recommendation: 'Add sequence numbers to packets';
reason: 'UDP does not guarantee order';
implementation: 'Reorder at application layer';
};
errorDetection: {
recommendation: 'Implement additional checksums';
reason: 'UDP checksum is optional in IPv4';
implementation: 'CRC or application-level checksum';
};
congestionControl: {
recommendation: 'Implement rate limiting';
reason: 'UDP has no built-in congestion control';
implementation: 'Token bucket or leaky bucket algorithm';
};
}
Reliability Layer on UDP
class ReliableUDP {
private socket: dgram.Socket;
private pendingACKs: Map<number, {
data: Buffer;
timestamp: number;
retries: number;
}>;
private sequenceNumber: number = 0;
constructor() {
this.socket = dgram.createSocket('udp4');
this.pendingACKs = new Map();
this.socket.on('message', (msg) => {
this.handleMessage(msg);
});
// Retransmission timer
setInterval(() => {
this.retransmitLostPackets();
}, 100);
}
async send(data: Buffer, port: number, address: string): Promise<void> {
const seq = this.sequenceNumber++;
// Add header with sequence number
const packet = Buffer.alloc(4 + data.length);
packet.writeUInt32BE(seq, 0);
data.copy(packet, 4);
// Store for potential retransmission
this.pendingACKs.set(seq, {
data: packet,
timestamp: Date.now(),
retries: 0
});
// Send packet
this.socket.send(packet, port, address);
}
private handleMessage(msg: Buffer) {
// Check if this is an ACK
if (msg.length === 4) {
const ackSeq = msg.readUInt32BE(0);
this.pendingACKs.delete(ackSeq);
console.log(`ACK received for packet ${ackSeq}`);
} else {
// Data packet - send ACK
const seq = msg.readUInt32BE(0);
this.sendACK(seq);
// Process data
const data = msg.slice(4);
this.handleData(data);
}
}
private sendACK(seq: number) {
const ack = Buffer.alloc(4);
ack.writeUInt32BE(seq, 0);
// Send ACK to sender
}
private retransmitLostPackets() {
const now = Date.now();
const timeout = 1000; // 1 second
for (const [seq, packet] of this.pendingACKs.entries()) {
if (now - packet.timestamp > timeout) {
if (packet.retries < 3) {
// Retransmit
console.log(`Retransmitting packet ${seq}`);
packet.retries++;
packet.timestamp = now;
// Re-send packet
} else {
// Give up
console.error(`Packet ${seq} lost after 3 retries`);
this.pendingACKs.delete(seq);
}
}
}
}
private handleData(data: Buffer) {
console.log('Received data:', data);
}
}
When to Use UDP
interface UDPUseCases {
perfectFor: {
realTimeGaming: 'Low latency more important than reliability';
liveStreaming: 'Can tolerate some packet loss';
voipVideo: 'Real-time communication';
dns: 'Single request-response, can retry';
iot: 'Low overhead, simple messages';
broadcasting: 'One-to-many communication';
};
notGoodFor: {
fileTransfer: 'Need reliability and ordering';
webPages: 'Need guaranteed delivery';
emails: 'Cannot lose data';
financial: 'Reliability critical';
database: 'Data integrity essential';
};
}