UDP (User Datagram Protocol) 是無連接、不可靠、面向數據報的傳輸層協議。特點:速度快、開銷小、無連接建立延遲,但不保證送達、不保證順序、不保證不重複。適用於即時通訊、視頻流、DNS 查詢、遊戲狀態同步等對實時性要求高、能容忍少量丟包的場景。
DatagramSocket:發送/接收數據報的端點,綁定本地端口。DatagramPacket:數據報封裝,包含數據緩衝區、長度、目標地址/端口 (發送時) 或 發送方地址/端口 (接收時)。
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
public class UdpEchoServer {
private static final int PORT = 9876;
private static final int BUFFER_SIZE = 1024;
public static void main(String[] args) {
try (DatagramSocket socket = new DatagramSocket(PORT)) {
System.out.println("UDP 回聲服務器已啟動,監聽端口: " + PORT);
byte[] buffer = new byte[BUFFER_SIZE];
while (true) {
// 1. 準備接收包
DatagramPacket receivePacket = new DatagramPacket(buffer, buffer.length);
// 2. 阻塞接收
socket.receive(receivePacket);
// 3. 解析數據
String received = new String(receivePacket.getData(), 0, receivePacket.getLength(), StandardCharsets.UTF_8);
InetAddress clientAddr = receivePacket.getAddress();
int clientPort = receivePacket.getPort();
System.out.printf("收到來自 %s:%d 的數據: %s%n", clientAddr.getHostAddress(), clientPort, received);
// 4. 構造回複包 (原樣返回)
byte[] responseData = ("Echo: " + received).getBytes(StandardCharsets.UTF_8);
DatagramPacket sendPacket = new DatagramPacket(
responseData, responseData.length, clientAddr, clientPort);
// 5. 發送
socket.send(sendPacket);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
public class UdpEchoClient {
private static final String SERVER_HOST = "localhost";
private static final int SERVER_PORT = 9876;
private static final int TIMEOUT_MS = 3000; // 超時時間 (UDP 無連接,必須設置超時避免永久阻塞)
public static void main(String[] args) {
try (DatagramSocket socket = new DatagramSocket()) {
// 關鍵: 設置接收超時,否則 receive() 會永久阻塞
socket.setSoTimeout(TIMEOUT_MS);
InetAddress serverAddr = InetAddress.getByName(SERVER_HOST);
Scanner scanner = new Scanner(System.in);
byte[] buffer = new byte[1024];
System.out.println("UDP 客戶端已啟動,輸入 'exit' 退出");
while (true) {
System.out.print("> ");
String input = scanner.nextLine();
if ("exit".equalsIgnoreCase(input)) break;
// 發送
byte[] sendData = input.getBytes(StandardCharsets.UTF_8);
DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, serverAddr, SERVER_PORT);
socket.send(sendPacket);
System.out.println("已發送: " + input);
// 接收回聲
try {
DatagramPacket receivePacket = new DatagramPacket(buffer, buffer.length);
socket.receive(receivePacket); // 可能拋出 SocketTimeoutException
String response = new String(receivePacket.getData(), 0, receivePacket.getLength(), StandardCharsets.UTF_8);
System.out.println("服務器回聲: " + response);
} catch (java.net.SocketTimeoutException e) {
System.out.println("等待回聲超時 (服務器未響應或丟包)");
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
| 要點 | 說明 |
|---|---|
| 端口綁定 | 服務端 new DatagramSocket(port) 綁定固定端口;客戶端 new DatagramSocket() 由系統分配臨時端口 |
| 數據報大小限制 | 理論最大 65,507 字節 (64KB - IP/UDP 頭部);實際建議 < 1500 字節 (MTU) 避免分片 |
| 超時設置 | 客戶端必須設置 socket.setSoTimeout(),否則 receive() 永久阻塞無法處理丟包 |
| 編碼統一 | 雙端統一使用 UTF-8 (StandardCharsets.UTF_8) 避免中文亂碼 |
| 異常處理 | 捕獲 SocketTimeoutException、PortUnreachableException (ICMP 錯誤)、SocketException |
// 發送端
InetAddress group = InetAddress.getByName("230.0.0.1"); // D類多播地址 224.0.0.0~239.255.255.255
MulticastSocket socket = new MulticastSocket();
socket.send(new DatagramPacket(data, data.length, group, 8888));
// 接收端
MulticastSocket socket = new MulticastSocket(8888);
socket.joinGroup(InetAddress.getByName("230.0.0.1"));
socket.receive(packet);
UDP 本身不可靠,若業務需可靠傳輸,需在應用層實現:
參考實現:Netflix UDP Reliability、QUIC 協議 (HTTP/3 底層)。
DatagramChannel channel = DatagramChannel.open();
channel.bind(new InetSocketAddress(9876));
channel.configureBlocking(false);
Selector selector = Selector.open();
channel.register(selector, SelectionKey.OP_READ);
while (selector.select() > 0) {
for (SelectionKey key : selector.selectedKeys()) {
if (key.isReadable()) {
ByteBuffer buf = ByteBuffer.allocate(1024);
SocketAddress sender = channel.receive(buf);
// 處理數據...
}
}
selector.selectedKeys().clear();
}
Java UDP 編程核心是 DatagramSocket + DatagramPacket。必須設置接收超時、處理丟包/亂序/重複、注意 MTU 限制。簡單請求-響應模式可直接使用,複雜可靠傳輸建議採用成熟庫 (Netty UDP、QUIC 實現) 或轉用 TCP。掌握 UDP 為理解 QUIC、WebRTC、遊戲網絡編程打下基礎。