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中对应的结构里面。

相关文章
相关标签/搜索