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
相关文章
相关标签/搜索