標籤:golang 位元組
在寫網路程式的時候,我們經常需要將結構體或者整數等資料類型序列化成二進位的buffer串。或者從一個buffer中解析出來一個結構體出來,最典型的就是在協議的header部分表徵head length 或者body length在拼包和拆包的過程中,需要按照規定的整數類型進行解析,且涉及到大小端序的問題。
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{}) errorfunc Write(w io.Writer, order ByteOrder, data interface{}) errorfunc Size(v interface{}) intvar BigEndian bigEndianvar LittleEndian littleEndian/*type ByteOrder interface {Uint16([]byte) uint16Uint32([]byte) uint32Uint64([]byte) uint64PutUint16([]byte, uint16)PutUint32([]byte, uint32)PutUint64([]byte, uint64)String() string}/*
通過Read介面可以將buf中得內容填充到data參數表示的資料結構中,通過Write介面可以將data參數裡麵包含的資料寫入到buffer中。 變數BigEndian和LittleEndian是實現了ByteOrder介面的對象,通過介面中提供的方法可以直接將uintx類型序列化(uintx())或者還原序列化(putuintx())到buf中。
2.1將結構體序列化到一個buf中
在序列化結構對象時,需要注意的是,被序列化的結構的大小必須是已知的,可以通過Size介面來獲得該結構的大小,從而決定buffer的大小。
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 Aa.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())
對應的輸出為:
a‘s size is 8after write ,buf is : [1 0 0 0 2 0 0 0]
通過Size可以得到所需buffer的大小。通過Write可以將對象a的內容序列化到buffer中。這裡採用了小端序的方式進行序列化(x86架構都是小端序,網路位元組序是大端序)。
對於結構體中得“_”成員不進行序列化。
2.2從buf中還原序列化回一個結構
從buffer中讀取時,一樣要求結構體的大小要固定,且需要還原序列化的結構體成員必須是可匯出的也就是必須是大寫開頭的成員,同樣對於“_”不進行還原序列化:
type A struct { // should be exported member when read back from buffer One int32 Two int32}var aa Abuf := 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]bytebinary.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中對應的結構裡面。
本文出自 “Done_in_72_hours” 部落格,請務必保留此出處http://gotaly.blog.51cto.com/8861157/1539119