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
网友评论