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

Java Socket 長連接解決 read 阻塞三大方案:協議長度、超時、關閉流

     編輯:Java 程式設計入門

阻塞根因

`InputStream.read()` 在流未關閉且無資料時永久阻塞。長連接雙方需協商「訊息邊界」。

方案一:協議層指定長度 (推薦)

HTTP `Content-Length`、自定義協議 `Header(4 bytes len) + Body`。讀取端先讀長度,再精確讀取 `len` 位元組,`read` 不會阻塞在訊息中間。

// 發送端
ByteBuffer buf = ByteBuffer.allocate(4 + body.length);
buf.putInt(body.length).put(body);
socket.getOutputStream().write(buf.array());

// 接收端
DataInputStream in = new DataInputStream(socket.getInputStream());
int len = in.readInt(); // 阻塞直到讀滿 4 字節
byte[] body = new byte[len];
in.readFully(body);     // 阻塞直到讀滿 len 字節

方案二:設定 Socket 超時

socket.setSoTimeout(5000); // 5 秒無資料拋 SocketTimeoutException

try {
    while (true) {
        int n = in.read(buf);
        if (n == -1) break; // 流關閉
        process(buf, n);
    }
} catch (SocketTimeoutException e) {
    // 心跳檢查、重發、或判定連線異常
}

注意:超時僅適用於「預期持續有資料」場景;若業務長時間無資料 (如推送),需應用層心跳。

方案三:半關閉/關閉流結束會話

發送端寫完資料呼叫 `socket.shutdownOutput()` (發送 FIN),接收端 `read()` 返回 `-1` 正常結束。適用於「請求-回應」單向傳輸,**不適用於雙向長連接**。

方案四:特定分隔符/結束標記 (應用層協議)

// 以 \n 或 \r\n 結束
BufferedReader br = new BufferedReader(new InputStreamReader(in));
String line;
while ((line = br.readLine()) != null) { // 內部處理緩衝與分隔符
    process(line);
}

缺點:資料內含分隔符需轉義;不適用二進制資料。

綜合建議架構

場景推薦方案
高效能二進制長連接 (遊戲、IM)長度前綴 + 心跳 + setSoTimeout(心跳間隔*2)
HTTP/REST 短連接Content-Length / Chunked + setSoTimeout
簡單文本協議 (Telnet-like)行分隔符 + BufferedReader
單次請求回應shutdownOutput + read 至 -1

代碼範本:長度前綴 + 超時 + 心跳

public class LengthPrefixedClient {
    private final Socket socket;
    private final DataInputStream in;
    private final DataOutputStream out;
    private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();

    public LengthPrefixedClient(String host, int port) throws IOException {
        socket = new Socket(host, port);
        socket.setSoTimeout(10000); // 10 秒超時 (心跳間隔 5 秒)
        in = new DataInputStream(socket.getInputStream());
        out = new DataOutputStream(socket.getOutputStream());

        // 發送心跳
        scheduler.scheduleAtFixedRate(() -> {
            try { send(HEARTBEAT); } catch (IOException e) { shutdown(); }
        }, 5, 5, TimeUnit.SECONDS);
    }

    public void send(byte[] payload) throws IOException {
        synchronized (out) {
            out.writeInt(payload.length);
            out.write(payload);
            out.flush();
        }
    }

    public byte[] receive() throws IOException {
        int len = in.readInt();
        byte[] data = new byte[len];
        in.readFully(data);
        return data;
    }

    public void shutdown() {
        scheduler.shutdownNow();
        closeQuietly(in); closeQuietly(out); closeQuietly(socket);
    }
}
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved