UDP(User Datagram Protocol)是傳輸層**無連接、不可靠、高效能**的協議。本文從協議原理、Java API 實戰、TCP 對比、高併發架構模式四個維度,系統闡述 Java UDP 程式設計精髓。
0 7 8 15 16 23 24 31 +--------+--------+--------+--------+ | Source Port | Destination Port | +--------+--------+--------+--------+ | Length | Checksum | +--------+--------+--------+--------+
| 特性 | TCP | UDP |
|---|---|---|
| 連接模式 | 面向連接(三次握手) | 無連接 |
| 可靠性 | 序號、確認、重傳、流控、擁塞控制 | 不可靠,不保證順序、不重傳 |
| 傳輸單位 | 位元組流 | 資料報 |
| 開銷 | 頭部 20-60 位元組 | 頭部 8 位元組 |
| 速度 | 較慢 | 極快 |
| 適用場景 | 檔案傳輸、HTTP、郵件、資料庫 | 視訊會議、即時遊戲、DNS、SNMP、廣播 |
// 客戶端:系統自動分配埠
DatagramSocket socket = new DatagramSocket();
// 伺服器:綁定固定埠
DatagramSocket serverSocket = new DatagramSocket(9999);
// 多網卡綁定特定 IP
DatagramSocket socket = new DatagramSocket(9999, InetAddress.getByName("192.168.1.100"));
// 關鍵配置
socket.setSoTimeout(5000); // 接收超時(毫秒)
socket.setSendBufferSize(64 * 1024); // 發送緩衝區
socket.setReceiveBufferSize(64 * 1024); // 接收緩衝區
socket.setBroadcast(true); // 允許廣播
socket.setReuseAddress(true); // 端口複用
// 發送封包
byte[] data = "Hello UDP".getBytes(StandardCharsets.UTF_8);
InetAddress addr = InetAddress.getByName("192.168.1.100");
DatagramPacket packet = new DatagramPacket(data, data.length, addr, 9999);
socket.send(packet);
// 接收封包
byte[] buffer = new byte[65535]; // 最大 UDP 封包
DatagramPacket receivePacket = new DatagramPacket(buffer, buffer.length);
socket.receive(receivePacket); // 阻塞等待
// 解析接收資料
String msg = new String(receivePacket.getData(), 0, receivePacket.getLength(), StandardCharsets.UTF_8);
InetAddress senderIP = receivePacket.getAddress();
int senderPort = receivePacket.getPort();
InetAddress local = InetAddress.getLocalHost(); // 本機
InetAddress byName = InetAddress.getByName("www.google.com"); // 域名解析
InetAddress byAddr = InetAddress.getByAddress(new byte[]{(byte)192, (byte)168, 1, 1}); // IP 陣列
String ip = byName.getHostAddress(); // "142.250.190.46"
String host = byName.getHostName(); // "www.google.com"
public class UDPEchoServer {
public static void main(String[] args) throws IOException {
try (DatagramSocket socket = new DatagramSocket(9999)) {
byte[] buffer = new byte[1024];
System.out.println("UDP Echo Server started on port 9999");
while (true) {
DatagramPacket request = new DatagramPacket(buffer, buffer.length);
socket.receive(request); // 阻塞
String msg = new String(request.getData(), 0, request.getLength());
System.out.printf("Received from %s:%d -> %s%n",
request.getAddress(), request.getPort(), msg);
// 原路回送
DatagramPacket reply = new DatagramPacket(
request.getData(), request.getLength(),
request.getAddress(), request.getPort()
);
socket.send(reply);
}
}
}
}
public class UDPClient {
public static void main(String[] args) throws IOException {
try (DatagramSocket socket = new DatagramSocket()) {
socket.setSoTimeout(3000); // 3 秒超時
InetAddress server = InetAddress.getByName("localhost");
int port = 9999;
String[] messages = {"Hello", "World", "UDP", "Programming"};
for (String msg : messages) {
byte[] data = msg.getBytes(StandardCharsets.UTF_8);
DatagramPacket packet = new DatagramPacket(data, data.length, server, port);
socket.send(packet);
System.out.println("Sent: " + msg);
// 等待回應
byte[] buffer = new byte[1024];
DatagramPacket reply = new DatagramPacket(buffer, buffer.length);
try {
socket.receive(reply);
String response = new String(reply.getData(), 0, reply.getLength());
System.out.println("Reply: " + response);
} catch (SocketTimeoutException e) {
System.out.println("Timeout: no reply for " + msg);
}
}
}
}
}
public class UDPBroadcaster {
public static void main(String[] args) throws IOException {
try (DatagramSocket socket = new DatagramSocket()) {
socket.setBroadcast(true);
InetAddress broadcastAddr = InetAddress.getByName("255.255.255.255"); // 本地廣播
// 或特定子網廣播:192.168.1.255
String msg = "DISCOVER_SERVICE";
byte[] data = msg.getBytes();
DatagramPacket packet = new DatagramPacket(data, data.length, broadcastAddr, 8888);
for (int i = 0; i < 3; i++) {
socket.send(packet);
System.out.println("Broadcast sent: " + msg);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public class ThreadPerTaskUDPServer {
private final ExecutorService executor = Executors.newCachedThreadPool();
public void start(int port) throws IOException {
try (DatagramSocket socket = new DatagramSocket(port)) {
byte[] buffer = new byte[1024];
while (!Thread.interrupted()) {
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
socket.receive(packet); // 主執行緒接收
// 複製資料避免併發覆蓋
byte[] data = Arrays.copyOf(packet.getData(), packet.getLength());
InetAddress addr = packet.getAddress();
int port = packet.getPort();
executor.submit(() -> handleRequest(data, addr, port));
}
}
}
private void handleRequest(byte[] data, InetAddress addr, int port) {
// 業務處理、資料庫、外部 API 呼叫
// 回送響應需建立新 DatagramSocket 或共享發送 socket
}
}
Java NIO `DatagramChannel` 配合 `Selector` 實現:
public class NIOUDPServer {
public static void main(String[] args) throws IOException {
Selector selector = Selector.open();
DatagramChannel channel = DatagramChannel.open();
channel.configureBlocking(false);
channel.bind(new InetSocketAddress(9999));
channel.register(selector, SelectionKey.OP_READ);
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (true) {
selector.select();
for (SelectionKey key : selector.selectedKeys()) {
if (key.isReadable()) {
DatagramChannel ch = (DatagramChannel) key.channel();
buffer.clear();
SocketAddress client = ch.receive(buffer);
if (client != null) {
buffer.flip();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
// 處理業務...
// 非阻塞發送
ch.send(ByteBuffer.wrap("OK".getBytes()), client);
}
}
}
selector.selectedKeys().clear();
}
}
}
| 場景 | 選擇 | 理由 |
|---|---|---|
| 視訊/語音通話 | UDP + RTP/RTCP | 容忍丟包、追求低延遲 |
| 即時多人遊戲 | UDP + 可靠性層 | 狀態同步、位置更新、高頻小封包 |
| DNS 查詢 | UDP | 單請求單響應、短連接、極快 |
| 日誌收集 | UDP (syslog) | 火且忘、不阻塞業務、可容忍丟失 |
| IoT 設備上報 | UDP / CoAP | 低功耗、頻寬受限、簡單請求響應 |
| 檔案傳輸 | TCP | 必須可靠、有序、大數據流 |
UDP 以極簡的 8 位元組頭部,換取了最大傳輸效能與最低延遲。Java `DatagramSocket`/`DatagramChannel` 提供了完整的 UDP 程式設計能力。**掌握「無連接、不可靠、資料報」三大特性,在應用層自行實現可靠性(序號、ACK、重傳)、流控、分片重組**,是構建高效能 UDP 應用的核心功力。選型時務必根據業務對可靠性、延遲、順序的敏感度,在 TCP/UDP/QUIC 中做出理性決策。