Golang中的字節序列化操做

在寫網絡程序的時候,咱們常常須要將結構體或者整數等數據類型序列化成二進制的buffer串。或者從一個buffer中解析出來一個結構體出來,最典型的就是在協議的header部分表徵head length 或者body length在拼包和拆包的過程當中,須要按照規定的整數類型進行解析,且涉及到大小端序的問題。golang

1.C中是怎麼操做的

在C中咱們最簡單的方法是用memcpy來一個×××數或者結構體等其餘類型複製到一塊內存中,而後在強轉回須要的類型。如:網絡

    // produce
    int a = 32;
    char *buf  = (char *)malloc(sizeof(int));
    memcpy(buf,&a,sizeof(int));

    // consume
    int b ;
    memcpy(&b,buf,sizeof(int))

必要的時候採用ntoh/hton系列函數進行大小端序的轉換。數據結構

2.golang中操做

經過"encoding/binary"能夠提供經常使用的二進制序列化的功能。該模塊主要提供了以下幾個接口:架構

func Read(r io.Reader, order ByteOrder, data interface{}) error
func Write(w io.Writer, order ByteOrder, data interface{}) error
func Size(v interface{}) int

var BigEndian bigEndian
var LittleEndian littleEndian
/*
type ByteOrder interface {
Uint16([]byte) uint16
Uint32([]byte) uint32
Uint64([]byte) uint64
PutUint16([]byte, uint16)
PutUint32([]byte, uint32)
PutUint64([]byte, uint64)
String() string
}
/*

經過Read接口能夠將buf中得內容填充到data參數表示的數據結構中,經過Write接口能夠將data參數裏面包含的數據寫入到buffer中。 變量BigEndian和LittleEndian是實現了ByteOrder接口的對象,經過接口中提供的方法能夠直接將uintx類型序列化(uintx())或者反序列化(putuintx())到buf中。tcp

2.1將結構體序列化到一個buf中

在序列化結構對象時,須要注意的是,被序列化的結構的大小必須是已知的,能夠經過Size接口來得到該結構的大小,從而決定buffer的大小。ide

i := uint16(1)
size :=  binary.Size(i)

固定大小的結構體,就要求結構體中不能出現[]byte這樣的切片成員,不然Size返回-1,且不能進行正常的序列化操做。函數

type A struct {
    // should be exported member when read back from buffer
    One int32
    Two int32
}

var a A


a.One = int32(1)
a.Two = int32(2)

buf := new(bytes.Buffer)
fmt.Println("a's size is ",binary.Size(a))
binary.Write(buf,binary.LittleEndian,a)
fmt.Println("after write ,buf is:",buf.Bytes())

對應的輸出爲:ui

a's size is  8
after write ,buf is : [1 0 0 0 2 0 0 0]

經過Size能夠獲得所需buffer的大小。經過Write能夠將對象a的內容序列化到buffer中。這裏採用了小端序的方式進行序列化(x86架構都是小端序,網絡字節序是大端序)。spa

對於結構體中得「_」成員不進行序列化。orm

2.2從buf中反序列化回一個結構

從buffer中讀取時,同樣要求結構體的大小要固定,且須要反序列化的結構體成員必須是可導出的也就是必須是大寫開頭的成員,一樣對於「_」不進行反序列化:

type A struct {
    // should be exported member when read back from buffer
    One int32
    Two int32
}

var aa A

buf := new(bytes.Buffer)
binary.Write(buf,binary.LittleEndian,a)
binary.Read(buf,binary.LittleEndian,&aa)
fmt.Println("after aa is ",aa)

輸出爲:

after write ,bufis : [1 0 0 0 2 0 0 0]
before aa is : {0 0}
after aa is  {1 2}

這裏使用Read從buffer中將數據導入到結構體對象aa中。若是結構體中對應的成員不是可導出的,那麼在轉換的時候會panic出錯。

2.3將整數序列化到buf中,並從buf中反序列化出來

咱們能夠經過Read/Write直接去讀或者寫一個uintx類型的變量來實現對×××數的序列化和反序列化。因爲在網絡中,對於×××數的序列化很是經常使用,所以系統庫提供了type ByteOrder接口能夠方便的對uint16/uint32/uint64進行序列化和反序列化:

int16buf := new(bytes.Buffer)
i := uint16(1)
binary.Write(int16buf,binary.LittleEndian,i)
fmt.Println(「write buf is:」int16buf.Bytes())

var int16buf2 [2]byte
binary.LittleEndian.PutUint16(int16buf2[:],uint16(1))
fmt.Println("put buffer is :",int16buf2[:])

ii := binary.LittleEndian.Uint16(int16buf2[:])
fmt.Println("Get buf is :",ii)

輸出爲:

write buffer is : [1 0]
put buf is: [1 0]
Get buf is : 1

經過調用binary.LittleEndian.PutUint16,能夠按照小端序的格式將uint16類型的數據序列化到buffer中。經過binary.LittleEndian.Uint16將buffer中內容反序列化出來。

3. 一個實在的例子

咱們來看一個網絡包包頭的定義和初始化:

type Head struct {
    Cmd byte
    Version byte
    Magic   uint16
    Reserve byte
    HeadLen byte
    BodyLen uint16
}

func NewHead(buf []byte)*Head{
    head := new(Head)

    head.Cmd     = buf[0]
    head.Version = buf[1]
    head.Magic   = binary.BigEndian.Uint16(buf[2:4])
    head.Reserve = buf[4]
    head.HeadLen = buf[5]
    head.BodyLen = binary.BigEndian.Uint16(buf[6:8])
    return head
}

這個是一個常見的在tcp 拼包得例子。在例子中經過binary.BigEndian.Uint16將數據按照網絡序的格式讀出來,放入到head中對應的結構裏面。

相關文章
相關標籤/搜索