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

Java Socket TCP 讀取資料不完整:原因分析與三種標準解決方案

     編輯:程式設計綜合問答

現象本質

TCP 是位元組流協議,無訊息邊界。「資料不完整」是常態,而非異常。發送端一次 write 的資料,接收端可能需多次 read 才能讀完;或多次 write 合併成一次 read(黏包)。

三種標準解決方案

方案一:固定長度訊息

// 發送端
byte[] payload = "Hello".getBytes(StandardCharsets.UTF_8);
ByteBuffer header = ByteBuffer.allocate(4).putInt(payload.length);
out.write(header.array());
out.write(payload);
out.flush();

// 接收端
byte[] lenBytes = new byte[4];
readFully(in, lenBytes, 4);
int len = ByteBuffer.wrap(lenBytes).getInt();
byte[] body = new byte[len];
readFully(in, body, len);

方案二:分隔符訊息(如換行符)

// 發送端
out.write(("Hello\n").getBytes(StandardCharsets.UTF_8));
out.flush();

// 接收端 - 使用 BufferedReader
BufferedReader reader = new BufferedReader(
    new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
String line = reader.readLine();  // 讀到 \n 為止

方案三:長度前綴 + 內容(最通用,推薦)

// 通用讀取工具方法
public static void readFully(InputStream in, byte[] buf, int offset, int len) 
        throws IOException {
    while (len > 0) {
        int r = in.read(buf, offset, len);
        if (r == -1) throw new EOFException();
        offset += r;
        len -= r;
    }
}

// 訊息協定類
public class TcpMessage {
    public static final int HEADER_SIZE = 4;
    
    public static void send(OutputStream out, byte[] body) throws IOException {
        ByteBuffer bb = ByteBuffer.allocate(HEADER_SIZE + body.length);
        bb.putInt(body.length);
        bb.put(body);
        out.write(bb.array());
        out.flush();
    }
    
    public static byte[] receive(InputStream in) throws IOException {
        byte[] header = new byte[HEADER_SIZE];
        readFully(in, header, 0, HEADER_SIZE);
        int bodyLen = ByteBuffer.wrap(header).getInt();
        if (bodyLen < 0 || bodyLen > 10_000_000) 
            throw new IOException("Invalid length: " + bodyLen);
        byte[] body = new byte[bodyLen];
        readFully(in, body, 0, bodyLen);
        return body;
    }
}

常見誤區澄清

誤區事實
「TCP 會亂序」TCP 保證有序交付,「不完整」是流切分問題
「睡眠幾毫秒再讀」不可靠,網路抖動會導致失敗
「available() > 0 才讀」available() 不可靠,阻塞 read 才是正解
「開大緩衝區一次讀完」無法解決黏包/拆包,且大訊息會 OOM

進階:Netty 等框架的處理

// Netty Pipeline 配置
pipeline.addLast(
    new LengthFieldBasedFrameDecoder(1024*1024, 0, 4, 0, 4),
    new LengthFieldPrepender(4),
    new StringDecoder(StandardCharsets.UTF_8),
    new MyBusinessHandler()
);

總結檢查清單

  • ✅ 定義明確的訊息協定(長度前綴 / 分隔符 / 固定長度)
  • ✅ 使用 readFully 循環讀取直到滿足預期長度
  • ✅ 設定合理的讀取超時 socket.setSoTimeout(ms)
  • ✅ 限制最大訊息長度防惡意攻擊
  • ✅ 生產環境建議直接用 Netty / Mina 等成熟框架
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved