最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Netty?拆包沾包問(wèn)題解決方案詳解

 更新時(shí)間:2022年11月25日 10:13:09   作者:鱷魚(yú)兒  
這篇文章主要為大家介紹了Netty?拆包沾包問(wèn)題解決方案示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

上一篇說(shuō)到Springboot整合Netty,自定義協(xié)議實(shí)現(xiàn),本文聊一些拆包/沾包問(wèn)題。

拆包/沾包問(wèn)題

TCP是面向字節(jié)流的協(xié)議,在發(fā)送方發(fā)送的若干包數(shù)據(jù)到接收方接收時(shí),這些數(shù)據(jù)包可能會(huì)被粘成一個(gè)數(shù)據(jù)包,而從接收緩沖區(qū)看,后一包數(shù)據(jù)的頭緊接著前一包數(shù)據(jù)的尾,這就形成沾包問(wèn)題。

但如果一次請(qǐng)求發(fā)送的數(shù)據(jù)量比較大,超過(guò)了緩沖區(qū)大小,TCP 就會(huì)將其拆分為多次發(fā)送,這就是拆包問(wèn)題,也就是將一個(gè)大的包拆分為多個(gè)小包進(jìn)行發(fā)送,接收端接收到多個(gè)包才能組成一個(gè)完整數(shù)據(jù)。

為什么UDP沒(méi)有粘包

粘包/拆包問(wèn)題在數(shù)據(jù)鏈路層、網(wǎng)絡(luò)層以及傳輸層都有可能發(fā)生。日常的網(wǎng)絡(luò)應(yīng)用開(kāi)發(fā)大都在傳輸層進(jìn)行,由于UDP有消息保護(hù)邊界,不會(huì)發(fā)生粘包/拆包問(wèn)題。

而TCP是面向字節(jié)流,沒(méi)有邊界,操作系統(tǒng)在發(fā)送 TCP 數(shù)據(jù)的時(shí)候,底層會(huì)有一個(gè)緩沖區(qū),通過(guò)這個(gè)緩沖區(qū)來(lái)進(jìn)行優(yōu)化,例如緩沖區(qū)為1024個(gè)字節(jié)大小,如果一次發(fā)送數(shù)據(jù)量小于1024,則會(huì)合并多個(gè)數(shù)據(jù)作為一個(gè)數(shù)據(jù)包發(fā)送;如果一次發(fā)送數(shù)據(jù)量大于1024,則會(huì)將這個(gè)包拆分成多個(gè)數(shù)據(jù)包進(jìn)行發(fā)送。上述兩種情況也是沾包和拆包問(wèn)題。

上圖出現(xiàn)的四種情況包括:

  • 正常發(fā)送,兩個(gè)包恰好滿足TCP緩沖區(qū)的大小或達(dá)到TCP等待時(shí)長(zhǎng),分別發(fā)送兩個(gè)包。
  • 沾包:D1、D2都過(guò)小,兩者進(jìn)行了沾包處理。
  • 拆包沾包:D2過(guò)大,進(jìn)行了拆包處理,而拆出去的一部分D2_1又與D1進(jìn)行粘包處理。
  • 沾包拆包:D1過(guò)大,進(jìn)行了拆包處理,而拆出去的一部分D1_2又與D2進(jìn)行粘包處理。

解決方案

對(duì)于粘包和拆包問(wèn)題,通??梢允褂眠@四種解決方案:

  • 使用固定數(shù)據(jù)長(zhǎng)度進(jìn)行發(fā)送,發(fā)送端將每個(gè)包都封裝成固定的長(zhǎng)度,比如100字節(jié)大小。如果不足100字節(jié)可通過(guò)補(bǔ)0等填充到指定長(zhǎng)度再發(fā)送。
  • 發(fā)送端在每個(gè)包的末尾使用固定的分隔符,例如##@##。如果發(fā)生拆包需等待多個(gè)包發(fā)送過(guò)來(lái)之后再找到其中的##@##進(jìn)行合并。如果發(fā)送沾包則找到其中的##@##進(jìn)行拆分。
  • 將消息分為頭部和消息體,頭部中保存整個(gè)消息的長(zhǎng)度,這種情況下接收端只有在讀取到足夠長(zhǎng)度的消息之后,才算是接收到一個(gè)完整的消息。
  • 通過(guò)自定義協(xié)議進(jìn)行粘包和拆包的處理。

Netty拆包沾包處理

