Netty4 學習筆記之三:粘包和拆包

前言

上一篇Netty 心跳 demo 中,瞭解了Netty中的客戶端和服務端之間的心跳。這篇就來說講Netty中的粘包和拆包以及相應的處理。java

名詞解釋

粘包: 會將消息粘粘起來發送。相似吃米飯,一口吃多個飯粒,而不是一粒一粒的吃。
拆包: 會將消息拆開,分爲屢次接受。相似喝飲料,一口一口的喝,而不是一口氣喝完。git

簡單的來講:
屢次發送較少內容,會發生粘包現象。
單次發送內容過多,會發生拆包現象。github

咱們使用簡單的Netty的服務端和客戶端demo用來測試粘包和拆包。
Hello Netty 發送一百次,就會發送粘包現象;
將《春江花月夜》和《行路難》發送一次就會發送拆包現象;bootstrap

示例圖:

粘包:

這裏寫圖片描述

拆包:

這裏寫圖片描述

解決粘包、拆包

由於Netty已經提供了幾個經常使用的解碼器,幫助咱們解決這些問題,因此咱們沒必要再去造輪子了,直接拿來用就行了。服務器

解決粘包

在Server服務端使用定長數據幀的解碼器 FixedLengthFrameDecoder以後。
能夠明顯看到數據已經按照咱們所設定的大小分割了。
這裏寫圖片描述markdown

解決拆包

在Server服務端使用字節解碼器 LineBasedFrameDecoder 以後。
因爲字節已經超過咱們設置的最大的字節數,因此報錯了。
這裏寫圖片描述socket

因此,咱們發送的字節在設置的範圍內的話,就能夠看到拆包現象已經解決。
這裏寫圖片描述ide

Netty還提供了一個HttpObjectAggregator類用於解決粘包、拆包現象。
如下摘自Netty官方文檔oop

若是對於單條HTTP消息你不想處理多個消息對象,你能夠傳入 HttpObjectAggregator 到pipline中。HttpObjectAggregator 會將多個消息對象轉變爲單個 FullHttpRequest 或者 FullHttpResponse。

使用HttpObjectAggregator 以後
這裏寫圖片描述測試

這裏寫圖片描述

能夠看到,粘包和拆包現象獲得了改善。

那麼開始貼代碼,幾乎和以前的demo同樣。

服務端:

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

/** * * Title: NettyServer * Description: Netty服務端 * Version:1.0.0 * @author pancm * @date 2017年10月8日 */
public class NettyServer {
        private static final int port = 6789; //設置服務端端口
        private static  EventLoopGroup group = new NioEventLoopGroup();   // 經過nio方式來接收鏈接和處理鏈接 
        private static  ServerBootstrap b = new ServerBootstrap();

        /** * Netty建立所有都是實現自AbstractBootstrap。 * 客戶端的是Bootstrap,服務端的則是 ServerBootstrap。 **/
        public static void main(String[] args) throws InterruptedException {
            try {
                b.group(group);
                b.channel(NioServerSocketChannel.class);
                b.childHandler(new NettyServerFilter()); //設置過濾器
                // 服務器綁定端口監聽
                ChannelFuture f = b.bind(port).sync();
                System.out.println("服務端啓動成功,端口是:"+port);
                // 監聽服務器關閉監聽
                f.channel().closeFuture().sync();
            }catch(Exception e){
                e.printStackTrace();
            }
            finally {
                group.shutdownGracefully(); //關閉EventLoopGroup,釋放掉全部資源包括建立的線程 
            }
        }
}
mport io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

/** * * Title: HelloServerInitializer * Description: Netty 服務端過濾器 * Version:1.0.0 * @author pancm * @date 2017年10月8日 */
public class NettyServerFilter extends ChannelInitializer<SocketChannel> {

     @Override
     protected void initChannel(SocketChannel ch) throws Exception {
         ChannelPipeline ph = ch.pipeline();
         // 解碼和編碼,應和客戶端一致
// ph.addLast(new FixedLengthFrameDecoder(100)); //定長數據幀的解碼器 ,每幀數據100個字節就切分一次。 用於解決粘包問題 
// ph.addLast(new LineBasedFrameDecoder(2048)); //字節解碼器 ,其中2048是規定一行數據最大的字節數。 用於解決拆包問題
         ph.addLast("aggregator", new HttpObjectAggregator(10*1024*1024)); 
         ph.addLast("decoder", new StringDecoder());
         ph.addLast("encoder", new StringEncoder());
         ph.addLast("handler", new NettyServerHandler());// 服務端業務邏輯
     }
 }
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

import java.net.InetAddress;

/** * * Title: HelloServerHandler * Description: 服務端業務邏輯 粘包、拆包測試 * Version:1.0.0 * @author pancm * @date 2017年10月8日 */
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
    /** 條數 */
    private int count=0; 
    /** * 業務邏輯處理 */
    @Override  
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {  
           String body = (String)msg;  
           System.out.println("接受的數據是: " + body + ";條數是: " + ++count); 
    }  

    /** * 創建鏈接時,返回消息 */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("鏈接的客戶端地址:" + ctx.channel().remoteAddress());
        ctx.writeAndFlush("客戶端"+ InetAddress.getLocalHost().getHostName() + "成功與服務端創建鏈接! ");
        super.channelActive(ctx);
    }
}

客戶端

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;

import java.io.IOException;
/** * * Title: NettyClient * Description: Netty客戶端 粘包、拆包測試 * Version:1.0.0 * @author pancm * @date 2017年10月16日 */
public class NettyClient {

    public static String host = "127.0.0.1";  //ip地址
    public static int port = 6789;          //端口
    /// 經過nio方式來接收鏈接和處理鏈接 
    private static EventLoopGroup group = new NioEventLoopGroup(); 
    private static  Bootstrap b = new Bootstrap();
    private static Channel ch;

    /** * Netty建立所有都是實現自AbstractBootstrap。 * 客戶端的是Bootstrap,服務端的則是 ServerBootstrap。 **/
    public static void main(String[] args) throws InterruptedException, IOException { 
            System.out.println("客戶端成功啓動...");
            b.group(group);
            b.channel(NioSocketChannel.class);
            b.handler(new NettyClientFilter()); 
            // 鏈接服務端
            ch = b.connect(host, port).sync().channel();
            star(3);
    }

    public static void star(int i) throws IOException{
        String str="春江潮水連海平,海上明月共潮生。"
                +" 灩灩隨波千萬裏,何處春江無月明! "
                +" 江流宛轉繞芳甸,月照花林皆似霰;"
                +" 空裏流霜不覺飛,汀上白沙看不見。"
                +" 江天一色無纖塵,皎皎空中孤月輪。"
                +" 江畔何人初見月?江月何年初照人?"
                +" 人生代代無窮已,江月年年望類似。"
                +" 不知江月待何人,但見長江送流水。"
                +" 白雲一片去悠悠,青楓浦上不勝愁。"
                +" 誰家今夜扁舟子?何處相思明月樓?"
                +" 可憐樓上月徘徊,應照離人妝鏡臺。"
                +" 玉戶簾中卷不去,搗衣砧上拂還來。"
                +" 此時相望不相聞,願逐月華流照君。"
                +" 鴻雁長飛光不度,魚龍潛躍水成文。"
                +" 昨夜閒潭夢落花,可憐春半不還家。"
                +" 江水流春去欲盡,江潭落月復西斜。"
                +" 斜月沉沉藏海霧,碣石瀟湘無限路。"
                +" 不知乘月幾人歸,落月搖情滿江樹。" 
                +" 噫籲嚱,危乎高哉!蜀道之難,難於上青天!蠶叢及魚鳧,開國何茫然!爾來四萬八千歲,不與秦塞通人煙。"
                +" 西當太白有鳥道,能夠橫絕峨眉巔。地崩山摧壯士死,而後天梯石棧相鉤連。上有六龍回日之高標,下有衝波逆折之回川。"
                +" 黃鶴之飛尚不得過,猿猱欲度愁攀援。青泥何盤盤,百步九折縈巖巒。捫參歷井仰脅息,以手撫膺坐長嘆。"
                +" 問君西遊什麼時候還?畏途巉巖不可攀。但見悲鳥號古木,雄飛雌從繞林間。又聞子規啼夜月,愁空山。"
                +" 蜀道之難,難於上青天,令人聽此凋朱顏!連峯去天不盈尺,枯鬆倒掛倚絕壁。飛湍瀑流爭喧豗,砯崖轉石萬壑雷。"
                +" 其險也如此,嗟爾遠道之人胡爲乎來哉!劍閣崢嶸而崔嵬,一夫當關,萬夫莫開。"
                +" 所守或匪親,化爲狼與豺。朝避猛虎,夕避長蛇;磨牙吮血,殺人如麻。錦城雖雲樂,不如早還家。"
                +" 蜀道之難,難於上青天,側身西望長諮嗟!";
        if(i==1){
            for(int j=0;j<100;j++){
                str="Hello Netty";
                ch.writeAndFlush(str);
            }
        }else if(i==2){
            str+=str;
            ch.writeAndFlush(str);
        }else if(i==3){
            //System.getProperty("line.separator") 結束標記
            byte [] bt=(str+System.getProperty("line.separator")).getBytes();
            ByteBuf message = Unpooled.buffer(bt.length);  
            message.writeBytes(bt);  
            ch.writeAndFlush(message);
        }


        System.out.println("客戶端發送數據:"+str+",發送數據的長度:"+str.length());
   }

}
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
/** * * Title: NettyClientFilter * Description: Netty客戶端 過濾器 * Version:1.0.0 * @author pancm * @date 2017年10月8日 */
public class NettyClientFilter extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline ph = ch.pipeline();
        /* * 解碼和編碼,應和服務端一致 * */
        ph.addLast("decoder", new StringDecoder());
        ph.addLast("encoder", new StringEncoder());
        ph.addLast("handler", new NettyClientHandler()); //客戶端的邏輯
    }
}
import java.util.Date;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

/** * * Title: NettyClientHandler * Description: 客戶端業務邏輯實現 * Version:1.0.0 * @author pancm * @date 2017年10月8日 */
public class NettyClientHandler extends ChannelInboundHandlerAdapter {

     /** * 業務邏輯處理 */
    @Override  
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {  
        System.out.println("客戶端接受的消息:"+msg);
    }  
    /** * 創建鏈接時 */
    @Override  
    public void channelActive(ChannelHandlerContext ctx) throws Exception {  
        System.out.println("創建鏈接時:"+new Date());  
        ctx.fireChannelActive();  
    }  

     /** * * 關閉鏈接時 */
    @Override  
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {  
        System.out.println("關閉鏈接時:"+new Date());  
    }  
}

該項目我放在github上了,有興趣的能夠看看!https://github.com/xuwujing/Netty
相關文章
相關標籤/搜索