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

Java Socket 通信入門到進階:阻塞 IO 完整示例、線程模型、編解碼、心跳與優雅關閉

     編輯:Java 程式設計入門

基礎阻塞 IO 模型(一請一應)

Server 端

public class BlockingServer {
    private static final int PORT = 1111;
    private static final Logger log = LoggerFactory.getLogger(BlockingServer.class);

    public static void main(String[] args) {
        try (ServerSocket server = new ServerSocket(PORT)) {
            log.info("Server 啟動,監聽端口 {}", PORT);
            while (true) {
                Socket client = server.accept(); // 阻塞等待連接
                // 簡單處理:每連接一線程(生產環境請用線程池)
                new Thread(() -> handle(client)).start();
            }
        } catch (IOException e) {
            log.error("Server 異常", e);
        }
    }

    private static void handle(Socket socket) {
        String clientInfo = socket.getRemoteSocketAddress().toString();
        try (BufferedReader in = new BufferedReader(
                     new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
             PrintWriter out = new PrintWriter(
                     new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8), true)) {

            log.info("客戶端連接: {}", clientInfo);
            String line;
            while ((line = in.readLine()) != null) { // 按行讀取
                log.info("收到 [{}] : {}", clientInfo, line);
                // 業務邏輯:回显 + 時間戳
                out.println("Echo: " + line + " @ " + Instant.now());
            }
        } catch (IOException e) {
            log.warn("客戶端斷開 [{}] : {}", clientInfo, e.getMessage());
        } finally {
            try { socket.close(); } catch (IOException ignored) {}
        }
    }
}

Client 端

public class BlockingClient {
    private static final String HOST = "127.0.0.1";
    private static final int PORT = 1111;

    public static void main(String[] args) {
        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))) {

            System.out.println("已連接服務器,輸入內容回車發送,輸入 exit 退出");
            String line;
            while ((line = console.readLine()) != null) {
                if ("exit".equalsIgnoreCase(line)) break;
                out.println(line); // 發送
                String resp = in.readLine(); // 同步等待響應
                System.out.println("服務器回應: " + resp);
            }
        } catch (IOException e) {
            System.err.println("客戶端異常: " + e.getMessage());
        }
    }
}

關鍵點深度解析

1. 字符編碼統一

  • 雙端強制指定 StandardCharsets.UTF_8,避免平台默認編碼不一致導致亂碼。
  • 使用 BufferedReader.readLine() / PrintWriter.println() 實現換行符分幀,簡單易用。

2. 線程模型演進

3. TCP 粘包/拆包與應用層協議

TCP 是字節流,無消息邊界。常見三種分幀策略:

  1. 定長:每條報文固定長度,不足補位。
  2. 分隔符:換行符 \n、\r\n 或特殊字符(示例採用)。
  3. 長度前綴:4 字節長度 + 體(推薦二進制協議)。

4. 心跳與空閒檢測


// Server 端:設置讀超時,超過 60s 無數據視為掉線
socket.setSoTimeout(60_000); // read() 超時拋 SocketTimeoutException

// 更優雅:Netty IdleStateHandler
// pipeline.addLast(new IdleStateHandler(0, 0, 60, TimeUnit.SECONDS));
// @Override userEventTriggered: IdleStateEvent.READER_IDLE -> close

5. 優雅關閉

// 雙向關閉協議
out.println("BYE");      // 發送結束標記
out.flush();
socket.shutdownOutput(); // 發送 FIN,對端 readLine() 返回 null
String last = in.readLine(); // 讀取對端最後響應
socket.close();

生產環境建議:直接使用 Netty

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.100.Final</version>
</dependency>
  • 事件驅動、零拷貝、內存池、內建編解碼器、SSL、HTTP/2、WebSocket
  • 避免重複造輪子,專注業務邏輯

總結

阻塞 Socket 適合學習網絡基礎、低併發內部工具。掌握「流讀寫、編碼、分幀、線程模型、超時、關閉」六大核心點,即可舉一反三。高併發、長連接、複雜協議場景,請毫不猶豫選擇 Netty。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
模型代碼修改適用場景缺點
主循環 + 新線程示例代碼連接數 < 500、低併發線程創建銷毀開銷大、OOM 風險
固定線程池ExecutorService pool = Executors.newFixedThreadPool(100); pool.execute(() -> handle(client));中等併發、請求處理快阻塞任務佔滿線程池導致拒絕服務
NIO 選擇器Single Thread + Selector (Netty)高併發、長連接、海量連接代碼複雜,建議直接用 Netty