TCP的粘包、半包和Netty的處理


參考文獻:極客時間傅健老師的《Netty源碼剖析與實戰》Talk is cheap.show me the code!

什么是粘包和半包

  在客戶端發送數據時,實際是把數據寫入到了TCP發送緩存里面的。

  半包:顧名思義就是接收到半個包,如果發送的包的大小比TCP發送緩存的容量大,那么這個數據包就會被分成多個包,通過socket多次發送到服務端,服務端第一次從接受緩存里面獲取的數據,實際是整個包的一部分,半包不是說只收到了全包的一半,是說收到了全包的一部分。

   粘包:如果發送的包的大小比TCP發送緩存容量小,並且TCP緩存可以存放多個包,那么客戶端和服務端的一次通信就可能傳遞了多個包,這時候服務端從接受緩存就可能一下讀取了多個包

舉個栗子:

  我們現在發送兩條消息:“ABC”,“DEF”,那么對方接收到的消息不一定是這種格式的,對方可能一次性就接收到“ABCDEF”,也可能分好幾次接收到“AB”,“CD”,“EF”,或者更為惡劣一次接收到了兩個消息“A”,“BCDEF”。那么這樣一次接收兩個消息的稱為粘包現象,分三次四接收到多個不完整的現象是半包現象。

粘包的主要原因:

  發送方每次寫入數據  < 套接字緩沖區大小,接收方讀取套接字緩沖區數據不夠及時。

半包的主要原因:

   發送方寫入數據 > 套接字緩沖區大小,還有就是發送的數據大於協議的MTU(Maximum Transmission Unit 最大傳輸單元)的時候必須拆包。(MTU其實就是TCP協議每層的大小)

換個角度:

  收發:一個發送可能被多次接收,多次發送可能被一次接收

  傳輸:一個發送可能占用多個傳輸包,多個發送可能公用一個傳輸包

根本原因:

  TCP是流動協議,消息無邊界。(UDP像郵寄的包裹,雖然一次運輸多個的,但每個包裹都有“界限”,一個一個簽收,所以無粘包、半包問題)

解決問題的根本手段:找出消息的邊界

  

Netty對三種常用封幀方式的支持(三個類都繼承ByteTomessageDecoder.java)

 解讀Netty處理粘包、半包的源碼-------解碼核心工作流程:

  先找到ByteToMessageDecoder.java,可以看到它繼承了ChannelInboundHandlerAdapter.java

 這個ChannelInboundHandlerAdapter有個核心的入口方法“channelRead()”;

 圖中的msg就相當於我們的數據,開始就把數據轉換成data,

ByteBuf data = (ByteBuf) msg;

 

然后判斷cumulation是否為null,cumulation是數據積累器,用來積累數據。

ByteBuf cumulation;

 

first = cumulation == null;
if (first) {
    cumulation = data;
 } else {
     cumulation = cumulator.cumulate(ctx.alloc(), cumulation, data);
 }

 

第一次的時候肯定為null  所以first是true,直接把data數據給了cumulation。再接下來就是解碼:

 callDecode(ctx, cumulation, out);

 

進入具體方法

參數中的in就是數據積累器中的數據,也就是我們傳入的數據。往下走又這么個方法調用:

decodeRemovalReentryProtection(ctx, in, out);

 

需要注意的是:decode中時,不能執行完handler remove清理操作,decode完之后需要清理數據,改方法的名稱長久是標識功能的。點進去可以查看

可以看出有個decodeState = STATE_CALLING_CHILD_DECODE;這個就是處理剛才remove handler 的;接下來就是“decode(ctx, in, out);”;這個decode是個抽象方法

之前說過Netty對三種封幀的支持分別是:“FixedLengthFrameDecoder”,“DelimiterBasedFrameDecoder”,“LengthFieldBasedFrameDecoder”。這里以“FixedLengthFrameDecoder”為例,所以點開FixedLengthFrameDecoder的decode();

   @Override
    protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        Object decoded = decode(ctx, in);
        if (decoded != null) {
            out.add(decoded);
        }
    }

 

然后再進去查看Object decoded = decode(ctx, in);

protected Object decode(
            @SuppressWarnings("UnusedParameters") ChannelHandlerContext ctx, ByteBuf in) throws Exception {
        if (in.readableBytes() < frameLength) {
            return null;
        } else {
            return in.readRetainedSlice(frameLength);
        }
    }

 

這時候就能發現關鍵點,代碼標粗,這個in還是數據積累器的數據,取得之后進行判斷,如果小於則返回null不能解出,否則解出。這樣一來就完成了一個數據解析的過程。

我只想做的更好,僅此而已


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM