| 方法 | 說明 |
receive(DatagramPacket p) | 阻塞接收封包,資料填入 p |
send(DatagramPacket p) | 發送封包至目標位址/埠口 |
setSoTimeout(int ms) | 設定接收超時 (避免永久阻塞) |
close() | 釋放埠口資源 |
異常處理:建構子可能拋出 SocketException (埠口衝突/權限不足),必須捕獲。
DatagramPacket:資料封包
發送用建構子:
DatagramPacket(byte[] buf, int length, InetAddress addr, int port)
DatagramPacket(byte[] buf, int offset, int length, InetAddress addr, int port)
接收用建構子:
DatagramPacket(byte[] buf, int length)
DatagramPacket(byte[] buf, int offset, int length)
重要限制:
- 理論最大 65,507 位元組 (IPv4),實務建議 ≤ 8KB 避免分片
getData() 取得原始位元組陣列
getLength()/getOffset() 取得有效資料範圍
完整範例:UDP 時間伺服器與客戶端
伺服器端 (TimeServer.java)
import java.io.*;
import java.net.*;
import java.util.*;
public class TimeServer {
final private static int DAYTIME_PORT = 13;
public static void main(String args[]) throws IOException {
DatagramSocket socket = new DatagramSocket(DAYTIME_PORT);
System.out.println("Time Server started on port " + DAYTIME_PORT);
while (true) {
byte buffer[] = new byte[256];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
socket.receive(packet); // 阻塞等待請求
String date = new Date().toString();
buffer = date.getBytes();
// 從請求封包取得客戶端位址/埠口回覆
InetAddress address = packet.getAddress();
int port = packet.getPort();
packet = new DatagramPacket(buffer, buffer.length, address, port);
socket.send(packet);
}
}
}
客戶端 (GetTime.java)
import java.io.*;
import java.net.*;
public class GetTime {
final private static int DAYTIME_PORT = 13;
public static void main(String args[]) throws IOException {
if (args.length == 0) {
System.err.println("Usage: java GetTime ");
System.exit(-1);
}
String host = args[0];
byte message[] = new byte[256];
InetAddress address = InetAddress.getByName(host);
System.out.println("Querying: " + address);
DatagramPacket packet = new DatagramPacket(message, message.length, address, DAYTIME_PORT);
DatagramSocket socket = new DatagramSocket(); // 臨時埠口
socket.send(packet);
packet = new DatagramPacket(message, message.length);
socket.receive(packet); // 阻塞等待回覆
String time = new String(packet.getData(), 0, packet.getLength());
System.out.println("Time at " + host + " is: " + time);
socket.close();
}
}
進階主題與最佳實踐
1. 非阻塞/多執行緒架構
生產環境建議使用 NIO DatagramChannel 或執行緒池處理並發請求,避免單執行緒阻塞成為瓶頸。
2. 可靠性補強模式
- 應用層 ACK/重傳:關鍵資料加入序號、ACK 機制
- 冗餘發送:重要封包重複發送 2-3 次
- FEC (前向錯誤修正):音視訊常用
3. 廣播與多播
// 廣播
socket.setBroadcast(true);
InetAddress broadcast = InetAddress.getByName("255.255.255.255");
socket.send(new DatagramPacket(data, data.length, broadcast, port));
// 多播
InetAddress group = InetAddress.getByName("239.255.0.1");
MulticastSocket ms = new MulticastSocket(port);
ms.joinGroup(group);
// 收發使用 ms
ms.leaveGroup(group);
4. 緩衝區大小調優
socket.setReceiveBufferSize(64 * 1024); // 64KB 接收緩衝
socket.setSendBufferSize(64 * 1024); // 64KB 發送緩衝
5. IPv6 支援
使用 Inet6Address、DatagramSocket(InetSocketAddress) 等 API,雙棧環境下自動處理。
常見錯誤與除錯技巧
| 現象 | 可能原因 | 排查步驟 |
| 發送無錯誤但對端收不到 | 防火牆阻擋、埠口錯誤、IP 版本不匹配 | tcpdump/Wireshark 抓包、telnet/nc 測試連通性 |
| 接收資料截斷 | 緩衝區過小、封包超過 MTU 分片丟失 | 調大 setReceiveBufferSize、控制封包大小 |
| SocketException: Address already in use | 埠口被佔用、TIME_WAIT 狀態 | 更換埠口、設定 SO_REUSEADDR (Java 不直接支援) |
| 多網卡環境發送失敗 | 預設路由非預期介面 | 使用 DatagramSocket(port, localAddr) 指定綁定 IP |
總結
Java DatagramSocket/DatagramPacket 提供簡潔的 UDP 程式介面。掌握無連線特性、封包大小限制、阻塞模型與異常處理是開發高效網路應用的關鍵。實務中請根據業務可靠性需求決定是否在應用層補強確認機制。
- 上一篇文章: Hibernate JNDI 綁定機制深度解析:SessionFactory 自動註冊原理
- 下一篇文章: 突破 Java 異常處理規則:捕獲 Throwable 的必要性與風險實測
Copyright ©
程式碼之家 All Rights Reserved