一个TCP长连接设备管理后台工程(四)---jtt808协议解析

协议解析

从前面内容我们可以发现,808协议是一个很典型的协议格式:

固定字段+变长字段

其中固定字段用来检测一个帧格式的完整性和有效性,所以一般会包含一下内容:帧头+变长字段对应的长度+校验。由于这一段的数据格式固定,目的单一,所以处理起来比较简单。

变长字段的长度是由固定字段终端某一个子字段的值决定的,而且这部分的格式比较多变,需要灵活处理。这一字段我们通常称为Body或者Apdu。

我们首先说明变长字段的处理流程。

Body处理

正因为Body字段格式灵活,所以为了提高代码的复用性和拓展性,我们需要对Body的处理机制进行抽象,提取出一个相对通用的接口出来。

有经验的工程师都知道,一个协议格式处理,无非就是编码和解码。编码我们称之为Marshal,解码我们称之为Unmarshal。对于不同的格式,我们只需要提供不同的Marshal和Unmarshal实现即可。

从前面分析可以知道,我们现在面对的一种格式是类似于Plain的格式,这种格式没有基本的分割符,下面我们就对这种编码来实现Marshal和Unmarshal。我们将这部分逻辑定义为一个codec包

package codec

func Unmarshal(data []byte, v interface{
   }) (int, error){
   }
func Marshal(v interface{
   }) ([]byte, error){
   }

参考官方库解析json的流程,很快我们就想到了用反射来实现这两个功能。

首先我们来分析Unmarshal,我们需要按照v的类型,将data数据按照对应的长度和类型赋值。举个最简单的例子:

func TestSimple(t *testing.T) {
   
	type Body struct {
   
		Age1 int8
		Age2 int16
	}

	data := []byte{
   0x01, 0x02, 0x03}
	pack := Body{
   }
	i, err := Unmarshal(data, &pack)
	if err != nil {
   
		t.Errorf("err:%s", err.Error())
	}

	t.Log("len:", i)
	t.Log("pack:", pack)
}
$ go test -v server/codec -run TestSimple
=== RUN   TestSimple
--- PASS: TestSimple (0.00s)
    codec_test.go:20: len: 3
    codec_test.go:21: pack: {
   1 515}
PASS
ok      server/codec    0.002s

对于Body结构体,第一个字段是int8,占用一个字节,所以分配的值是0x01。第二个字段是int16,占用两个字节,分配的值是0x02,0x03,然后把这两个字节按照大端格式组合成一个int16就行了。所以结果就是Age1字段为1(0x01),Age2字段为515(0x0203)

所以处理的关键是,我们要识别出v interface{}的类型,然后计算该类型对应的大小,再将data中对应大小的数据段组合成对应类型值复制给v中的对应字段。

v interface{}的类型多变,可能会涉及到结构体嵌套等,所以会存在递归处理,当然第一步我们需要获取到v的类型:

rv := reflect.ValueOf(v)
switch rv.Kind() {
   
    case reflect.Int8:
		//
	case reflect.Uint8:
		//
	case reflect.Int16:
		//
	case reflect.Uint16:
		//
	case reflect.Int32:
		//
	case reflect.Uint32:
		//
	case reflect.Int64:
		//
	case reflect.Uint64:
		//
	case reflect.Float32:
    	//
    case reflect.Float64:
		//
	case reflect.String:
		//
	case reflect.Slice:
		//
	case reflect.Struct:
    	//需要对struct中的每个元素进行解析
}

其他的类型都比较好处理,需要说明的是struct类型,首先我们要能够遍历struct中的各个元素,于是我们找到了:

fieldCount := v.NumField()
v.Field(i)

NumField()能够获取结构体内部元素个数,然后Field(i)通过指定index就可以获取到指定的元素了。获取到了元素后,我们就需要最这个元素进行再次的Unmarshal,也就是递归。但是此时我们通过v.Field(i)获取到的是reflect.Value类型,而不是interface{}类型了,所以递归的入参我们使用reflect.Value。另外还需要考虑的一个问题是data数据的索引问题,一次调用Unmarshal就会消耗掉一定字节的data数据,消耗的长度应该能够被获取到,以方便下一次调用Unmarshal时,能够对入参的data数据索引做正确的设定。因此,Unmarshal函数需要返回一个当前当用后所占用的字节长度。比如int8就是一个字节,struct就是各个字段字节之和。

