日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

Netty源码学习6——netty编码解码器&粘包半包问题的解决

發(fā)布時間:2023/11/29 windows 45 coder
生活随笔 收集整理的這篇文章主要介紹了 Netty源码学习6——netty编码解码器&粘包半包问题的解决 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

系列文章目錄和關(guān)于我

零丶引入

經(jīng)過《Netty源碼學習4——服務(wù)端是處理新連接的&netty的reactor模式和《Netty源碼學習5——服務(wù)端是如何讀取數(shù)據(jù)的》的學習,我們了解了服務(wù)端是如何處理新連接并讀取客戶端發(fā)送的數(shù)據(jù)的:

  • netty的reactor:主reactor中的NioEventLoop監(jiān)聽accept事件,然后調(diào)用NioServerSocketChannel#Unsafe讀取數(shù)據(jù)——依賴JDK ServerSockectChannel#accept,獲取到新連接——SockectChannel后,會包裝為NioSocketChannel然后調(diào)用channelRead,隨后ServerBootstrapAcceptor 會負載均衡的選擇一個子reactor 注冊NioSocketChannel對read事件感興趣
  • read事件:子reactor中的NioEventLoop會監(jiān)聽read事件,調(diào)用NioSocketChannel讀取客戶端發(fā)送數(shù)據(jù)(依賴JDK SocketChannel#read(ByteBuffer)),netty會使用ByteBufAllocator優(yōu)化ByteBuf的分配,使用AdaptiveRecvByteBufAllocator對ByteBuf進行擴容縮容,以及控制是否繼續(xù)讀取。

——至此數(shù)據(jù)以及讀取到了ByteBuf中,服務(wù)端需要先解碼ByteBuf中的數(shù)據(jù),然后我們業(yè)務(wù)處理器才能根據(jù)發(fā)送的消息進行響應(yīng),業(yè)務(wù)執(zhí)行結(jié)果還需要進行編碼才能發(fā)送,so 這一篇和大家一起學習以下Netty中的編碼解碼。

一丶看看其他開源框架是如何使用Netty的編碼解碼的

1.Dubbo

Apache Dubbo 是一款 RPC 服務(wù)開發(fā)框架,用于解決微服務(wù)架構(gòu)下的服務(wù)治理與通信問題,使用 Dubbo 開發(fā)的微服務(wù)原生具備相互之間的遠程地址發(fā)現(xiàn)與通信能力, 利用 Dubbo 提供的豐富服務(wù)治理特性,可以實現(xiàn)諸如服務(wù)發(fā)現(xiàn)、負載均衡、流量調(diào)度等服務(wù)治理訴求。

Dubbo 中的網(wǎng)絡(luò)通信可以基于Netty,Dubbo 官方源碼如下

可以看到Dubbo會向ChannelPipeline中加入decoder和encoder,負責編碼解碼。

2.Sentinel

Sentinel 是面向分布式、多語言異構(gòu)化服務(wù)架構(gòu)的流量治理組件,主要以流量為切入點,從流量路由、流量控制、流量整形、熔斷降級、系統(tǒng)自適應(yīng)過載保護、熱點流量防護等多個維度來幫助開發(fā)者保障微服務(wù)的穩(wěn)定性。(詳細學習:《Sentinel基本使用與源碼分析》)

sentinel提供了集群限流的能力,本質(zhì)是服務(wù)端控制令牌的下發(fā),客戶端通過網(wǎng)絡(luò)通信申請令牌,如下是集群限流中,使用netty實現(xiàn)服務(wù)端的源碼:

可以看到sentinel集群限流會向ChannelPipeline中增加

  • LengthFieldBasedFrameDecoder:基于長度字段的解碼器——一級解碼器,根據(jù)frame中的長度字段,解碼出消息

  • NettyRequestDecoder:請求解碼器——二次解碼器,將一次解碼器解碼出的消息,反序列化為請求對象

  • LengthFieldPrepender:長度放在frame頭部的編碼器,將服務(wù)端響應(yīng)的消息添加上長度信息

  • NettyResponseEncoder:將服務(wù)端處理返回的java對象,編碼成ByteBuf

3.對比Dubbo和Sentinel對netty的使用

相比于Sentinel,Dubbo的使用更加簡潔,直接將編碼解碼的邏輯封裝到自己的adapter之中

Sentinel的使用也是非常標準,也利于我們理解netty的編解碼運行機制——即編碼解碼其實是ChannelHandler的一種實現(xiàn),通過將編碼解碼加入到ChannelPipline中實現(xiàn)數(shù)據(jù)的逐環(huán)處理。

二丶什么是編碼,解碼器,為什么需要編碼解碼器

netty中的編碼解碼器是負責將應(yīng)用程序的數(shù)據(jù)格式轉(zhuǎn)換為可以在網(wǎng)絡(luò)中傳輸?shù)淖止?jié)流,以及將接收到的字節(jié)流轉(zhuǎn)換回為應(yīng)用程序可以處理的數(shù)據(jù)格式的組件。編解碼器是網(wǎng)絡(luò)通信的關(guān)鍵組件,因為它們抽象掉了網(wǎng)絡(luò)層和應(yīng)用層之間的復(fù)雜轉(zhuǎn)換細節(jié)。

