2、关于网络中接受的数据如何序列化和反序列化的思考以及实现

1、背景介绍

因工作接触到半导体行业,主要负责 EAP 相关的东西,其中需要实现 SECS/GEM 协议,消息协议使用的是 SECS-II ,其中有一种数据类型是 A 类型,表示字符串类型。需要将接收到的 SECS 指令记录在日志中,以及反解析 SECS 指令。

我们知道,网络中接受到的数据都是 byte,需要自己根据规则来进行序列化和反序列化,像平常我们使用 HTTP 通信时,都是使用第三方通信库,底层的实现已经封装好了。

比如我们规定现在传输过来的数据表示的是ASCII码,那此时byte = 65 表示的是 字符A,我们把这一步叫做序列化,现在需要把 字符A 转换为byte = 65 ,就叫做反序列化。

知道上面的规则定义后,来看看现在的具体需求,需求蛮简单,就是在收到数据后,将其序列化后存储在日志后,方便后续的维护以及排查问题等,还有一个功能是可以将日志中的 SECS 指令导入到模拟器中(其实就是将 日志中的 SECS 指令反序列化为 byte 数据)。

2、数据如何序列化和反序列化的思考

知道需求后,就开始想想怎么来实现。

因为使用的是 golang 作为开发语言,所以接下来讲解都是基于 go ,不过不同语言关于这块应该大差不差。

最简单的想法就是使用 golang 的格式化字符串中的 %S 来序列化数据,这样简单也方便,其实大多数情况下是可以的,不过有一个问题就是,当遇到ASCII中的控制字符时,这种方式就不太行了。

比如 ASCII=10 (LF) - 换行:n 格式化后,记录日志时就直接换行了,日志会乱七八糟的换行,这种可能还可以接受,如果接收到的数据是6 (ACK) - 应答:不常用 这样的时候,使用%s 得到的结果是看不错效果的,那更加谈不上反序列化了。

那怎么解决呢?

对于 go 有一种简便方式,使用 go 中的格式化方式 %q该值对应的单引号括起来的go语法字符字面值,必要时会采用安全的转义表示.。是不是看着一头雾水,其实就是使用特殊的字符串代替 ASCII 中的控制字符,接下来看看是如何表示的。

控制字符采用以下方式:  ascii: 0 <---> "x00" ascii: 1 <---> "x01" ascii: 2 <---> "x02" ascii: 3 <---> "x03" ascii: 4 <---> "x04" ascii: 5 <---> "x05" ascii: 6 <---> "x06" ascii: 7 <---> "a"   ascii: 8 <---> "b"   ascii: 9 <---> "t" ascii: 10 <---> "n" ascii: 11 <---> "v" ascii: 12 <---> "f" ascii: 13 <---> "r" ascii: 14 <---> "x0e" ascii: 15 <---> "x0f" ascii: 16 <---> "x10" ascii: 17 <---> "x11" ascii: 18 <---> "x12" ascii: 19 <---> "x13" ascii: 20 <---> "x14" ascii: 21 <---> "x15" ascii: 22 <---> "x16" ascii: 23 <---> "x17" ascii: 24 <---> "x18" ascii: 25 <---> "x19" ascii: 26 <---> "x1a" ascii: 27 <---> "x1b" ascii: 28 <---> "x1c" ascii: 29 <---> "x1d" ascii: 30 <---> "x1e" ascii: 31 <---> "x1f" ascii: 127 <---> "x7f"  ascii: 92 <---> "\" 

知道了上面的规定后,那后面的编码就简单很多了,下面贴下自己的代码。

3、代码实现

3.1、byte转字符串的方式

package main  import ( 	"fmt" 	"unicode" )  var controlCharters = make([]byte, 33) var controlCharterMaps = make(map[byte]byte)  func init() { 	for i := 0; i <= 127; i++ { 		if unicode.IsControl(rune(i)) { 			controlCharters = append(controlCharters, byte(i)) 			controlCharterMaps[byte(i)] = byte(i) 		} 	} } func main() { 	var recvData []byte 	for i := 0; i <= 127; i++ { 		recvData = append(recvData, byte(i)) 	}  	fmt.Println(fmt.Sprintf("%q", string(recvData))) }  

输出结果:

x00x01x02x03x04x05x06abtnvfrx0ex0fx10x11x12x13x14x15x16x17x18x19x1ax1bx1cx1dx1ex1f !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~x7f 

3.2、字符串转byte的方式

package main  import ( 	"errors" 	"fmt" 	"strings" 	"unicode" )  var controlCharters = make([]byte, 33) var controlCharterMaps = make(map[byte]byte)  func init() { 	for i := 0; i <= 127; i++ { 		if unicode.IsControl(rune(i)) { 			controlCharters = append(controlCharters, byte(i)) 			controlCharterMaps[byte(i)] = byte(i) 		} 	} } func main() { 	s := "x00x01x02x03x04x05x06abtnvfrx0ex0fx10x11x12x13x14x15x16x17x18x19x1ax1bx1cx1dx1ex1f!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~x7f" 	unescaped, err := replaceControlCharacters(s) 	if err != nil { 		fmt.Printf("err: %s", err) 		return 	}  	fmt.Println([]byte(unescaped)) }  // replaceControlCharacters 将 sml 中表示为控制字符以及转义字符() 转换为 ascii 字符 func replaceControlCharacters(input string) (string, error) {  	controlCharMap := map[string]string{ 		"\\":  "\", 		"\n":   "n", 		"\t":   "t", 		"\r":   "r", 		"\b":   "b", 		"\a":   "a", 		"\f":   "f", 		"\v":   "v", 		"\x00": "x00", 		"\x01": "x01", 		"\x02": "x02", 		"\x03": "x03", 		"\x04": "x04", 		"\x05": "x05", 		"\x06": "x06", 		"\x0e": "x0e", 		"\x0f": "x0f", 		"\x10": "x10", 		"\x11": "x11", 		"\x12": "x12", 		"\x13": "x13", 		"\x14": "x14", 		"\x15": "x15", 		"\x16": "x16", 		"\x17": "x17", 		"\x18": "x18", 		"\x19": "x19", 		"\x1a": "x1a", 		"\x1b": "x1b", 		"\x1c": "x1c", 		"\x1d": "x1d", 		"\x1e": "x1e", 		"\x1f": "x1f", 		"\x7f": "x7f", 	}  	var output strings.Builder 	i := 0  	for i < len(input) { 		if input[i] == '\' { 			escaped := false 			for key, value := range controlCharMap { 				if strings.HasPrefix(input[i:], key) { 					output.WriteString(value) 					i += len(key) 					escaped = true 					break 				} 			} 			if !escaped { 				return "", errors.New(fmt.Sprintf("illegal control character: %s, origin string: %s", string(input[i]), input)) 			} 		} else { 			output.WriteByte(input[i]) 			i++ 		} 	}  	return output.String(), nil }  

输出方式:

[0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127] 

从上面可以看出,方式还是很简单的,其实还有很多其他方式可以达到序列化以及反序列化的方式,比如将收到的数据使用 base64 等方式,这样可以的。只不过因为我们的日志还需要给到客户看,使用 base64 并不友好(自己平时看也不方便),所以需要使用上面这种方式。

发表评论

评论已关闭。

相关文章