func Unmarshal(data []byte, v interface{
   })  (int,error) {
   
	rv := reflect.ValueOf(v)
	if rv.Kind() != reflect.Ptr || rv.IsNil() {
   
		return 0,fmt.Errorf("error")
	}

	return refUnmarshal(data, reflect.ValueOf(v))
}

func refUnmarshal(data []byte, v reflect.Value)  (int,error) {
   
	var usedLen int = 0
	if v.Kind() == reflect.Ptr {
   
		v = v.Elem()
	}
	switch v.Kind() {
   
	case reflect.Int8:
		usedLen = usedLen + 1
	case reflect.Uint8:
		usedLen = usedLen + 1
	case reflect.Int16:
		if len(data) < 2 {
   
			return 0, fmt.Errorf("data to short")
		}
		usedLen = usedLen + 2
	case reflect.Uint16:
		if len(data) < 2 {
   
			return 0, fmt.Errorf("data to short")
		}
		usedLen = usedLen + 2
	case reflect.Int32:
		if len(data) < 4 {
   
			return 0, fmt.Errorf("data to short")
		}
		usedLen = usedLen + 4
	case reflect.Uint32:
		if len(data) < 4 {
   
			return 0, fmt.Errorf("data to short")
		}
		usedLen = usedLen + 4
	case reflect.Int64:
		usedLen = usedLen + 8
	case reflect.Uint64:
		usedLen = usedLen + 8
	case reflect.Float32:
		usedLen = usedLen + 4
	case reflect.Float64:
		usedLen = usedLen + 8
	case reflect.String:
		//待处理
	case reflect.Slice:
		//待处理
	case reflect.Struct:
		fieldCount := v.NumField()

		for i := 0; i < fieldCount; i++ {
   
			l, err := refUnmarshal(data[usedLen:], v.Field(i), v.Type().Field(i), streLen)
			if err != nil {
   
				return 0, err
			}

			usedLen = usedLen + l
		}
	}
	return usedLen, nil
}

解析到这个地方我们发现,我们又遇到了另外的一个问题:我们没有办法单纯的通过类型来获取到string和struct的长度,而且我们还必须处理这两个类型,因为这两个类型在协议处理中是很常见的。既然单纯的通过类型无法判断长度,我们就要借助tag了。我们尝试着在string和slice上设定tag来解决这个问题。但是tag是属于结构体的,只有结构体内部元素才能拥有tag,而且我们不能通过元素本身获取tag,必须通过上层的struct的type才能获取到,所以此时我们入参还要加入一个通过结构体type获取到的对应字段reflect.StructField:

func refUnmarshal(data []byte, v reflect.Value, tag reflect.StructField) (int, error) {
   
	var usedLen int = 0
	if v.Kind() == reflect.Ptr {
   
		v = v.Elem()
	}
	switch v.Kind() {
   
	case reflect.Int8:
		usedLen = usedLen + 1
	case reflect.Uint8:
		usedLen = usedLen + 1
	case reflect.Int16:
		usedLen = usedLen + 2
	case reflect.Uint16:
		usedLen = usedLen + 2
	case reflect.Int32:
		usedLen = usedLen + 4
	case reflect.Uint32:
		usedLen = usedLen + 4
	case reflect.Int64:
		usedLen = usedLen + 8
	case reflect.Uint64:
		usedLen = usedLen + 8
	case reflect.Float32:
		usedLen = usedLen + 4
	case reflect.Float64:
		usedLen = usedLen + 8
	case reflect.String:
		strLen := tag.Tag.Get("len")
		var lens int = 0
		if strLen == "" {
   
			//
		} else {
   
			lens64, err := strconv.ParseInt(strLen, 10, 0)
			if err != nil {
   
				return 0, err
			}

			lens = int(lens64)
		}
		usedLen = usedLen + int(lens)
	case reflect.Slice:
		strLen := tag.Tag.Get("len")
		var lens int = 0
		if strLen == "" {
   
			//
		} 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值