Netty 拆包 丢包 过程分析

基础

正常流程:TCP缓存->Netty本地缓存->拆包器拆包->Handler处理封装好的数据包

测试代码:netty/demo/tcppackage

参考博文:http://www.jianshu.com/p/a0a51fd79f62

项目地址

如果不设置解码器

测试流程:

  1. 客户端连续发送n次18字节的数据

        @Override
        public void channelActive(ChannelHandlerContext ctx) {
            ByteBuf message = null;
            //连续发送100个包 56 一次  113两次  170 收三次
            for (int i = 0; i < 110; i++) {
                message = Unpooled.buffer(req.length);
                message.writeBytes(req);
                ctx.writeAndFlush(message);
            }
        }
  1. 服务端在channelRead 回调函数中处理接收的数据

    初始化本地缓存大小[1024] 1kb

.option(ChannelOption.SO_BACKLOG, 1024)

加断点测试:

        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws UnsupportedEncodingException {
[breakpoint]            ByteBuf buf = (ByteBuf) msg;
                        byte[] req = new byte[buf.readableBytes()];
                        buf.readBytes(req);
        }
  1. 测试结果

n = 56

channelRead 只执行 1 次

buf 数据长度为: 1008
buf 容量为: 1024

n = 110

channelRead 执行 2 次

第一次
buf 数据长度为: 1024
buf 容量为: 1024

第二次

buf 数据长度为: 956
buf 容量为: 1024

  1. 结论

channelRead的回调次数与发送端发送的数据有关

次数 = 数据总量/本地缓存size + 1

最后一次可以不满 1024

  1. 疑惑

接收端是怎么知道发送端的数据发完了的呢?

writeAndFlush 是将数据发送出去

以上的数据是连续的发送110次本地缓存会满1024,

如果不连续的发送会怎样呢?

隔1s 发送一次

@Override
        public void channelActive(ChannelHandlerContext ctx) {
            ByteBuf message = null;
            //连续发送100个包 56 一次  113两次  170 收三次
            for (int i = 0; i < 110; i++) {
                message = Unpooled.buffer(req.length);
                message.writeBytes(req);
                ctx.writeAndFlush(message);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

服务端接收结果:

channelRead数据长度:18
channelReadComplete1
channelRead数据长度:18
channelReadComplete2
channelRead数据长度:18
channelReadComplete3
channelRead数据长度:18
channelReadComplete4
channelRead数据长度:18
channelReadComplete5
channelRead数据长度:18
channelReadComplete6
channelRead数据长度:18
channelReadComplete7

对比连续发送110 次的结果


channelRead数据长度:1024
channelRead数据长度:956
channelReadComplete2

对比结果可以猜测数据流连续和不连续会有不同的结果
当发送端发送数据过快,channelReadComplete可能会很久才被调用一次
如果一次数据读取完毕之后(可能接收端一边收,发送端一边发,这里的读取完毕指的是接收端在某个时间不再接受到数据为止) 至少在1s已经超过了这个时间

设置解码器和编码器

  1. 给接收端添加一个字符串解码器
arg0.pipeline().addLast(new StringDecoder());

连续发送110次的结果

还是读了两次:channelReadComplete 2

唯一的区别是 msg 直接变成了 String类型 
  1. 在添加了字符串解码器的基础上添加一个拆包器

基于换行符的拆包器,最大长度为1024


arg0.pipeline().addLast(new LineBasedFrameDecoder(1024));

连续发送110次结果

数据内容:Query time order
channelRead数据长度:16
数据内容:Query time order
channelRead数据长度:16
数据内容:Query time order
channelRead数据长度:16
数据内容:Query time order
channelRead数据长度:16
数据内容:Query time order
channelRead数据长度:16
数据内容:Query time order
channelReadComplete110
  1. 结论

channelRead 接收的数据包的长度不是固定的1024了;

而是 分隔符 分割出来的数据包

回调中获取的字符串 不包含分隔符(2字节)

  1. 疑惑

如果一行数据超过了最大帧长会怎样

将发送端和接收端的最大帧长设置为10

An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception.
io.netty.handler.codec.TooLongFrameException: frame length (68) exceeds the allowed maximum (10)

需要拆包的长度 是否大于该拆包器允许的最大长度(maxLength),
这个参数在构造函数中被传递进来,如超出允许的最大长度,
就将这段数据抛弃,返回null;

数据长度(需要拆包的长度)是68 但是帧长(拆包器最大长度)10,这段数据被抛弃

将接收端的最大帧长调整为 68 正常;调整值小于68就异常了

将发送端的帧长设置为10对发送没有影响

如果一部分数据在拆包器允许的最大范围内,一部分超过了正常的,如何丢包?

修改接收端的最大帧长:20

arg0.pipeline().addLast(new LineBasedFrameDecoder(20));

让发送端连发三波数据:

        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            super.channelActive(ctx);
            ByteBuf message = null;
//          ========第一波数据==================
            String msg1 = "Query time order 1";
            message = Unpooled.buffer((msg1+ System.getProperty("line.separator")).getBytes().length);
            message.writeBytes((msg1 + System.getProperty("line.separator")).getBytes());
            ctx.writeAndFlush(message);
//          ========第二波数据===================
            StringBuilder stringBuilder = new StringBuilder();
            for (int i = 0; i < 6; i++) {
                stringBuilder.append("Query time order2");
            }

            String msg2 = stringBuilder.toString();
            message = Unpooled.buffer((msg2+ System.getProperty("line.separator")).getBytes().length);
            message.writeBytes((msg2 + System.getProperty("line.separator")).getBytes());
            ctx.writeAndFlush(message);

//          =========第三波数据=====================
            String msg3 = "Query time order 3";
            message = Unpooled.buffer((msg3+ System.getProperty("line.separator")).getBytes().length);
            message.writeBytes((msg3 + System.getProperty("line.separator")).getBytes());
            ctx.writeAndFlush(message);

        }

测试结果:


channelRead数据长度:18
数据内容:Query time order 1

六月 29, 2017 2:10:11 下午 io.netty.channel.DefaultChannelPipeline onUnhandledInboundException
警告: An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception.
io.netty.handler.codec.TooLongFrameException: frame length (102) exceeds the allowed maximum (20)

channelRead数据长度:18
数据内容:Query time order 3


channelReadComplete2

结论:自动丢包

可读字节长度:102 > 大于拆包器 最大长度20

102个字节自动进入丢包模式

丢包的原则:参考LineBasedFrameDecoder.java 源码

你可能感兴趣的:(web)