細說 Netty 中的粘包和拆包

TCP/IP中的“粘包”與“拆包”

“粘包拆包”是個僞命題

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

但爲什麼會有粘拆包問題呢?

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

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

寫一個簡易版TCP Server

    //接收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 Client

    //發送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 MTU=MSS+20bytes(IP包頭)+20bytes(TCP包頭),IP層分片默認是禁用的(Don`t fragment)

看一下執行結果:

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

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

由此可見,每次可讀取的報文大小,都是以ip數據報爲單位的。接收端每次接收的報文大小也都是以ip數據報爲單位。

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

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

Netty中的粘拆包處理

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

Netty的請求處理是一個Pipeline結構,通過handler接口,可以定義不同的encoder/decoder,從而解決粘包拆包(處理包界限)問題,當然也可以自己處理,原理都是相同的。

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

LengthFieldBasedFrameDecoder

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

LineBasedFrameDecoder

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

DelimiterBasedFrameDecoder

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

FixedLengthFrameDecover

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章