Skip to content

netty 自定义协议粘包拆包解决

目前所在的项目中需要与电力设备的采集终端进行通讯.

相关的协议为376.1,根据规划及相关的参考数据,最终决定使用netty作为scoket框架.

解决相关的高并发问题.

在实现的过程中,由于硬件端不可控,可能是很多的家的设备,且实际的网络情况不是特别稳定。所以兼容性尤其显得重要。

这里就需要解决netty的自定义协议的粘包拆包。

 

TCP粘包/拆包解决办法

1-定长消息,例如每个报文长度固定,不够补空格

2-使用回车换行符分割,在包尾加上分割符,例如Ftp协议

3-消息分割,头为长度(消息总长度或消息体长度),通常头用一个int32表示

4-复杂的应用层协议

最终的解决代码附上:

1. 解码器继承 ByteToMessageDecoder

 

protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
// 可读长度必须大于基本长度
if (byteBuf.readableBytes() > HEADER_SIZE) {
 //超过最大长度
 if (byteBuf.readableBytes() > 16391) {
 byteBuf.skipBytes(byteBuf.readableBytes());
 }
 //寻找包头
 while (true){
 //记录数据包开始的指针位置
 byteBuf.markReaderIndex();
 int head=byteBuf.readByte()& 0xFF;
 //判断是否是属于包头
 if(head == HEAD){
 break;
 }
 //不是包头
 //指针复位
 byteBuf.resetReaderIndex();
 // 缓存指针向后移动一个字节
 byteBuf.readByte();
 //判断当前缓存是否依然满足一条指令的最小长度
 if(byteBuf.readableBytes() < HEADER_SIZE){
 return;
 }
 }
 //成功找到包头
 byte lengthA = byteBuf.readByte();
 byte lengthB = byteBuf.readByte();
 byte lengthC = byteBuf.readByte();
 byte lengthD = byteBuf.readByte();
 //比较是否相等
 if(lengthC==lengthA&&lengthD==lengthB){
 int lenB= lengthB & 0xFF;
 int lenA= lengthA & 0xFF;
 //颠倒
 String lentCode=Integer.toHexString(lenB)+Integer.toHexString(lenA);
 //转换为二进制
 String binLen=Integer.toBinaryString(Integer.valueOf(lentCode,16));
 //转换为十进制
 int msgLength=Integer.valueOf(binLen.substring(0,binLen.length()-2),2)+8;

 //复位到最后一次标记的地方
 byteBuf.resetReaderIndex();
 //判断当前数据是否已经到齐
 if(byteBuf.readableBytes() < msgLength){
 //重新读取
 //等待消息完成
 System.out.println(new Date()+" "+Thread.currentThread().getName()+" 长度不对 当前长度为: " + byteBuf.readableBytes()+" 需求长度为:"+msgLength);
 return;
 }
 //读取消息内容
 byte[] bytes = new byte[msgLength];
 byteBuf.readBytes(bytes);
 //构造对象
 list.add( new Message(bytes));

 }
}

4 Comments

  1. JokerChan JokerChan

    //不是包头
    //指针复位
    byteBuf.resetReaderIndex();
    // 缓存指针向后移动一个字节
    byteBuf.readByte();

    缓存指针向后移动一个字节这一步是否有必要?请指点下

    • 这里其实需要解决的问题是,当读取到的数据不是头HEAD 之后,这个时候恢复到之前的开始读的地方,byteBuf.markReaderIndex();
      通过byteBuf.resetReaderIndex(); 这里来恢复到位置,然后通过byteBuf.readByte();进行废弃操作,也就是这一位是无效位。
      下一句的 byteBuf.readableBytes() 获取长度 是为了确认数据长度是否达到可解析的状态,如果小于最低长度。

      当然如果不进行 复位 读取一个byte ,这里就需要保证你数据无法达到解析长度时(这个时候,读取的不会再恢复了。等同丢弃),你之前的读取的数据是能被丢弃的。

  2. cl cl

    这句是做什么用的

    //超过最大长度
    if (byteBuf.readableBytes() > 16391) {
    byteBuf.skipBytes(byteBuf.readableBytes());
    }

    • 如果你待解析的报文长度大于你最大报文的长度,你是很难知道哪一段是你需要的内容,常规都是选择放弃,通过补偿(重发等)方式解决。

JokerChan进行回复 取消回复

电子邮件地址不会被公开。 必填项已用*标注