基于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(); }