美文网首页
Netty处理半包粘包的解码思路

Netty处理半包粘包的解码思路

作者: 一只yami | 来源:发表于2022-11-23 15:33 被阅读0次

    Netty处理半包粘包的解码思路

    前言

    写netty通讯的都不免遇到半包粘包情况。
    那么半包粘包是什么意思呢?
    我们可以把客户端与服务端的网络通讯看作两个人聊天


    我们可以把客户端与服务端的网络通讯看作两个人聊天
    首先对于半包的情况

    假设A要告诉B 我喜欢唱跳RAP篮球
    由于某些不明原因,A无法一次性说完这句话,只说了 我喜欢唱跳,后面 RAP篮球 咽了一口口水才说出来
    上述这种情况就是半包,终端一次接收到的包不是完整的包,只有一部分,需要等待接收完毕


    另一种情况粘包
    A对B说 我喜欢唱跳RAP篮球music
    明显这里 唱跳RAP篮球的时候这句话就结束了,但是后面还有一句 music
    等于是两句话合到一块说了,我们需要断句分开处理
    上述情况就是粘包,终端一次收到的包是两包或者更多包,需要分割解析

    在物联网通讯中,硬件网络原因或者配置原因导致每次发送的报文不固定,半包粘包有可能同时存在是非常常见的。
    正确解析一包完整的报文对完成通讯任务不可或缺


    本次demo代码传送门:netty-decode
    直接上代码 看注释就完事

    import io.netty.buffer.ByteBuf;
    import io.netty.buffer.ByteBufUtil;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.handler.codec.ByteToMessageDecoder;
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.List;
    
    @Slf4j
    public class DemoDecoder extends ByteToMessageDecoder {
    
        // 最大包长
        public static final int MAX_BUFFER = 4096;
        // 包开始标识 55
        public static final byte HEAD = 0x55;
        // 包结束标识 45
        public static final byte TAIL = 0x45;
    
        @Override
        protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
            /*打印16进制数据*/
            log.info("hex data:[{}] , channel:[{}]", ByteBufUtil.hexDump(in).toUpperCase(), ctx.channel().id());
    
            if (in.readableBytes() > MAX_BUFFER) {
                // 当收到的包超过最大包长,认为是异常数据 直接丢弃
                in.skipBytes(in.readableBytes());
                // mark一下可读的位置 避免重置可读下标时重置到了认为要丢弃的部分
                in.markReaderIndex();
                return;
            }
    
            if (in.readByte() != HEAD) {
                // 如果不是开始符的包不接收
                in.skipBytes(in.readableBytes());
                in.markReaderIndex();
                return;
            }
    
            /*
             *   这里应该加私有协议的判断,走业务判断是不是一个正常的协议包,如果不是依旧像上面一样丢弃
             *   由于是demo就简单认为包是定长的 包括包头包尾一共 45个字节
             */
            if (in.readableBytes() < 44) {
                // 当前接收到的包大小不足一个完整的包 原因有很多网络问题网卡优化等,导致服务端收到的包是一个半包,并不完整
                // 此时需要返回等待接收剩余的包,不然业务层不能解析一个不完整的包
                in.resetReaderIndex();
                /*
                 *   由于没有在out add新对象
                 *   所以不会进入decode循环
                 *   下一次这个通道继续发数据时,会在当前in的基础上继续写入 直接读取in即可
                 */
                return;
            }
    
            if (in.readableBytes() >= 44) {
                // 当前接收到的包已经达到一个完整包的长度
                in.skipBytes(43);// 读取正文部分
                if (in.readByte() != TAIL) {
                    // 如果包尾不是45结束的,认为这不是一个正确的包 直接丢弃
                    in.skipBytes(in.readableBytes());
                    in.markReaderIndex();
                    return;
                } else {
                    // 简单的包头包尾校验完成后 可以把这一段数据转对象或者直接把bytebuf传给下一个处理器
                    in.resetReaderIndex(); //校验的时候读过了这个流需要重置才能重新读
    
                    ByteBuf byteBuf = in.readBytes(45); // 因为设定是45包长 所以直接读45
                    in.markReaderIndex();// 已经读到完整包了 mark一下防止下次reset又把读完的包再读一次
    
                    /*可能后面还有 out add过后会进入下一个循环继续读后面的*/
                    out.add(byteBuf);
                    return;
    
                }
            }
    
    
            /*通过 out.add 可以把这里处理好的数据传到下一个handler*//*
            out.add(in);
    
            *//*
             * 添加到out之后,in必须至少读1字节 防止由decoder引起的无限循环的机制
             * 这里全部读取当前流中所有字节
             * *//*
            in.skipBytes(in.readableBytes());*/
        }
    }
    
    

    测试

    设定完整16进制报文为
    55005B0114543A26030B2C55001D68001B0000001D680000000F01190250150A8A0000160B17023936D516E545
    55开头 45结尾

    模拟发包
    测试1:分三段

    channelFuture.channel().write(ByteUtils.getSendBuffer("55005B0114543A26030B2C55001D68"));
    channelFuture.channel().write(ByteUtils.getSendBuffer("001B0000001D680000000F01190250150A8A0000160B17023936D516E5"));
    channelFuture.channel().write(ByteUtils.getSendBuffer("45"));
    

    测试2:既有半包也有粘包

    channelFuture.channel().write(ByteUtils.getSendBuffer("55005B0114543A26030B2C55001D68"));
    channelFuture.channel().write(ByteUtils.getSendBuffer("001B0000001D680000000F01190250150A8A0000160B17023936D516E5"));
    channelFuture.channel().write(ByteUtils.getSendBuffer("4555005B0114543A26030B2C55001D68001B0000001D680000000F01190250150A8A0000160B17023936D516E5"));
    channelFuture.channel().writeAndFlush(ByteUtils.getSendBuffer("45"));
    

    实测发送了3包结果应该是客户端打印3个ok

    相关文章

      网友评论

          本文标题:Netty处理半包粘包的解码思路

          本文链接:https://www.haomeiwen.com/subject/hnuzxdtx.html