主要作用有:

  • 數(shù)據(jù)序列化與反序列化:

    • 編碼(序列化):將應(yīng)用數(shù)據(jù)結(jié)構(gòu)(如對象、消息)轉(zhuǎn)換成字節(jié)流,以便能夠通過網(wǎng)絡(luò)發(fā)送。
    • 解碼(反序列化):將網(wǎng)絡(luò)中接收到的字節(jié)流轉(zhuǎn)換回應(yīng)用數(shù)據(jù)結(jié)構(gòu)。
  • 協(xié)議實現(xiàn):

    編解碼器實現(xiàn)了網(wǎng)絡(luò)通信中所需遵守的特定協(xié)議規(guī)則,如 HTTP、WebSocket,SMTP。
    它們確保數(shù)據(jù)符合協(xié)議格式,并能夠正確地被發(fā)送和接收方理解。
    處理流控制問題:

  • 對于面向流的協(xié)議(如 TCP),解決粘包和半包等問題,確保數(shù)據(jù)的完整性。

  • 解耦應(yīng)用與網(wǎng)絡(luò)層&擴展性與靈活性:

    編解碼器允許開發(fā)者專注于業(yè)務(wù)邏輯,而無需關(guān)心底層的字節(jié)處理。應(yīng)用邏輯可以與網(wǎng)絡(luò)傳輸邏輯分離,使得代碼更加清晰和可維護。

    應(yīng)用開發(fā)者也可以隨機的切換不同的編碼解碼器,提升擴展性和靈活性。

三丶Netty解決tcp粘包,半包的編解碼器

1.tcp是基于流的協(xié)議&為什么會出現(xiàn)粘包,半包

TCP 傳輸?shù)臄?shù)據(jù)被視為一個連續(xù)的、無邊界的字節(jié)流。網(wǎng)絡(luò)上的兩個應(yīng)用程序通過建立一個 TCP 連接來交換數(shù)據(jù),而這個數(shù)據(jù)流就像是從一個地方倒水到另一個地方,水(數(shù)據(jù))會連續(xù)不斷地流動,而不是一杯一杯分開倒(即不像獨立的消息或數(shù)據(jù)包)。

  • TCP 數(shù)據(jù)發(fā)送:

    當應(yīng)用程序要發(fā)送數(shù)據(jù)時,它會將數(shù)據(jù)寫入到 TCP 套接字的發(fā)送緩沖區(qū)。這個寫入操作通常是通過像 write() 或 send() 這樣的系統(tǒng)調(diào)用完成的。

    TCP 協(xié)議會從發(fā)送緩沖區(qū)中取出數(shù)據(jù),并將數(shù)據(jù)分割成合適大小的段,此大小受多個因素影響,包括最大傳輸單元(MTU)和網(wǎng)絡(luò)擁塞窗口(congestion window)。然后,TCP 將每個段封裝在一個 TCP 數(shù)據(jù)包中,并加上 TCP 頭部,其中包含序列號等信息,再將數(shù)據(jù)包發(fā)送到網(wǎng)絡(luò)中。

    這里的關(guān)鍵點是,TCP 不關(guān)心應(yīng)用程序傳遞給它的數(shù)據(jù)是一條消息還是多條消息,它只是簡單地將這些數(shù)據(jù)作為字節(jié)序列處理。因此,即使應(yīng)用程序以多個 write() 調(diào)用發(fā)送多條消息,TCP 仍可能將它們合并成一個數(shù)據(jù)包發(fā)送,這就可能導致粘包問題。

  • TCP 數(shù)據(jù)接收:

    在接收端,TCP 數(shù)據(jù)包到達后,TCP 協(xié)議會解析 TCP 頭部信息,并根據(jù)序列號將數(shù)據(jù)放入接收緩沖區(qū)中的正確位置。

    接收端的應(yīng)用程序通過 read() 或 recv() 等系統(tǒng)調(diào)用從 TCP 套接字的接收緩沖區(qū)中讀取數(shù)據(jù)。這里也是不考慮消息邊界的,應(yīng)用程序可能一次讀取任意大小的數(shù)據(jù),這可能導致一次讀取操作包含了多條消息(粘包),或只有部分消息(半包)

2.netty是怎么解決粘包,半包問題的

解決粘包,半包問題的關(guān)系,是如何分辨那一部分是一條完整的消息。

Netty 通過提供一系列編解碼器(Decoder 和 Encoder)來解決 TCP 粘包和半包問題。這些編解碼器位于 Netty 的管道(ChannelPipeline)中,它們對進出的數(shù)據(jù)流進行處理,確保數(shù)據(jù)的完整性和邊界的正確性。

  • FixedLengthFrameDecoder:

    這個解碼器按照固定的長度對接收到的數(shù)據(jù)進行分割。如果發(fā)送的數(shù)據(jù)小于固定長度,那么發(fā)送方需要進行填充。

  • LineBasedFrameDecoder:
    這個解碼器基于換行符(\n 或 \r\n)拆分數(shù)據(jù)流。它適用于文本協(xié)議,如 SMTP 或 POP3。

  • DelimiterBasedFrameDecoder:
    這個解碼器根據(jù)指定的分隔符來拆分數(shù)據(jù)流。分隔符可以是任意的字節(jié)序列,如特定的字符或者字符串。

  • LengthFieldBasedFrameDecoder:
    這是一個更加通用和靈活的解碼器,它基于消息頭的長度字段來確定每個消息的長度。發(fā)送方在消息頭中指定了消息體的長度,接收方通過解碼器讀取指定長度的數(shù)據(jù),從而確保完整性。

  • LengthFieldPrepender:
    這個編碼器在發(fā)送消息的前面添加長度字段,與 LengthFieldBasedFrameDecoder 配合使用,可確保粘包和半包問題不會發(fā)生

3.源碼學習

可以看到解碼器都是ByteToMessageDecoder的子類,編碼器只有LengthFieldPrepender是MessageToMessageEncoder的子類(和LengthFieldBasedFrameDecoder是一對)

3.1 ByteToMessageDecoder

以類似流的方式將字節(jié)從一個ByteBuf解碼為另一個消息類型,是一個ChannelInboundHandler,意味著可以處理入站事件

其中最關(guān)鍵的是channelRead方法

public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    // 只處理ByteBuf類型
    if (msg instanceof ByteBuf) {
        selfFiredChannelRead = true;
        // List的一種實現(xiàn) clear方法不會清空內(nèi)容,recycle方法會清空
        // newInstance方法使用FastThreadLocal緩存已有對象,避免重復(fù)構(gòu)造
        CodecOutputList out = CodecOutputList.newInstance();
        try {
            first = cumulation == null;
            // cumulation累積器 ,第一次會把傳入的byteBuf和空buf累計
            // 后續(xù)會和原有的內(nèi)容進行累計
            cumulation = cumulator.cumulate(ctx.alloc(),
                    first ? Unpooled.EMPTY_BUFFER : cumulation, (ByteBuf) msg);
            // 調(diào)用子類進行解碼
            callDecode(ctx, cumulation, out);
        } catch (DecoderException e) {
            throw e;
        } catch (Exception e) {
            throw new DecoderException(e);
        } finally {
           
            try {
                // 省略資源釋放部分
                int size = out.size();
                firedChannelRead |= out.insertSinceRecycled();
                 // 編碼后內(nèi)容觸發(fā)channelRead
                fireChannelRead(ctx, out, size);
            } finally {
                // 釋放資源
                out.recycle();
            }
        }
    } else {
        // 只處理ByteBuf類型
        ctx.fireChannelRead(msg);
    }
}
  • netty使用了CodecOutputList來記錄解碼生成的內(nèi)容,也就是說子類實現(xiàn)decode方法時,如果得到了完整的消息,需要將消息加入到CodecOutputList中,CodecOutputList#newInstance是從FastThreadLocal中獲取的,線程安全,每一個線程進行復(fù)用

  • Cumulator:累積器,由于TCP存在粘包,半包的情況,NioSockectChannel在讀取的時候不一定可以讀取到一個完整的消息,所有需要使用Cumulator進行累計,netty提供了兩種累積器的實現(xiàn)

    • 合并:顧名思義,會將已經(jīng)積攢的ByteBuf和當前需要累計的ByteBuf進行合并,是真真切切發(fā)生內(nèi)存拷貝的

    • 組合:這種策略下,會將已經(jīng)積攢的ByteBuf和當前需要累計的ByteBuf進行組合——生成一個邏輯視圖:CompositeByteBuf

  • 模板模式:ByteToMessageDecoder將累積的過程進行了抽象,子類只需要實現(xiàn)decode將解碼生成的消息寫入到CodecOutputList中即可

3.1 FixedLengthFrameDecoder 定長消息

使用子類進行解碼,需要保證發(fā)送來的消息長度是一致的!其使用字段frameLength記錄完整消息的長度

如下是解碼源碼:

3.2 LineBasedFrameDecoder 換行符解碼器

顧名思義就是找到換行符所在的位置,分割出一條消息

這個累有點雞肋,因為不支持自定義換行符,如果換行符需要支持指定可以使用DelimiterBasedFrameDecoder

3.3 DelimiterBasedFrameDecoder 支持自定義分割符的解碼器

原理和LineBasedFrameDecoder 類似,內(nèi)部使用delimiters數(shù)組記錄分割符是什么

3.4 LengthFieldBasedFrameDecoder

基于消息頭的長度字段來確定每個消息的長度來解碼出消息,相比于上面幾種,它使用更加廣泛的解碼器(消息定長如果消息太短需要補齊,浪費網(wǎng)絡(luò)資源,換行和分割符解碼同樣會浪費一些網(wǎng)絡(luò)資源)

此類源碼上的注釋詳細解釋了如何使用,它有如下幾個重要的參數(shù):

  • maxFrameLength : 發(fā)送的數(shù)據(jù)包最大長度;
  • lengthFieldOffset :長度域偏移量,指的是長度域位于整個數(shù)據(jù)包字節(jié)數(shù)組中的下標;
  • lengthFieldLength :長度域的自己的字節(jié)數(shù)長度。
  • lengthAdjustment :長度域的偏移量矯正。 如果長度域的值,除了包含有效數(shù)據(jù)域的長度外,還包含了其他域(如長度域自身)長度,那么,就需要進行矯正。矯正的值為:包長 - 長度域的值 – 長度域偏移 – 長度域長。
  • initialBytesToStrip :丟棄的起始字節(jié)數(shù)。丟棄處于有效數(shù)據(jù)前面的字節(jié)數(shù)量。比如前面有4個節(jié)點的長度域,則它的值為4。

例子:

3.5 LengthFieldPrepender

在發(fā)送消息的前面添加長度字段,與 LengthFieldBasedFrameDecoder 配合使用,可確保粘包和半包問題不會發(fā)生。

因此它是一個ChannelOutboundHandler,其原理也比較簡單,在發(fā)送消息前加上長度信息

四丶總結(jié)&啟下

這一篇我們學習了netty是如何解決TCP協(xié)議中粘包半包的問題,以及粘包半包問題為何會出現(xiàn),并學習netty中常用的編碼解碼器源碼

其實netty對于其他協(xié)議,如:udp,websockect,http,smtp都有對應(yīng)的實現(xiàn),這也是為啥開發(fā)者喜歡使用netty的原因——不需要重復(fù)造*

另外netty還支持多種序列化反序列化方式:json,xml,Protobuf等

后續(xù)應(yīng)該會更新netty追求卓越性能打造的一些*,如FastThreadLocal,對象池,內(nèi)存池,時間輪。以及和學習交流群的小伙伴們一起基于netty寫一個簡陋的rpc框架,鞏固一下netty的使用。

總結(jié)

以上是生活随笔為你收集整理的Netty源码学习6——netty编码解码器&粘包半包问题的解决的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。