序列化與反序列化
在網絡上客戶端與服務之間通過數據交換來通信,消息被當作字節序列來傳輸,它們是沒有結構的,僅僅只是一串字節流。
但是我們需要傳輸的數據可能是高度結構化的,所以在傳輸前必須進行序列化,需要有合適的協議來約定傳輸的內容的含義。
在傳輸之前將要(有類型的)數據 序列化 成字節流,接收到字節流時,將需要將字節流 反序列化成合適的數據結構,這兩個操作被分別稱為編組和解組。
在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)的參數和結果。