序列化与反序列化
在网络上客户端与服务之间通过数据交换来通信,消息被当作字节序列来传输,它们是没有结构的,仅仅只是一串字节流。
但是我们需要传输的数据可能是高度结构化的,所以在传输前必须进行序列化,需要有合适的协议来约定传输的内容的含义。
在传输之前将要(有类型的)数据 序列化 成字节流,接收到字节流时,将需要将字节流 反序列化成合适的数据结构,这两个操作被分别称为编组和解组。
在go语言中有几种方法进行序列化和反序列化操作:
- 自定义协议
- ASN.1
- JSON
- gob
1.自定义协议
我们知道在网络中传输都是字节流,那我们如何将一个字节流按不同的数据类型的字节大小进行封装与解封?
简单的方法,我们可以通过自定义协议,比如一个消息有消息长度、消息id、消息内容,协议规定消息长度为4个字节、消息id为四个字节,不规定数据内容的大小。
封装:先将消息长度转换成字节流写入字节序列,再将消息id转换成字节流写入字节序列,最后将数据内容写入字节序列。
解封:先读取四个字节长度为消息长度N、再读取4个字节为消息id,然后读取N个字节长度的消息内容。
package pack import ( "bytes" "encoding/binary" "fmt" "sync" ) type Message struct { Msglen uint32 Msgid uint32 Msgdata []byte } //封包函数 func Pack(len uint32,id uint32,data []byte)([]byte,error) { var bufferPool = sync.Pool{ New:func() interface{}{ return new(bytes.Buffer) }, } //获取一个存放bytes的缓冲区,存储字节序列 dataBuff := bufferPool.Get().(*bytes.Buffer) //将数据长度写入字节流 err := binary.Write(dataBuff,binary.LittleEndian,len) checkerr(err) //将id写入字节流 err = binary.Write(dataBuff,binary.LittleEndian,id) checkerr(err) //将数据内容写入字节流 err = binary.Write(dataBuff,binary.LittleEndian,data) checkerr(err) return dataBuff.Bytes(),nil } //解包函数 func Unpack(data []byte)(*Message,error){ //这里可以不需要额外创建一个数据缓冲 //创建一个io。Reader boolBuffer := bytes.NewReader(data) msg := &Message{} //读取数据长度和id err := binary.Read(boolBuffer, binary.LittleEndian, &msg.Msglen) checkerr(err) err = binary.Read(boolBuffer, binary.LittleEndian, &msg.Msgid) checkerr(err) //数据包限制 //if // //} return msg,nil } func checkerr(err error){ if err != nil{ fmt.Println("数据写入与读取失败") } }
2.ASN.1
文档:https://www.php.cn/manual/view/35180.html
抽象语法表示法/1(ASN.1)最初出现在 1984 年,它是一个为电信行业设计的复杂标准,Go的标准包 asn1 实现了它的一个子集,它可以将复杂的数据结构序列化成自描述的数据。
在go语言中的asn.1包支持go语言基本数据类型、整型、字符串、时间、结构体等的序列化与反序列化操作。
以下两个函数用以对数据的编、解组
func Marshal(val interface{}) ([]byte, os.Error) func Unmarshal(val interface{}, b []byte) (rest []byte, err os
一个简单的示例:
/* 使用ASN.1序列化与反序列整型数据的例子*/ package main import( "encoding/asn1" "fmt" "os" ) func main() { mdata, err := asn1.Marshal (13) checkError(err) var n int _ _ , err1 := asn1.Unmarshal(mdata, &) checkError(err1) fmt. Println(("After marshal/unmarshal: ", n) } func checkError(err error) { if err != nil{ fmt. Fprintf(os. Stderr, "Fatal error: %s" , err. Error ()) os. Exit (1) } }
ASN.1 支持的数据类型
任何序列化方法都只能处理某些数据类型,而对其他的数据类型无能为力。因此为了评估类似 ASN.1 等序列化方案的可行性,你必须先将要在程序中使用的数据类型与它们支持的数据类型做个比较,下面是 ASN.1 支持的数据类型,可以参考:http://www.obj-sys.com/asn1tutorial/node4.html
但不是以上所有的类型、可能的值都被 Go 支持,在 Go 'asn1'包文档中定义的规则如下:
- An ASN.1 INTEGER can be written to an int or int64. If the encoded value does not fit in the Go type, Unmarshal returns a parse error.
- An ASN.1 BIT STRING can be written to a BitString.
- An ASN.1 OCTET STRING can be written to a []byte.
- An ASN.1 OBJECT IDENTIFIER can be written to an ObjectIdentifier.
- An ASN.1 ENUMERATED can be written to an Enumerated.
- An ASN.1 UTCTIME or GENERALIZEDTIME can be written to a *time.Time.
- An ASN.1 PrintableString or IA5String can be written to a string.
- Any of the above ASN.1 values can be written to an interface{}. The value stored in the interface has the corresponding Go type. For integers, that type is int64.
- An ASN.1 SEQUENCE OF x or SET OF x can be written to a slice if an x can be written to the slice's element type.
- An ASN.1 SEQUENCE or SET can be written to a struct if each of the elements in the sequence can be written to the corresponding element in the struct.
3.JSON
文档:https://www.php.cn/manual/view/35187.html
JSON全称是JavaScript Object Notation,它是一种应用于JavaScript系统之间传递数据的轻量级
格式。它使用基于文本的格式,因为足够通用,现在已经成为了多种编程语言采用的通用的
序列化方法了。
JSON 序列化对象,数组和基本值。基本值包括:字符串,数字,布尔值和 NULL 值。数组
是逗号分割的一组值的列表,可以用来表示各种编程语言中的数组、向量、列表或者序列。
它们由方括号来界定,对象则由一个包含在大括号中的"field: values"对构成的列表来表示。
JSON 是一个非常简单但却十分有用的语言,尽管他基于文本的格式在字符传递上开销过多,但是却很适合阅读和使用。
4.gob
文档:https://www.php.cn/manual/view/35185.html
gob 是 Go 中特有的序列化技术。它只能编码 Go 的数据类型,目前它不支持其他语言,反之亦然。它支持除 interface,function,channel 外的所有的 Go 数据类型。它支持任何类型和任何大小的整数,还有字符串和布尔值,结构,数组与切片。
一个典型的用途是传输远程过程调用(RPC)的参数和结果。