首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 基於 UDP 協議傳輸數據的實現方法

     編輯:Java 相關知識

UDP 協議簡介

UDP (User Datagram Protocol) 是無連接、不可靠、面向數據報的傳輸層協議。特點:速度快、開銷小、無連接建立延遲,但不保證送達、不保證順序、不保證不重複。適用於即時通訊、視頻流、DNS 查詢、遊戲狀態同步等對實時性要求高、能容忍少量丟包的場景。

核心 API:DatagramSocket 與 DatagramPacket

  • DatagramSocket:發送/接收數據報的端點,綁定本地端口。
  • DatagramPacket:數據報封裝,包含數據緩衝區、長度、目標地址/端口 (發送時) 或 發送方地址/端口 (接收時)。

完整示例:UDP 回聲服務器與客戶端

服務端


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

進階主題

1. 多播 (Multicast) - 一對多廣播


// 發送端
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);

2. 可靠性增強:應用層實現 ACK/重傳

UDP 本身不可靠,若業務需可靠傳輸,需在應用層實現:

  • 序列號 (Sequence Number)
  • 確認機制 (ACK)
  • 超時重傳 (RTO 指數退避)
  • 滑動窗口 / 選擇性確認 (SACK)

參考實現:Netflix UDP Reliability、QUIC 協議 (HTTP/3 底層)。

3. NIO 非阻塞 UDP (DatagramChannel)


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、遊戲網絡編程打下基礎。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved