基于netty 报文的拆包粘包处理方法

一、拆包/粘包的问题

  正常情况下客户端发上来的报文都是单独,一条报文就是一个完善的。但是特殊情况下会出现2个报文粘在一起发上来。

  正常情况的报文:

  757200501011313130323630383237374137393738323030303532000000000000000055012238393836303242343130313638303038333035320000000000000000000000000000000000000000D58A

  7572 是帧起始序列,也就是包头

  0050 是报文的长度

  D58A 为CRC16 检验

  粘包:

        757200501011313130323630383237374137393738323030303532000000000000000055012238393836303242343130313638303038333035320000000000000000000000000000000000000000D58A757200501011313130323630383237374137393738323030303532000000000000000055

  这是报文第二条是个不完整的包,我们服务端需要做到将包拆成完整的包,并且第二个包需要等到下一条报文拼接成完整的包。

 

二、 netty的解决方案

  1.消息定长,报文大小固定长度,发送和接收方遵循相同的约定,这样即使粘包了通过接收方编程实现获取定长报文也能区分。

     2.包尾添加特殊分隔符,例如每条报文结束都添加回车换行符(例如FTP协议)或者指定特殊字符作为报文分隔符,接收方通过特殊分隔符切分报文区分。

        3.将消息分为消息头和消息体,消息头中包含表示信息的总长度(或者消息体长度)的字段

三、实现方法

  创建一个实现类继承netty的 MessageToMessageDecoder方法

public class MessageDecoder extends MessageToMessageDecoder<ByteBuf> {
    private byte[] remainingBytes;
    private static byte[] HEAD_DATA = new byte[]{0x75, 0x72}; //协议帧起始序列 7572 2个字节
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
        ByteBuf currBB = null;
        if(remainingBytes == null) {
            currBB = msg;
        }else {
            byte[] tb = new byte[remainingBytes.length + msg.readableBytes()];
            System.arraycopy(remainingBytes, 0, tb, 0, remainingBytes.length);
            byte[] vb = new byte[msg.readableBytes()];
            msg.readBytes(vb);
            System.arraycopy(vb, 0, tb, remainingBytes.length, vb.length);
            currBB = Unpooled.copiedBuffer(tb);
        }
        while(currBB.readableBytes() > 0) {
            if(!doDecode(ctx, currBB, out)) {
                break;
            }
        }
        if(currBB.readableBytes() > 0) {
            remainingBytes = new byte[currBB.readableBytes()];
            currBB.readBytes(remainingBytes);
        }else {
            remainingBytes = null;
        }
//        out.add(remainingBytes);
//        remainingBytes=null;
    }

    private boolean doDecode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) {
        if(msg.readableBytes() < 2)
            return false;
        msg.markReaderIndex();
        byte[] header = new byte[2];
        msg.readBytes(header);
        byte[] dataLength=new byte[2]; //报文的长度
        msg.readBytes(dataLength);
        if (!Arrays.equals(header, HEAD_DATA)) {
            return false;
           // throw new DecoderException("errorMagic: " + Arrays.toString(header));
        }
        int len = Integer.parseInt(DatatypeConverter.printHexBinary(dataLength), 16);
       // int len =msg.readInt();
        if(msg.readableBytes() < len-4) {
            msg.resetReaderIndex();
            return false;
        }
        msg.resetReaderIndex();
        byte[] body = new byte[len];
        msg.readBytes(body);
        out.add(Unpooled.copiedBuffer(body));
        if(msg.readableBytes() > 0)
            return true;
        return false;
    }
}

netty 客户端ChannelPipeline加入创建的 MessageDecoder 类

public synchronized boolean openDev() {
        if(isOpen()){
            return true;
        }
        if(group ==null){
            group =new NioEventLoopGroup();
        }
        Bootstrap b =new Bootstrap();
        final MessageHandler hander =new MessageHandler();
        hander.setMedia(this);
        final ServerHandler serverHandler =new ServerHandler();
        serverHandler.setMedia(this);
        b.handler(new HeartbeatHandlerInitializer(this));//心跳
        b.group(group).channel(NioSocketChannel.class).
                option(ChannelOption.TCP_NODELAY, true).
                handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        ChannelPipeline pipeline=socketChannel.pipeline();
                        //pipeline.addLast(new MessageEncoder());//协议编码器
                        pipeline.addLast(new MessageDecoder());//协议解码器
                        pipeline.addLast(hander);
                        pipeline.addLast(new ClientHandler(TcpClient.this));
                    }
                });
        try {
            f =b.connect(mediaPara.getIp(),mediaPara.getPort()).sync();
            f.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
            return false;
        }
        return channel.isActive();
    }

 

posted @ 2018-05-14 14:27  习惯性遗忘  阅读(4287)  评论(0编辑  收藏  举报