首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計綜合問答

Java Socket 多線程雙向通訊實戰:伺服器端持續監聽、客戶端專屬執行緒處理多輪讀寫

     編輯:程式設計綜合問答

實現「伺服器發送訊息 → 客戶端回應 → 伺服器根據內容判斷再回覆」的多輪互動,核心在於:**伺服器為每個客戶端連線建立專屬執行緒,在執行緒內循環處理輸入/輸出流**;客戶端同樣需啟動接收執行緒異步讀取伺服器推送。

架構圖解


[Server Main Thread] ──accept()──▶ [ClientHandler Thread-1] ◀──Socket──▶ [Client Main Thread]
                                    │  循環 read/write                      │
                                    ▼                                       ▼
                              [ClientHandler Thread-2] ◀──Socket──▶ [Client Receiver Thread]
                                    │                                       │
                                    ▼                                       ▼
                                 ... (多客戶端並發)

伺服器端核心實現


public class ChatServer {
    private static final int PORT = 8189;
    private static final Set clients = Collections.synchronizedSet(new HashSet<>());

    public static void main(String[] args) throws IOException {
        try (ServerSocket serverSocket = new ServerSocket(PORT)) {
            System.out.println("伺服器啟動,監聽埠 " + PORT);
            while (true) {
                Socket client = serverSocket.accept();
                clients.add(client);
                new Thread(new ClientHandler(client)).start();
            }
        }
    }

    static class ClientHandler implements Runnable {
        private final Socket socket;
        private BufferedReader in;
        private PrintWriter out;

        ClientHandler(Socket socket) { this.socket = socket; }

        @Override
        public void run() {
            try {
                in = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
                out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8), true);

                String clientAddr = socket.getInetAddress().getHostAddress();
                System.out.println(clientAddr + " 已連線");
                broadcast("【系統】" + clientAddr + " 加入聊天室", socket);

                String msg;
                while ((msg = in.readLine()) != null) { // 循環讀取客戶端訊息
                    System.out.println("收自 " + clientAddr + ": " + msg);
                    // 業務邏輯:根據訊息內容決定回覆
                    String reply = processMessage(msg);
                    out.println(reply); // 回覆該客戶端
                    broadcast("[" + clientAddr + "] " + msg, socket); // 廣播給其他人
                }
            } catch (IOException e) {
                System.err.println("客戶端異常斷開: " + e.getMessage());
            } finally {
                clients.remove(socket);
                closeQuietly(socket);
                broadcast("【系統】" + socket.getInetAddress().getHostAddress() + " 離開", null);
            }
        }

        private String processMessage(String msg) {
            // 範例:簡單指令處理
            if ("/time".equals(msg)) return "伺服器時間: " + Instant.now();
            if ("/quit".equals(msg)) throw new RuntimeException("客戶端請求斷開");
            return "收到: " + msg; // 預設回覆
        }

        private void broadcast(String msg, Socket exclude) {
            for (Socket c : clients) {
                if (c != exclude && !c.isClosed()) {
                    try {
                        PrintWriter w = new PrintWriter(new OutputStreamWriter(c.getOutputStream(), StandardCharsets.UTF_8), true);
                        w.println(msg);
                    } catch (IOException ignored) {}
                }
            }
        }

        private void closeQuietly(Socket s) {
            try { if (s != null) s.close(); } catch (IOException ignored) {}
        }
    }
}

客戶端核心實現(雙執行緒:主執行緒發送、接收執行緒監聽)


public class ChatClient {
    private static final String HOST = "localhost";
    private static final int PORT = 8189;

    public static void main(String[] args) throws IOException {
        try (Socket socket = new Socket(HOST, PORT)) {
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
            PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8), true);
            BufferedReader console = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8));

            // 接收執行緒:持續讀取伺服器推送
            Thread receiver = new Thread(() -> {
                try {
                    String line;
                    while ((line = in.readLine()) != null) {
                        System.out.println(line); // 顯示伺服器廣播/回覆
                    }
                } catch (IOException e) {
                    System.err.println("連線中斷: " + e.getMessage());
                }
            });
            receiver.setDaemon(true); // 主執行緒結束時自動退出
            receiver.start();

            // 主執行緒:讀取鍵盤輸入發送
            String input;
            System.out.print("> ");
            while ((input = console.readLine()) != null) {
                out.println(input);
                if ("/quit".equals(input)) break;
                System.out.print("> ");
            }
        }
    }
}

關鍵設計要點

要點實現方式原因
每客戶端一執行緒new Thread(new ClientHandler(socket)).start()阻塞式 readLine() 不會阻塞主監聽迴圈,支援高併發
雙向異步通訊客戶端啟動獨立 receiver 執行緒主執行緒可持續發送,同時即時接收伺服器推送
訊息邊界處理使用 readLine() (換行符分隔)簡單可靠;生產環境建議自定協議 (長度前綴/JSON/Protobuf)
資源釋放try-with-resources + finally 關閉 Socket防止檔案描述符洩漏,確保伺服器穩定運行
編碼統一明確指定 StandardCharsets.UTF_8避免中文亂碼,跨平台相容

生產環境擴展建議

  • 執行緒池:用 ExecutorService 替代手動 new Thread,限制最大併發數。
  • NIO/Netty:高併發 (萬級連線) 改用 Selector 或 Netty 事件驅動模型。
  • 心跳機制:定時發送 PING 檢測死連線,清理殭屍 Socket。
  • 協議框架:採用 Protobuf/MessagePack + 長度前綴,支援複雜物件序列化。
  • 安全性:加入 TLS (SSLServerSocket)、認證授權、速率限制。

總結

Java Socket 多輪雙向通訊的核心模式是:**伺服器 accept 循環 + 客戶端專屬執行緒循環 read/write + 客戶端雙執行緒 (發送/接收)**。掌握此模式並注意資源管理、編碼統一、訊息邊界,即可構建穩健的網路互動應用。

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