Netty對(duì)解決粘包和拆包的方案做了抽象,提供了一些解碼器(Decoder)來(lái)解決粘包和拆包的問(wèn)題。如:

LineBasedFrameDecoder:以行為單位進(jìn)行數(shù)據(jù)包的解碼,使用換行符\n或者\r\n作為依據(jù),遇到\n或者\r\n都認(rèn)為是一條完整的消息。

DelimiterBasedFrameDecoder:以特殊的符號(hào)作為分隔來(lái)進(jìn)行數(shù)據(jù)包的解碼。 FixedLengthFrameDecoder:以固定長(zhǎng)度進(jìn)行數(shù)據(jù)包的解碼。

LenghtFieldBasedFrameDecode:適用于消息頭包含消息長(zhǎng)度的協(xié)議(最常用)。

基于Netty進(jìn)行網(wǎng)絡(luò)讀寫(xiě)的程序,可以直接使用這些Decoder來(lái)完成數(shù)據(jù)包的解碼。對(duì)于高并發(fā)、大流量的系統(tǒng)來(lái)說(shuō),每個(gè)數(shù)據(jù)包都不應(yīng)該傳輸多余的數(shù)據(jù)(所以補(bǔ)齊的方式不可?。?code>LenghtFieldBasedFrameDecode更適合這樣的場(chǎng)景。

LineBasedFrameDecoder

使用LineBasedFrameDecoder解決粘包問(wèn)題,其會(huì)根據(jù)"\n"或"\r\n"對(duì)二進(jìn)制數(shù)據(jù)進(jìn)行拆分,封裝到不同的ByteBuf實(shí)例中

    /**
     * 服務(wù)啟動(dòng)器
     *
     * @return
     */
    @Bean
    public ServerBootstrap serverBootstrap() {
        ServerBootstrap serverBootstrap = new ServerBootstrap()
                // 指定使用的線程組
                .group(boosGroup(), workerGroup())
                // 指定使用的通道
                .channel(NioServerSocketChannel.class)
                // 指定連接超時(shí)時(shí)間
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, nettyProperties.getTimeout())          
                // 通過(guò)換行符處理沾包/拆包
                .childHandler(new NettyServerLineBasedHandler());
        return serverBootstrap;
    }
public class NettyServerLineBasedHandler extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();
        // 使用LineBasedFrameDecoder解決粘包問(wèn)題,其會(huì)根據(jù)"\n"或"\r\n"對(duì)二進(jìn)制數(shù)據(jù)進(jìn)行拆分,封裝到不同的ByteBuf實(shí)例中,并且每次查找的最大長(zhǎng)度為1024字節(jié)
        pipeline.addLast(new LineBasedFrameDecoder(1024, true, true));
        // 將上一步解碼后的數(shù)據(jù)轉(zhuǎn)碼為Message實(shí)例
        pipeline.addLast(new MessageDecodeHandler());
        // 對(duì)發(fā)送客戶端的數(shù)據(jù)進(jìn)行編碼
        pipeline.addLast(new MessageEncodeHandler());
        // 對(duì)數(shù)據(jù)進(jìn)行最終處理
        pipeline.addLast(new ServerListenerHandler());
    }
}

DelimiterBasedFrameDecoder

以特殊的符號(hào)作為分隔來(lái)進(jìn)行數(shù)據(jù)包的解碼,上文中就是以##@##作為分割符作為示例展開(kāi)講解的。這里再粘貼一下關(guān)鍵代碼: 使用DelimiterBasedFrameDecoder處理拆包/沾包,并且每次查找的最大長(zhǎng)度為1024字節(jié)。

    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        // 數(shù)據(jù)分割符
        String delimiterStr = "##@##";
        ByteBuf delimiter = Unpooled.copiedBuffer(delimiterStr.getBytes());
        ChannelPipeline pipeline = socketChannel.pipeline();
        // 使用自定義分隔符處理拆包/沾包,并且每次查找的最大長(zhǎng)度為1024字節(jié)
        pipeline.addLast(new DelimiterBasedFrameDecoder(1024, delimiter));
        // 將上一步解碼后的數(shù)據(jù)轉(zhuǎn)碼為Message實(shí)例
        pipeline.addLast(new MessageDecodeHandler());
        // 對(duì)發(fā)送客戶端的數(shù)據(jù)進(jìn)行編碼,并添加數(shù)據(jù)分隔符
        pipeline.addLast(new MessageEncodeHandler(delimiterStr));
        // 對(duì)數(shù)據(jù)進(jìn)行最終處理
        pipeline.addLast(new ServerListenerHandler());
    }

MessageEncodeHandler對(duì)發(fā)送數(shù)據(jù)進(jìn)行添加分割符并編碼操作

public class MessageEncodeHandler extends MessageToByteEncoder<Message> {
    // 數(shù)據(jù)分割符
    String delimiter;
    public MessageEncodeHandler(String delimiter) {
        this.delimiter = delimiter;
    }
    @Override
    protected void encode(ChannelHandlerContext channelHandlerContext, Message message, ByteBuf out) throws Exception {
        out.writeBytes((message.toJsonString() + delimiter).getBytes(CharsetUtil.UTF_8));
    }
}

FixedLengthFrameDecoder

服務(wù)端代碼設(shè)置,在NettyConfig配置中將worker處理器改為NettyServerFixedLengthHandler,使用固定100字節(jié)長(zhǎng)度處理消息。

    /**
     * 服務(wù)啟動(dòng)器
     *
     * @return
     */
    @Bean
    public ServerBootstrap serverBootstrap() {
        ServerBootstrap serverBootstrap = new ServerBootstrap()
                // 指定使用的線程組
                .group(boosGroup(), workerGroup())
                // 指定使用的通道
                .channel(NioServerSocketChannel.class)
                // 指定連接超時(shí)時(shí)間
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, nettyProperties.getTimeout())
                // 指定為固定長(zhǎng)度字節(jié)的處理器
                .childHandler(new NettyServerFixedLengthHandler());
        return serverBootstrap;
    }

NettyServerFixedLengthHandler類代碼,使用FixedLengthFrameDecoder設(shè)置按固定100字節(jié)數(shù)去拆分接收到的ByteBuf。并自定義一個(gè)消息編碼器,對(duì)字節(jié)長(zhǎng)度不足100字節(jié)的消息進(jìn)行補(bǔ)0操作。

public class NettyServerFixedLengthHandler extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        // 固定字節(jié)長(zhǎng)度
        Integer length = 100;
        ChannelPipeline pipeline = socketChannel.pipeline();
        // 按固定100字節(jié)數(shù)拆分接收到的ByteBuf的解碼器
        pipeline.addLast(new FixedLengthFrameDecoder(length));
        // 將上一步解碼后的數(shù)據(jù)轉(zhuǎn)碼為Message實(shí)例
        pipeline.addLast(new MessageDecodeHandler());
        // 對(duì)發(fā)送客戶端的數(shù)據(jù)進(jìn)行自定義編碼,并設(shè)置字節(jié)長(zhǎng)度不足補(bǔ)0
        pipeline.addLast(new MessageEncodeFixedLengthHandler(length));
        // 對(duì)數(shù)據(jù)進(jìn)行最終處理
        pipeline.addLast(new ServerListenerHandler());
    }
}

自定義MessageEncodeFixedLengthHandler編碼類,使用固定字節(jié)長(zhǎng)度編碼消息,字節(jié)長(zhǎng)度不足時(shí)補(bǔ)0。

public class MessageEncodeFixedLengthHandler extends MessageToByteEncoder<Message> {
    private int length;
    public MessageEncodeFixedLengthHandler(int length) {
        this.length = length;
    }
    /**
     * 使用固定字節(jié)長(zhǎng)度編碼消息,字節(jié)長(zhǎng)度不足時(shí)補(bǔ)0
     *
     * @param ctx the {@link ChannelHandlerContext} which this {@link MessageToByteEncoder} belongs to
     * @param msg the message to encode
     * @param out the {@link ByteBuf} into which the encoded message will be written
     * @throws Exception
     */
    @Override
    protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws Exception {
        String jsonStr = msg.toJsonString();
        // 如果長(zhǎng)度不足,則進(jìn)行補(bǔ)0
        if (jsonStr.length() < length) {
            jsonStr = addSpace(jsonStr);
        }
        // 使用Unpooled.wrappedBuffer實(shí)現(xiàn)零拷貝,將字符串轉(zhuǎn)為ByteBuf
        ctx.writeAndFlush(Unpooled.wrappedBuffer(jsonStr.getBytes()));
    }
    /**
     * 如果沒(méi)有達(dá)到指定長(zhǎng)度進(jìn)行補(bǔ)0
     *
     * @param msg
     * @return
     */
    private String addSpace(String msg) {
        StringBuilder builder = new StringBuilder(msg);
        for (int i = 0; i < length - msg.length(); i++) {
            builder.append(0);
        }
        return builder.toString();
    }
}

LenghtFieldBasedFrameDecode

LenghtFieldBasedFrameDecode適用于消息頭包含消息長(zhǎng)度的協(xié)議,根據(jù)消息長(zhǎng)度判斷是否讀取完一個(gè)數(shù)據(jù)包。

    /**
     * 服務(wù)啟動(dòng)器
     *
     * @return
     */
    @Bean
    public ServerBootstrap serverBootstrap() {
        ServerBootstrap serverBootstrap = new ServerBootstrap()
                // 指定使用的線程組
                .group(boosGroup(), workerGroup())
                // 指定使用的通道
                .channel(NioServerSocketChannel.class)
                // 指定連接超時(shí)時(shí)間
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, nettyProperties.getTimeout())
                // 請(qǐng)求頭包含數(shù)據(jù)長(zhǎng)度
                .childHandler(new NettyServerLenghtFieldBasedHandler());
        return serverBootstrap;
    }
public class NettyServerLenghtFieldBasedHandler extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();
        // 請(qǐng)求頭包含數(shù)據(jù)長(zhǎng)度,根據(jù)長(zhǎng)度進(jìn)行沾包拆包處理
        /**
         * maxFrameLength:指定了每個(gè)包所能傳遞的最大數(shù)據(jù)包大小;
         * lengthFieldOffset:指定了長(zhǎng)度字段在字節(jié)碼中的偏移量;
         * lengthFieldLength:指定了長(zhǎng)度字段所占用的字節(jié)長(zhǎng)度;
         * lengthAdjustment:對(duì)一些不僅包含有消息頭和消息體的數(shù)據(jù)進(jìn)行消息頭的長(zhǎng)度的調(diào)整,這樣就可以只得到消息體的數(shù)據(jù),這里的lengthAdjustment指定的就是消息頭的長(zhǎng)度;
         * initialBytesToStrip:對(duì)于長(zhǎng)度字段在消息頭中間的情況,可以通過(guò)initialBytesToStrip忽略掉消息頭以及長(zhǎng)度字段占用的字節(jié)。
         */
        pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 0, 2, 0, 2));
        // 在請(qǐng)求頭添加字節(jié)長(zhǎng)度字段
        pipeline.addLast(new LengthFieldPrepender(2));
        // 將上一步解碼后的數(shù)據(jù)轉(zhuǎn)碼為Message實(shí)例
        pipeline.addLast(new MessageDecodeHandler());
        // 對(duì)發(fā)送客戶端的數(shù)據(jù)進(jìn)行編碼,字節(jié)長(zhǎng)度不足補(bǔ)0
        pipeline.addLast(new MessageEncodeHandler());
        // 對(duì)數(shù)據(jù)進(jìn)行最終處理
        pipeline.addLast(new ServerListenerHandler());
    }
}

總結(jié)

造成TCP協(xié)議粘包/拆包問(wèn)題的原因是TCP協(xié)議數(shù)據(jù)傳輸是基于字節(jié)流的,它不包含消息、數(shù)據(jù)包等概念,是無(wú)界的,需要應(yīng)用層協(xié)議自己設(shè)計(jì)消息的邊界,即消息幀(Message Framing)。如果應(yīng)用層協(xié)議沒(méi)有使用基于長(zhǎng)度或者基于分隔符(終結(jié)符)劃分邊界等方式進(jìn)行處理,則會(huì)導(dǎo)致多個(gè)消息的粘包和拆包。

以上就是Netty 拆包沾包問(wèn)題解決方案示例的詳細(xì)內(nèi)容,更多關(guān)于Netty 拆包沾包解決方案的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Java StringBuffer類與StringBuilder類用法實(shí)例小結(jié)

    Java StringBuffer類與StringBuilder類用法實(shí)例小結(jié)

    這篇文章主要介紹了Java StringBuffer類與StringBuilder類用法,結(jié)合實(shí)例形式總結(jié)分析了Java StringBuffer類與StringBuilder類的功能、原理及添加、刪除、替換、截取等操作實(shí)現(xiàn)技巧,需要的朋友可以參考下
    2019-03-03
  • java題解LeetCode20.有效的括號(hào)

    java題解LeetCode20.有效的括號(hào)

    這篇文章主要為大家介紹了java題解LeetCode20.有效的括號(hào)示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-10-10
  • Spring Boot整合MyBatis-Plus實(shí)現(xiàn)CRUD操作的示例代碼

    Spring Boot整合MyBatis-Plus實(shí)現(xiàn)CRUD操作的示例代碼

    本文主要介紹了Spring Boot整合MyBatis-Plus實(shí)現(xiàn)CRUD操作,可以快速實(shí)現(xiàn)數(shù)據(jù)庫(kù)的增刪改查操作,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2025-04-04
  • Java簡(jiǎn)單工廠模式詳細(xì)解釋

    Java簡(jiǎn)單工廠模式詳細(xì)解釋

    本文主要介紹了JAVA簡(jiǎn)單工廠模式(從現(xiàn)實(shí)生活角度理解代碼原理)的相關(guān)知識(shí)。具有很好的參考價(jià)值。下面跟著小編一起來(lái)看下吧
    2021-11-11
  • 詳解如何給Sprintboot應(yīng)用添加插件機(jī)制

    詳解如何給Sprintboot應(yīng)用添加插件機(jī)制

    這篇文章主要為大家介紹了如何給 Sprintboot 應(yīng)用添加插件機(jī)制,文中有詳細(xì)的解決方案及示例代碼,具有一定的參考價(jià)值,需要的朋友可以參考下
    2023-08-08
  • 詳解JVM基礎(chǔ)之字節(jié)碼的增強(qiáng)技術(shù)

    詳解JVM基礎(chǔ)之字節(jié)碼的增強(qiáng)技術(shù)

    字節(jié)碼增強(qiáng)技術(shù)就是一類對(duì)現(xiàn)有字節(jié)碼進(jìn)行修改或者動(dòng)態(tài)生成全新字節(jié)碼文件的技術(shù)。接下來(lái),我們將從最直接操縱字節(jié)碼的實(shí)現(xiàn)方式開(kāi)始深入進(jìn)行剖析,感興趣的可以了解一下
    2022-10-10
  • java 自動(dòng)生成略縮圖示例代碼

    java 自動(dòng)生成略縮圖示例代碼

    本篇文章,在前輩的經(jīng)驗(yàn)基礎(chǔ)上,分別對(duì)單圖生成略縮圖和批量生成略縮圖做個(gè)小結(jié)
    2013-07-07
  • springboot斷言異常封裝與統(tǒng)一異常處理實(shí)現(xiàn)代碼

    springboot斷言異常封裝與統(tǒng)一異常處理實(shí)現(xiàn)代碼

    異常處理其實(shí)一直都是項(xiàng)目開(kāi)發(fā)中的大頭,但關(guān)注異常處理的人一直都特別少,下面這篇文章主要給大家介紹了關(guān)于springboot斷言異常封裝與統(tǒng)一異常處理的相關(guān)資料,需要的朋友可以參考下
    2023-01-01
  • Spring Cloud Alibaba使用Sentinel實(shí)現(xiàn)接口限流

    Spring Cloud Alibaba使用Sentinel實(shí)現(xiàn)接口限流

    這篇文章主要介紹了Spring Cloud Alibaba使用Sentinel實(shí)現(xiàn)接口限流,本文詳細(xì)的介紹了Sentinel組件的用法以及接口限流,感興趣的可以了解一下
    2019-04-04
  • Mybatis開(kāi)啟控制臺(tái)打印sql語(yǔ)句方式

    Mybatis開(kāi)啟控制臺(tái)打印sql語(yǔ)句方式

    這篇文章主要介紹了Mybatis開(kāi)啟控制臺(tái)打印sql語(yǔ)句方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-07-07

最新評(píng)論

略阳县| 雅安市| 卢龙县| 左云县| 广饶县| 黄冈市| 巴林左旗| 西华县| 禹州市| 鹤壁市| 宁强县| 青海省| 醴陵市| 麻栗坡县| 胶州市| 台南县| 徐闻县| 泸西县| 台湾省| 汝城县| 如皋市| 鄂尔多斯市| 绥棱县| 呼伦贝尔市| 濉溪县| 白河县| 罗江县| 额济纳旗| 昂仁县| 通许县| 潞城市| 县级市| 教育| 中西区| 涪陵区| 淮滨县| 涡阳县| 吴江市| 芮城县| 武宣县| 遵义市|