一个TCP长连接设备管理后台工程(五)---帧过滤器

本文介绍了如何设计并实现一个TCP长连接设备管理后台的帧过滤器,处理TCP通讯中的拆包和粘包问题。通过定义数据包结构,实现单帧和多帧过滤,确保正确解析JTT808协议数据。
摘要由CSDN通过智能技术生成

帧过滤器

帧过滤器的作用就是,从接收到的buff中,过滤出有效的完整jtt808数据包。由于是tcp通讯,那么这其中不可避免的会涉及到数据包的两个常规处理:拆包和粘包。

拆包和粘包的简要说明:

假设客户端分别发送了两个数据包D1和D2给服务端,由于服务端一次读取到的字节数是不确定的,故可能存在以下4种情况。

(1)服务端分两次读取到了两个独立的数据包,分别是D1和D2,没有粘包和拆包;

(2)服务端一次接收到了两个数据包,D1和D2粘合在一起,被称为TCP粘包;

(3)服务端分两次读取到了两个数据包,第一次读取到了完整的D1包和D2包的部分内容,第二次读取到了D2包的剩余内容,这被称为TCP拆包;

(4)服务端分两次读取到了两个数据包,第一次读取到了D1包的部分内容D1_1,第二次读取到了D1包的剩余内容D1_2和D2包的整包。

如果此时服务端TCP接收滑窗非常小,而数据包D1和D2比较大,很有可能会发生第五种可能,即服务端分多次才能将D1和D2包接收完全,期间发生多次拆包。

拆包与粘包的说明网上资料很多,此处不做过多说明。但是我们设计出来的过滤器,要能够正常应对拆包和粘包的情况。

首先,我们根据jtt808协议定义我们的数据包结构:

type MultiField struct {
   
	MsgSum   uint16
	MsgIndex uint16
}

type Header struct {
   
	MID       uint16
	Attr      uint16
	Version   uint8
	PhoneNum  string
	SeqNum    uint16
	MutilFlag MultiField
}

type Message struct {
   
	HEADER Header
	BODY   []byte
}

由于Attr其实是由多个位域字段组成,所以我们再定义三个函数:

func (h *Header) IsMulti() bool {
   
	if ((h.Attr >> 12) & 0x0001) > 0 {
   
		return true
	}
	return false
}

//BodyLen is a function for get body len
func (h *Header) BodyLen() int {
   
	return int(h.Attr & 0x03ff)
}

//MakeAttr is generate attr
func MakeAttr(verFlag byte, mut bool, enc byte, lens uint16) uint16 {
   
	attr := lens & 0x03FF

	if verFlag > 0 {
   
		attr = attr & 0x4000
	}

	if mut {
   
		attr = attr & 0x2000
	}

	encMask := (uint16(enc) & 0x0007) << 10
	return attr + encMask
}

由于要考虑拆包和粘包问题,所以我们的过滤器需要能够同时分析多包数据,但是基本单元函数分析一帧数据,所以我们先实现一帧数据的过滤器:filterSigle

我们想要的过滤器原型是如下的一个函数:

func filterSigle(data []byte) (Message, int, error)

该函数接收一个存有从tcp端接收的数据流切片,需要返回我们解析出来的Message、解析过后消耗的字节数,错误信息。

很明显,这个返回的消耗字节数就是为了应对拆包和粘包用的。

我们知道jtt808协议是以0x7e开始和结尾的,我们定义一个常量:

const (
	ProtoHeader byte = 0x7e
)

filterSigle的第一个逻辑就是要识别帧头和帧尾了:

var usedLen int = 0

startindex := bytes.IndexByte(data, ProtoHeader)
if startindex >= 0 {
   
    usedLen = startindex + 1
    endindex := bytes.IndexByte(data[usedLen:], ProtoHeader)
    if endindex >= 0 {
   
        endindex = endindex + usedLen
    }
}

此处的帧头和帧尾索引均相对于data的起始字节而言。理想的情况下,在startindex和endindex之间的数据就是我们需要解析的数据,所以之后的解析都是对这部分数据进行分析。我们对这部分的逻辑单独用一个函数来处理,这个函数主要完成三个逻辑:转义、校验检查和解析

func frameParser(data []byte) (Message, error) {
   

}

data入参从startindex到endindex,不包括startindex和endindex。

在转义之前,首先判断基本的长度。通过对帧头固定字段分析可以知道,消息头部分的长度为17或者19个字节,加上帧头帧尾校验位的话最小就是17+3=20个字节,鉴于BODY部分可能为空,所以整个帧最小长度应该为20字节:

if len(data)+2 < 17+3 {
   
    return Message{
   }, fmt.Errorf("header is too short")
}

转义比较简单,为了代码复用,定义成一个函数:

func Escape(data, oldBytes, newBytes []byte) []byte {
   
	buff := make([]byte, 0)

	var startindex int = 0

	for startindex < len(data) {
   
		index := bytes.Index(data[startindex:], oldBytes)
		if index >= 0 {
   
			buff = append(buff, data[startindex:index]...)
			buff = append(buff, newBytes
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值