| 類 | 職責 | 關鍵方法 |
|---|---|---|
| InetAddress | IP 地址封裝 (IPv4/IPv6) | getByName(), getAllByName(), getLocalHost(), getHostAddress() |
| DatagramSocket | 發送/接收端點綁定 | send/receive, setSoTimeout, connect (綁定遠端), close |
| DatagramPacket | 數據報封裝 (緩衝區+地址+端口) | getData(), getLength(), getOffset(), getAddress(), getPort(), setData() |
// 1. 客戶端/發送端:臨時端口,系統自動分配
DatagramSocket clientSocket = new DatagramSocket();
// 2. 服務端/接收端:固定監聽端口
DatagramSocket serverSocket = new DatagramSocket(9999);
// 3. 多網卡綁定:指定本地 IP (如內網 192.168.1.100 僅接收內網流量)
DatagramSocket multiNicSocket = new DatagramSocket(9999, InetAddress.getByName("192.168.1.100"));
異常處理:端口佔用拋 SocketException,**必須 try-catch 並關閉資源**。
public class UdpSender {
public static void main(String[] args) throws IOException {
InetAddress target = InetAddress.getByName("192.168.1.200"); // 目標 IP
int port = 9999; // 目標端口
try (DatagramSocket socket = new DatagramSocket()) { // try-with-resources 自動 close
String message = "Hello UDP at " + Instant.now();
byte[] buf = message.getBytes(StandardCharsets.UTF_8);
DatagramPacket packet = new DatagramPacket(buf, buf.length, target, port);
socket.send(packet);
System.out.println("Sent: " + message);
}
}
}
public class UdpReceiver {
private static final int PORT = 9999;
private static final int BUFFER_SIZE = 8192; // 8KB 緩衝區
public static void main(String[] args) throws IOException {
try (DatagramSocket socket = new DatagramSocket(PORT)) {
socket.setSoTimeout(5000); // 5秒無數據拋 SocketTimeoutException,避免永久阻塞
byte[] buffer = new byte[BUFFER_SIZE];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
System.out.println("UDP Server listening on port " + PORT);
while (true) {
try {
socket.receive(packet); // 阻塞直到收到數據
int length = packet.getLength();
String received = new String(packet.getData(), 0, length, StandardCharsets.UTF_8);
InetAddress clientAddr = packet.getAddress();
int clientPort = packet.getPort();
System.out.printf("From %s:%d - %s%n", clientAddr.getHostAddress(), clientPort, received);
// 回射確認 (可選)
String ack = "ACK: " + received;
byte[] ackBuf = ack.getBytes(StandardCharsets.UTF_8);
DatagramPacket ackPacket = new DatagramPacket(ackBuf, ackBuf.length, clientAddr, clientPort);
socket.send(ackPacket);
// 重置 length 供下次接收 (關鍵!否則後續數據截斷)
packet.setLength(BUFFER_SIZE);
} catch (SocketTimeoutException e) {
System.out.println("Heartbeat: no data in 5s, still alive...");
}
}
}
}
}
DatagramPacket 內部維護 offset 和 length。**接收後必須 packet.setLength(buffer.length) 重置**,否則下次接收數據長度受限於上次實際長度,導致數據截斷 Bug。**生產環境建議每次接收 new DatagramPacket() 或顯式重置**。
socket.connect(address, port)connect() 將 Socket 綁定到特定遠端地址/端口。send(packet) **無需在 Packet 中指定地址/端口**,內部自動填充。receive() **只接收該遠端發來的數據**,自動過濾其他來源 (內核級過濾,性能優於應用層判斷)。// 廣播發送 (需網卡支持、路由器允許)
socket.setBroadcast(true);
InetAddress broadcastAddr = InetAddress.getByName("255.255.255.255"); // 或子網廣播 192.168.1.255
DatagramPacket bcPacket = new DatagramPacket(buf, buf.length, broadcastAddr, PORT);
socket.send(bcPacket);
// 組播加入 (IGMP)
InetAddress group = InetAddress.getByName("239.255.0.1");
NetworkInterface ni = NetworkInterface.getByInetAddress(InetAddress.getLocalHost());
MulticastSocket ms = new MulticastSocket(PORT);
ms.joinGroup(new InetSocketAddress(group, PORT), ni);
// 接收組播數據...
ms.leaveGroup(new InetSocketAddress(group, PORT), ni);
Java UDP 編程核心三件套:**InetAddress 定址、DatagramSocket 端點、DatagramPacket 載體**。掌握要點:
packet.setLength(buf.length) 避免截斷。setSoTimeout() + 捕獲 SocketTimeoutException。connect() 內核過濾 + 發送簡化。UDP 以**極致簡單**換取**極致可控**,是高性能網絡編程的基石。理解其語義邊界,才能在 QUIC、gRPC、WebRTC、IoT 協議棧中游刃有餘。