UDP (User Datagram Protocol) 是無連線、不可靠、無序的傳輸層協定。雖缺乏 TCP 的可靠性保證,但其低延遲、低開銷、支援廣播/多播的特性,使其成為即時音視訊 (RTP)、線上遊戲狀態同步、DNS 查詢、SNMP 監控、IoT 感測器資料上報的首選。Java 在 java.net 提供 DatagramSocket、DatagramPacket、InetAddress 三大核心類,操作 UDP 極為便利。
| 類別 | 關鍵構造器/方法 | 用途 |
|---|---|---|
InetAddress | getByName(host), getLocalHost(), getAllByName() | IP 位址解析與封裝 (支援域名/IPv4/IPv6) |
DatagramSocket | DatagramSocket() (客戶端臨時埠)DatagramSocket(port) (伺服端固定埠)DatagramSocket(port, InetAddress) (多網卡綁定)receive(packet) 阻塞接收send(packet) 發送setSoTimeout(ms) 設定接收逾時close() 釋放埠口 | UDP 端點,收發 DatagramPacket |
DatagramPacket | DatagramPacket(buf, length, addr, port) (發送)DatagramPacket(buf, length) (接收)getData() 取得資料陣列getLength() 實際長度getAddress()/getPort() 來源資訊setData(buf, offset, length) 重用緩衝區 | 資料報封裝:承載位元組陣列、目標/來源位址埠 |
receive() 會阻塞執行緒直到收到封包或逾時 (若設定 setSoTimeout),必須在獨立執行緒執行。DatagramSocket 若埠口被佔用會拋 SocketException,需捕捉處理。public class UdpEchoServer {
public static void main(String[] args) throws IOException {
try (DatagramSocket socket = new DatagramSocket(12345)) {
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
System.out.println("UDP Echo Server listening on 12345...");
while (true) {
socket.receive(packet); // 阻塞
String received = new String(packet.getData(), 0, packet.getLength(), StandardCharsets.UTF_8);
System.out.printf("From %s:%d -> %s%n", packet.getAddress(), packet.getPort(), received);
// 原樣回傳
socket.send(new DatagramPacket(packet.getData(), packet.getLength(), packet.getAddress(), packet.getPort()));
}
}
}
}
public class UdpEchoClient {
public static void main(String[] args) throws IOException {
if (args.length != 1) { System.err.println("Usage: java UdpEchoClient "); return; }
InetAddress serverAddr = InetAddress.getByName(args[0]);
try (DatagramSocket socket = new DatagramSocket()) { // 臨時埠
socket.setSoTimeout(3000); // 3 秒逾時
BufferedReader console = new BufferedReader(new InputStreamReader(System.in));
String line;
while ((line = console.readLine()) != null) {
if ("bye".equalsIgnoreCase(line)) break;
byte[] data = line.getBytes(StandardCharsets.UTF_8);
DatagramPacket sendPkt = new DatagramPacket(data, data.length, serverAddr, 12345);
socket.send(sendPkt);
// 等待回應
byte[] recvBuf = new byte[1024];
DatagramPacket recvPkt = new DatagramPacket(recvBuf, recvBuf.length);
try {
socket.receive(recvPkt);
String echo = new String(recvPkt.getData(), 0, recvPkt.getLength(), StandardCharsets.UTF_8);
System.out.println("Echo: " + echo);
} catch (SocketTimeoutException e) {
System.out.println("Request timeout (no response)");
}
}
}
}
}
DatagramChannel channel = DatagramChannel.open();
channel.bind(new InetSocketAddress(12345));
channel.configureBlocking(false);
Selector selector = Selector.open();
channel.register(selector, SelectionKey.OP_READ);
// 事件迴圈同 TCP NIO,可單執行緒處理大量 UDP 連線
適合高併發 UDP 服務 (如遊戲伺服器、日誌收集器)。
socket.setBroadcast(true); socket.send(packetToBroadcastAddr),目標 255.255.255.255 或子網廣播位址 (如 192.168.1.255)。MulticastSocket 加入群組 joinGroup(InetAddress.getByName("239.0.0.1")),發送至多播位址,群組成員皆可收到。適用股票行情推送、叢集發現。若業務需可靠傳輸,必須在 UDP 上建立輕量協定:
// 發送端:重用 Direct Buffer 減少 GC
ByteBuffer buffer = ByteBuffer.allocateDirect(1472);
while (sending) {
buffer.clear();
buffer.put(data);
buffer.flip();
DatagramPacket pkt = new DatagramPacket(buffer.array(), buffer.limit(), addr, port);
socket.send(pkt);
}
// 接收端:預分配陣列池
byte[] buf = new byte[1472];
DatagramPacket pkt = new DatagramPacket(buf, buf.length);
while (running) {
socket.receive(pkt);
process(pkt.getData(), pkt.getLength());
pkt.setLength(buf.length); // 重置長度供下次接收
}
| 現象 | 可能原因 | 解決方案 |
|---|---|---|
| 發送成功但對方收不到 | 防火牆攔截、NAT 無穿透、目標埠口錯誤、廣播未啟用 setBroadcast(true) | 檢查防火牆規則、使用 STUN/TURN、確認目標位址埠、啟用廣播旗標 |
| 資料截斷/亂碼 | 緩衝區過小、編碼不一致、單包超過 MTU 導致 IP 分片丟失 | 增大緩衝區 (建議 8KB)、統一 UTF-8、控制單包 ≤ 1472B |
receive() 永久阻塞 | 未設定 setSoTimeout、對方未回應 | 必須設定逾時或放入獨立執行緒並提供關閉機制 |
| 多網卡環境收發異常 | Socket 綁定錯誤介面 | 用 new DatagramSocket(port, InetAddress.getByName("192.168.x.x")) 綁定特定 IP |
Java UDP 程式設計核心在於:掌握 DatagramPacket 封裝/解封裝、理解無連線阻塞模型、處理封包大小與 MTU 關係、必要時引入 NIO 或應用層可靠協定。對於即時性要求高、可容忍少量丟包的場景,UDP 配合合理的應用層策略 (序號/重傳/FEC) 往往比 TCP 更優。建議從極簡 Echo 範例起步,逐步加入逾時重傳、心跳、多播等功能,構建穩健的 UDP 應用。