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

Java UDP 聊天程序實戰:基於 DatagramSocket 的簡易即時通訊

     編輯:Java 程式設計入門

UDP 協議特性回顧

UDP (User Datagram Protocol) 是無連接、不可靠、面向數據報的傳輸層協議:

  • 優點:速度快、開銷小、無連接建立延遲、支持廣播/組播。
  • 缺點:不保證送達、不保證順序、無流控擁塞控制、數據包大小受限(理論 64KB,實際建議 < 512B 避免分片)。
  • 適用場景:即時音視頻、遊戲同步、日誌收集、DNS 查詢、廣播發現服務。

架構設計:雙向通信模型

本例採用客戶端/服務端對等模型:雙方各綁定一個本地端口接收數據,發送時指定對方 IP+端口。區別於 TCP 的 `ServerSocket.accept()`,UDP 無「連接」概念,雙方均為 `DatagramSocket`。


+----------------+          DatagramPacket          +----------------+
|   Client A     |  <---------------------------->  |   Client B     |
| (sendPort=8888)|  發送目標: B_IP:6666             | (sendPort=6666)|
| (recvPort=6666)|  接收綁定: 0.0.0.0:6666         | (recvPort=8888)|
+----------------+                                  +----------------+

核心類:通信線程


public class UdpChatThread extends Thread {
    private final DatagramSocket receiveSocket; // 接收專用 Socket
    private final DatagramSocket sendSocket;    // 發送專用 Socket
    private final JTextArea messageArea;        // UI 顯示區域
    private final String targetIp;
    private final int targetPort;
    private static final int BUFFER_SIZE = 4096;

    public UdpChatThread(int localPort, String targetIp, int targetPort, JTextArea area) 
            throws SocketException {
        this.receiveSocket = new DatagramSocket(localPort); // 綁定本地端口接收
        this.sendSocket = new DatagramSocket();             // 發送端口由系統自動分配
        this.targetIp = targetIp;
        this.targetPort = targetPort;
        this.messageArea = area;
    }

    @Override
    public void run() {
        byte[] buffer = new byte[BUFFER_SIZE];
        DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
        
        try {
            while (!isInterrupted() && !receiveSocket.isClosed()) {
                receiveSocket.receive(packet); // 阻塞等待數據
                String msg = new String(packet.getData(), 0, packet.getLength(), StandardCharsets.UTF_8);
                SwingUtilities.invokeLater(() -> 
                    messageArea.append("[" + packet.getAddress().getHostAddress() + "] " + msg + "\n")
                );
                packet.setLength(buffer.length); // 重置長度,關鍵!否則後續包會被截斷
            }
        } catch (SocketException e) {
            // Socket 關閉導致的異常,正常退出
        } catch (IOException e) {
            SwingUtilities.invokeLater(() -> 
                messageArea.append("[系統] 接收錯誤: " + e.getMessage() + "\n")
            );
        }
    }

    public void send(String text) {
        try {
            byte[] data = text.getBytes(StandardCharsets.UTF_8);
            InetAddress addr = InetAddress.getByName(targetIp);
            DatagramPacket packet = new DatagramPacket(data, data.length, addr, targetPort);
            sendSocket.send(packet);
            SwingUtilities.invokeLater(() -> messageArea.append("[我] " + text + "\n"));
        } catch (IOException e) {
            SwingUtilities.invokeLater(() -> messageArea.append("[系統] 發送失敗: " + e.getMessage() + "\n"));
        }
    }

    public void close() {
        receiveSocket.close();
        sendSocket.close();
        interrupt();
    }
}

關鍵技術點解析

1. 雙 Socket 設計

接收與發送使用獨立 Socket,避免發送時阻塞接收線程,也便於分別綁定/配置端口。

2. 緩衝區長度重置

packet.setLength(buffer.length) 是新手最易忽略的坑:`receive()` 會將 `packet.length` 修改為實際接收長度,若不重置,下次接收大包時會被舊長度截斷。

3. Swing 線程安全

網絡線程不可直接操作 UI 組件,必須用 `SwingUtilities.invokeLater()` 拋回 EDT (Event Dispatch Thread)。

4. 配置文件外部化

使用 `Properties` 文件管理 IP/端口,避免硬編碼:


# udp-config.properties
local.port=6666
target.ip=127.0.0.1
target.port=8888

客戶端/服務端啟動類(對稱結構)


public class UdpChatApp {
    private JFrame frame;
    private JTextArea area;
    private JTextField input;
    private UdpChatThread chatThread;

    public UdpChatApp(String title, int localPort, String targetIp, int targetPort) {
        // ... Swing UI 初始化代碼省略 ...
        try {
            chatThread = new UdpChatThread(localPort, targetIp, targetPort, area);
            chatThread.start();
        } catch (SocketException e) { /* 錯誤處理 */ }
        
        // 發送按鈕事件
        sendBtn.addActionListener(e -> chatThread.send(input.getText().trim()));
        
        // 窗口關閉釋放資源
        frame.addWindowListener(new WindowAdapter() {
            @Override public void windowClosing(WindowEvent e) { chatThread.close(); }
        });
    }
    
    public static void main(String[] args) {
        // 啟動 A: 本地 6666 接收,發送給 8888
        new UdpChatApp("用戶 A", 6666, "127.0.0.1", 8888);
        // 啟動 B: 本地 8888 接收,發送給 6666
        // new UdpChatApp("用戶 B", 8888, "127.0.0.1", 6666);
    }
}

常見問題與優化方向

總結

UDP 編程核心是 DatagramSocket + DatagramPacket。本例實現了基礎的雙向文本聊天,展示了異步接收線程、緩衝區管理、Swing 線程協作、配置外部化等工程化細節。理解 UDP 的不可靠特性,才能在合適場景(如遊戲狀態同步、視頻流)發揮其低延遲優勢;若需可靠傳輸,請直接使用 TCP 或基於 UDP 封裝可靠層(如 QUIC、KCP、RUDP)。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
問題原因解決方案
局域網不通防火牆攔截 UDP開放對應端口,或使用 NAT 穿透技術
消息亂序/丟失UDP 本身不保證應用層添加序列號、ACK 重傳、滑動窗口(變相實現可靠 UDP)
大包分片丟失MTU 限制導致 IP 分片應用層分片重組,或限制包大小 < 1400 字節
中文亂碼編碼不一致統一指定 `StandardCharsets.UTF_8`