細說 Netty 中的粘包和拆包

TCP/IP中的「粘包」與「拆包」

「粘包拆包」是個僞命題

確實,我也認爲這是個僞命題,tcp這種雙工面向流的協議,原本就沒有粘拆包的說法,包的界限問題應該須要由上層的應用處理。網絡

但爲何會有粘拆包問題呢?

  1. 應用程序寫入的數據大於套接字緩衝區大小,這將會發生拆包。
  2. 應用程序寫入數據小於套接字緩衝區大小,網卡將應用屢次寫入的數據發送到網絡上,這將會發生粘包。
  3. 進行MSS(最大報文長度)大小的TCP分段,當TCP報文長度-TCP頭部長度>MSS的時候將發生拆包。
  4. 接收方法不及時讀取套接字緩衝區數據,這將發生粘包。(例如鏈接複用時,如不處理包界限問題必定會發生「粘包」,由於tcp並不知道接收的數據屬於應用的第幾回報文)

發送端的字節流都會先傳入緩衝區,再經過網絡傳入到接收端的緩衝區中,最終由接收端獲取。
tcp buffers.pngsocket

在應用層角度來觀察粘拆包

寫一個簡易版TCP Servertcp

//接收4k*1000大小的數據
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    serverSocketChannel.bind(new InetSocketAddress(9527));
    SocketChannel socketChannel = serverSocketChannel.accept();
    int size = 4096*1000;
    ByteBuffer byteBuffer = ByteBuffer.allocate(size);
    while (byteBuffer.hasRemaining()){
        int read = socketChannel.read(byteBuffer);
        System.out.println(read);
    }

簡易版TCP Clientspa

//發送4k*1000大小的數據
    int size = 4096*1000;
    ByteBuffer byteBuffer = ByteBuffer.allocate(size);
    SocketChannel socketChannel = SocketChannel.open();
    socketChannel.connect(new InetSocketAddress("127.0.0.1",9527));
    for (int i = 0; i < size; i++) {
        byteBuffer.put((byte) 1);
    }
    byteBuffer.flip();
    while (byteBuffer.hasRemaining()){
        int write = socketChannel.write(byteBuffer);
        System.out.println(write);
    }

當前環境下,MSS是1380,,IP層分片默認是禁用的(Don`t fragment),忽略便可日誌

看一下執行結果:code

//服務端打印結果
39672
2736
2736
2736
13680
2736
19152
2736
10944
2736
5472
2736
16416
2736
2736
12312
1368
5472
1368

從日誌上看,每次讀取的報文最小值是1368,恰好比mss小一點點(mss只是最大報文段長度,實際可讀取的值須要減去各層協議的首部大小,因此最小值是1368)。每次讀取的長度值雖然有波動,但都是1368的整數倍。server

因而可知,每次可讀取的報文大小(tcp buffer中),都是以ip數據報爲單位的。接收端每次接收的報文大小也都是以ip數據報爲單位。blog

因此在讀取報文(tcp buffer)時,也是以ip數據報爲單位,絕對不會出現讀取到半個ip包的問題(忽略因mtu大小致使的ip層分片)。接口

那麼粘包拆包裏的這個「包」的最小單位也是一個IP數據報ip

Netty中的粘拆包處理

Netty中並無直接說粘包拆包這個問題,但《Netty權威指南》這本書上倒解釋了粘包拆包,不用糾結這個名詞,跟着大多數人叫也沒錯,錯的人多了也就是對的。

Netty的請求處理是一個Pipeline結構,經過handler接口,能夠定義不一樣的encoder/decoder,從而解決粘包拆包(處理包界限)問題,固然也能夠本身處理,原理都是相同的。

Netty中內置了幾個編解碼器,能夠很簡單的處理包界限問題。

LengthFieldBasedFrameDecoder

經過在包頭增長消息體長度的解碼器,解析數據時首先獲取首部長度,而後定長讀取socket中的數據。

LineBasedFrameDecoder

換行符解碼器,報文尾部增長固定換行符rn,解析數據時以換行符做爲報文結尾。

DelimiterBasedFrameDecoder

分隔符解碼器,使用特定分隔符做爲報文的結尾,解析數據時以定義的分隔符做爲報文結尾

FixedLengthFrameDecoder

定長解碼器,這個最簡單,消息體固定長度,解析數據時按長度讀取便可

相關文章
相關標籤/搜索