這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
在寫網路程式的時候,我們經常需要將結構體或者整數等資料類型序列化成二進位的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