一个TCP长链接设备管理后台工程(一)
一个TCP长链接设备管理后台工程(二)
一个TCP长链接设备管理后台工程(三)git
Github仓库地址github
从前面内容咱们能够发现,808协议是一个很典型的协议格式:json
固定字段+变长字段
其中固定字段用来检测一个帧格式的完整性和有效性,因此通常会包含一下内容:帧头+变长字段对应的长度+校验。因为这一段的数据格式固定,目的单一,因此处理起来比较简单。segmentfault
变长字段的长度是由固定字段终端某一个子字段的值决定的,并且这部分的格式比较多变,须要灵活处理。这一字段咱们一般称为Body或者Apdu。数组
咱们首先说明变长字段的处理流程。bash
正由于Body字段格式灵活,因此为了提升代码的复用性和拓展性,咱们须要对Body的处理机制进行抽象,提取出一个相对通用的接口出来。app
有经验的工程师都知道,一个协议格式处理,无非就是编码和解码。编码咱们称之为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的流程,很快咱们就想到了用反射来实现这两个功能。ui
首先咱们来分析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 == "" { // } else { lens64, err := strconv.ParseInt(strLen, 10, 0) if err != nil { return 0, err } lens = int(lens64) } usedLen = usedLen + int(lens) case reflect.Struct: fieldCount := v.NumField() for i := 0; i < fieldCount; i++ { l, err := refUnmarshal(data[usedLen:], v.Field(i), v.Type().Field(i)) if err != nil { return 0, err } usedLen = usedLen + l } } return usedLen, nil }
这样咱们就能过获取到全部的字段对应的长度了,这个很关键。而后咱们只须要根据对应的长度,从data中填充对应的数据值便可
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: v.SetInt(int64(data[0])) usedLen = usedLen + 1 case reflect.Uint8: v.SetUint(uint64(data[0])) usedLen = usedLen + 1 case reflect.Int16: if len(data) < 2 { return 0, fmt.Errorf("data to short") } v.SetInt(int64(Bytes2Word(data))) usedLen = usedLen + 2 case reflect.Uint16: if len(data) < 2 { return 0, fmt.Errorf("data to short") } v.SetUint(uint64(Bytes2Word(data))) usedLen = usedLen + 2 case reflect.Int32: if len(data) < 4 { return 0, fmt.Errorf("data to short") } v.SetInt(int64(Bytes2DWord(data))) usedLen = usedLen + 4 case reflect.Uint32: if len(data) < 4 { return 0, fmt.Errorf("data to short") } v.SetUint(uint64(Bytes2DWord(data))) usedLen = usedLen + 4 case reflect.Int64: v.SetInt(64) usedLen = usedLen + 8 case reflect.Uint64: v.SetUint(64) usedLen = usedLen + 8 case reflect.Float32: v.SetFloat(32.23) usedLen = usedLen + 4 case reflect.Float64: v.SetFloat(64.46) 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) } if len(data) < int(lens) { return 0, fmt.Errorf("data to short") } v.SetString(string(data[:lens])) usedLen = usedLen + int(lens) case reflect.Slice: 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) } v.SetBytes(data[:lens]) usedLen = usedLen + int(lens) case reflect.Struct: fieldCount := v.NumField() for i := 0; i < fieldCount; i++ { l, err := refUnmarshal(data[usedLen:], v.Field(i), v.Type().Field(i)) if err != nil { return 0, err } usedLen = usedLen + l } } return usedLen, nil }
一个基本的Unmarshal函数就完成了。可是这个处理是比较理想的,在实际中可能会存在这样的一种状况:在一个协议中有若干字段,其余的字段都是固定长度,只有一个字段是长度可变的,而这个可变长度的计算是由整体长度-固定长度来计算出来的。在这种状况下,咱们须要提早计算出已知字段的固定长度,而后用data长度-固定长度,获得惟一的可变字段的长度。因此我如今要有一个获取这个结构的有效长度的函数。前面的Unmarshal内部已经能够获取到每一个字段的长度了,咱们只须要把这个函数简单改造一下就好了:
func RequireLen(v interface{}) (int, error) { rv := reflect.ValueOf(v) if rv.Kind() != reflect.Ptr || rv.IsNil() { return 0, fmt.Errorf("error") } return refRequireLen(reflect.ValueOf(v), reflect.StructField{}) } func refRequireLen(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") if strLen == "" { return 0, nil } lens, err := strconv.ParseInt(strLen, 10, 0) if err != nil { return 0, err } usedLen = usedLen + int(lens) case reflect.Slice: strLen := tag.Tag.Get("len") if strLen == "" { return 0, nil } lens, err := strconv.ParseInt(strLen, 10, 0) if err != nil { return 0, err } usedLen = usedLen + int(lens) case reflect.Struct: fieldCount := v.NumField() for i := 0; i < fieldCount; i++ { l, err := refRequireLen(v.Field(i), v.Type().Field(i)) if err != nil { return 0, err } usedLen = usedLen + l } } return usedLen, nil }
这样咱们就能够实现一个完整的Unmarshal
func Unmarshal(data []byte, v interface{}) (int, error) { rv := reflect.ValueOf(v) if rv.Kind() != reflect.Ptr || rv.IsNil() { return 0, fmt.Errorf("error") } lens, err := RequireLen(v) if err != nil { return 0, err } if len(data) < lens { return 0, fmt.Errorf("data too short") } return refUnmarshal(data, reflect.ValueOf(v), reflect.StructField{}, len(data)-lens) } func refUnmarshal(data []byte, v reflect.Value, tag reflect.StructField, streLen int) (int, error) { var usedLen int = 0 if v.Kind() == reflect.Ptr { v = v.Elem() } switch v.Kind() { case reflect.Int8: v.SetInt(int64(data[0])) usedLen = usedLen + 1 case reflect.Uint8: v.SetUint(uint64(data[0])) usedLen = usedLen + 1 case reflect.Int16: if len(data) < 2 { return 0, fmt.Errorf("data to short") } v.SetInt(int64(Bytes2Word(data))) usedLen = usedLen + 2 case reflect.Uint16: if len(data) < 2 { return 0, fmt.Errorf("data to short") } v.SetUint(uint64(Bytes2Word(data))) usedLen = usedLen + 2 case reflect.Int32: if len(data) < 4 { return 0, fmt.Errorf("data to short") } v.SetInt(int64(Bytes2DWord(data))) usedLen = usedLen + 4 case reflect.Uint32: if len(data) < 4 { return 0, fmt.Errorf("data to short") } v.SetUint(uint64(Bytes2DWord(data))) usedLen = usedLen + 4 case reflect.Int64: v.SetInt(64) usedLen = usedLen + 8 case reflect.Uint64: v.SetUint(64) usedLen = usedLen + 8 case reflect.Float32: v.SetFloat(32.23) usedLen = usedLen + 4 case reflect.Float64: v.SetFloat(64.46) usedLen = usedLen + 8 case reflect.String: strLen := tag.Tag.Get("len") var lens int = 0 if strLen == "" { lens = streLen } else { lens64, err := strconv.ParseInt(strLen, 10, 0) if err != nil { return 0, err } lens = int(lens64) } if len(data) < int(lens) { return 0, fmt.Errorf("data to short") } v.SetString(string(data[:lens])) usedLen = usedLen + int(lens) case reflect.Slice: strLen := tag.Tag.Get("len") var lens int = 0 if strLen == "" { lens = streLen } else { lens64, err := strconv.ParseInt(strLen, 10, 0) if err != nil { return 0, err } lens = int(lens64) } v.SetBytes(data[:lens]) usedLen = usedLen + int(lens) 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 }
理解了上面的流程,Marshal就就很好写了,只是复制过程反过来就好了。这其中还有一些小的转换逻辑将字节数组转换成多字节整形:Bytes2Word、Word2Bytes、Bytes2DWord、Dword2Bytes。这类转换都使用大端格式处理。完整代码以下:
package codec import ( "fmt" "reflect" "strconv" ) func RequireLen(v interface{}) (int, error) { rv := reflect.ValueOf(v) if rv.Kind() != reflect.Ptr || rv.IsNil() { return 0, fmt.Errorf("error") } return refRequireLen(reflect.ValueOf(v), reflect.StructField{}) } func Unmarshal(data []byte, v interface{}) (int, error) { rv := reflect.ValueOf(v) if rv.Kind() != reflect.Ptr || rv.IsNil() { return 0, fmt.Errorf("error") } lens, err := RequireLen(v) if err != nil { return 0, err } if len(data) < lens { return 0, fmt.Errorf("data too short") } return refUnmarshal(data, reflect.ValueOf(v), reflect.StructField{}, len(data)-lens) } func Marshal(v interface{}) ([]byte, error) { rv := reflect.ValueOf(v) if rv.Kind() != reflect.Ptr || rv.IsNil() { return []byte{}, fmt.Errorf("error") } return refMarshal(reflect.ValueOf(v), reflect.StructField{}) } func refRequireLen(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") if strLen == "" { return 0, nil } lens, err := strconv.ParseInt(strLen, 10, 0) if err != nil { return 0, err } usedLen = usedLen + int(lens) case reflect.Slice: strLen := tag.Tag.Get("len") if strLen == "" { return 0, nil } lens, err := strconv.ParseInt(strLen, 10, 0) if err != nil { return 0, err } usedLen = usedLen + int(lens) case reflect.Struct: fieldCount := v.NumField() for i := 0; i < fieldCount; i++ { l, err := refRequireLen(v.Field(i), v.Type().Field(i)) if err != nil { return 0, err } usedLen = usedLen + l } } return usedLen, nil } func refUnmarshal(data []byte, v reflect.Value, tag reflect.StructField, streLen int) (int, error) { var usedLen int = 0 if v.Kind() == reflect.Ptr { v = v.Elem() } switch v.Kind() { case reflect.Int8: v.SetInt(int64(data[0])) usedLen = usedLen + 1 case reflect.Uint8: v.SetUint(uint64(data[0])) usedLen = usedLen + 1 case reflect.Int16: if len(data) < 2 { return 0, fmt.Errorf("data to short") } v.SetInt(int64(Bytes2Word(data))) usedLen = usedLen + 2 case reflect.Uint16: if len(data) < 2 { return 0, fmt.Errorf("data to short") } v.SetUint(uint64(Bytes2Word(data))) usedLen = usedLen + 2 case reflect.Int32: if len(data) < 4 { return 0, fmt.Errorf("data to short") } v.SetInt(int64(Bytes2DWord(data))) usedLen = usedLen + 4 case reflect.Uint32: if len(data) < 4 { return 0, fmt.Errorf("data to short") } v.SetUint(uint64(Bytes2DWord(data))) usedLen = usedLen + 4 case reflect.Int64: v.SetInt(64) usedLen = usedLen + 8 case reflect.Uint64: v.SetUint(64) usedLen = usedLen + 8 case reflect.Float32: v.SetFloat(32.23) usedLen = usedLen + 4 case reflect.Float64: v.SetFloat(64.46) usedLen = usedLen + 8 case reflect.String: strLen := tag.Tag.Get("len") var lens int = 0 if strLen == "" { lens = streLen } else { lens64, err := strconv.ParseInt(strLen, 10, 0) if err != nil { return 0, err } lens = int(lens64) } if len(data) < int(lens) { return 0, fmt.Errorf("data to short") } v.SetString(string(data[:lens])) usedLen = usedLen + int(lens) case reflect.Slice: strLen := tag.Tag.Get("len") var lens int = 0 if strLen == "" { lens = streLen } else { lens64, err := strconv.ParseInt(strLen, 10, 0) if err != nil { return 0, err } lens = int(lens64) } v.SetBytes(data[:lens]) usedLen = usedLen + int(lens) 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 } func refMarshal(v reflect.Value, tag reflect.StructField) ([]byte, error) { data := make([]byte, 0) if v.Kind() == reflect.Ptr { v = v.Elem() } switch v.Kind() { case reflect.Int8: data = append(data, byte(v.Int())) case reflect.Uint8: data = append(data, byte(v.Uint())) case reflect.Int16: temp := Word2Bytes(uint16(v.Int())) data = append(data, temp...) case reflect.Uint16: temp := Word2Bytes(uint16(v.Uint())) data = append(data, temp...) case reflect.Int32: temp := Dword2Bytes(uint32(v.Int())) data = append(data, temp...) case reflect.Uint32: temp := Dword2Bytes(uint32(v.Uint())) data = append(data, temp...) case reflect.String: strLen := tag.Tag.Get("len") lens, err := strconv.ParseInt(strLen, 10, 0) if err != nil { return []byte{}, err } if int(lens) > v.Len() { zeroSlice := make([]byte, int(lens)-v.Len()) data = append(data, zeroSlice...) } data = append(data, v.String()...) case reflect.Slice: strLen := tag.Tag.Get("len") lens, err := strconv.ParseInt(strLen, 10, 0) if err != nil { return []byte{}, err } if int(lens) > v.Len() { zeroSlice := make([]byte, int(lens)-v.Len()) data = append(data, zeroSlice...) } data = append(data, v.Bytes()...) case reflect.Struct: fieldCount := v.NumField() for i := 0; i < fieldCount; i++ { d, err := refMarshal(v.Field(i), v.Type().Field(i)) if err != nil { return []byte{}, err } data = append(data, d...) } } return data, nil } func Bytes2Word(data []byte) uint16 { if len(data) < 2 { return 0 } return (uint16(data[0]) << 8) + uint16(data[1]) } func Word2Bytes(data uint16) []byte { buff := make([]byte, 2) buff[0] = byte(data >> 8) buff[1] = byte(data) return buff } func Bytes2DWord(data []byte) uint32 { if len(data) < 4 { return 0 } return (uint32(data[0]) << 24) + (uint32(data[1]) << 16) + (uint32(data[2]) << 8) + uint32(data[3]) } func Dword2Bytes(data uint32) []byte { buff := make([]byte, 4) buff[0] = byte(data >> 24) buff[1] = byte(data >> 16) buff[2] = byte(data >> 8) buff[3] = byte(data) return buff }
测试程序codec_test.go
package codec import ( "testing" ) func TestUnmarshal(t *testing.T) { type Data struct { Size int8 Size2 uint16 Size3 uint32 Name string `len:"5"` Message string Sec []byte `len:"3"` } type Body struct { Age1 int8 Age2 int16 Length int32 Data1 Data } data := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0x31, 0x32, 0x33, 0x34, 0x35, 0x31, 0x30, 0x03, 0x02, 0x01} pack := Body{} i, err := Unmarshal(data, &pack) if err != nil { t.Errorf("err:%s", err.Error()) } t.Log("len:", i) t.Log("pack:", pack) } func TestMarshal(t *testing.T) { type Data struct { Size int8 Size2 uint16 Size3 uint32 Name string `len:"5"` Message string Sec []byte `len:"3"` } type Body struct { Age1 int8 Age2 int16 Length int32 Data1 Data } pack := Body{ Age1: 13, Age2: 1201, Length: 81321, Data1: Data{ Size: 110, Size2: 39210, Size3: 85632, Name: "ASDFG", Message: "ZXCVBN", Sec: []byte{0x01, 0x02, 0x03}, }, } data, err := Marshal(&pack) if err != nil { t.Errorf("err:%s", err.Error()) } t.Log("data:", data) t.Log("pack:", pack) }
测试结果:
$ go test -v server/codec -run TestMarshal === RUN TestMarshal --- PASS: TestMarshal (0.00s) codec_test.go:70: data: [13 4 177 0 1 61 169 110 153 42 0 1 78 128 65 83 68 70 71 90 88 67 86 66 78 1 2 3] codec_test.go:71: pack: {13 1201 81321 {110 39210 85632 ASDFG ZXCVBN [1 2 3]}} PASS ok server/codec 0.001s # xml @ xia in ~/work/code/code_go/cmdtest [10:21:47] $ go test -v server/codec -run TestUnmarshal === RUN TestUnmarshal --- PASS: TestUnmarshal (0.00s) codec_test.go:31: len: 24 codec_test.go:32: pack: {1 515 67438087 {-95 41635 2762319527 12345 10 [3 2 1]}} PASS ok server/codec 0.002s