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

Socket InputStream 讀取長度前綴問題:PushbackInputStream 回退字節實現協議解析

     編輯:程式設計綜合問答

場景

自定義協議:`[10位長度數字][JSON數據]`,如 `0000000237{"CODE":"100"}`。需先讀取 10 字節長度,再按長度讀取 JSON。

問題代碼


InputStream in = socket.getInputStream();
byte[] buffer = new byte[10];
in.read(buffer); // 期望讀長度,實際可能讀到部分 JSON

原因:`read(buffer)` **不保證讀滿 buffer**,可能返回任意 1~10 字節;且流無標記,無法「退回」多讀的字節。

正確模式:PushbackInputStream + 確保讀滿


PushbackInputStream pin = new PushbackInputStream(socket.getInputStream());

// 1. 精確讀取 10 字節長度前綴
byte[] lenBytes = new byte[10];
int totalRead = 0;
while (totalRead < 10) {
    int r = pin.read(lenBytes, totalRead, 10 - totalRead);
    if (r == -1) throw new EOFException("連接關閉");
    totalRead += r;
}
int dataLength = Integer.parseInt(new String(lenBytes, StandardCharsets.US_ASCII).trim());

// 2. 精確讀取數據體
byte[] data = new byte[dataLength];
totalRead = 0;
while (totalRead < dataLength) {
    int r = pin.read(data, totalRead, dataLength - totalRead);
    if (r == -1) throw new EOFException("數據不完整");
    totalRead += r;
}

// 3. 解析 JSON
String json = new String(data, StandardCharsets.UTF_8);
// JSONObject obj = new JSONObject(json);

PushbackInputStream 回退用法 (若誤讀多字節)


// 假設一次性 read 大 buffer,需把多讀的推回
byte[] bigBuf = new byte[1024];
int n = pin.read(bigBuf); // 實際只需 10 字節長度
if (n > 10) {
    pin.unread(bigBuf, 10, n - 10); // 將多讀的字節推回流頭
}
// 後續 read 會先返回推回的字節

Netty 等框架的解決方式

  • `LengthFieldBasedFrameDecoder`:自動處理長度字段解碼、半包/粘包、腐敗幀檢測。
  • 應用層只需處理完整帧 `ByteBuf`,無需手動管理流讀取。

最佳實踐總結

  1. **TCP 是流無邊界**,應用層必須自行定義幀邊界 (長度前綴、分隔符、固定長度)。
  2. **讀取必須循環直到滿足預期字節數**,單次 `read` 不可靠。
  3. **需要「偷看」或「回退」字節時用 `PushbackInputStream`/`BufferedInputStream.mark/reset`**。
  4. **生產環境優先使用 Netty/Mina 等成熟網絡框架**,避免重造輪子引入 Bug。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved