Golang标准库binary详解
Golang标准库binary
binary包实现了数字和字节序列之间的简单转换。
1、ByteOrder
ByteOrder指定了如何将一个字节序列转换为16、32或64位的无符号整数:
type ByteOrder interface { Uint16([]byte) uint16 Uint32([]byte) uint32 Uint64([]byte) uint64 PutUint16([]byte, uint16) PutUint32([]byte, uint32) PutUint64([]byte, uint64) String() string }
ByteOrder是一个接口,在binary中有两个实现了该接口的结构体,分别是littleEndian和bigEndian,也就是小端和大端。大端小端指的是数据如何存储在内存中,比如:将低位字节存储在低地址空间中、高位字节存储在高地址空间中就是小端字节序;相反,将低位字节存储在高地址空间中、高位字节存储在低地址空间中就是大端字节序。
例如:十六进制数0X12345678以小端和大端字节序分别在内存中的存储方式如下:
littleEndian:
littleEndian在其它包中是无法创建的,但是在binary中已经创建了一个名为LittleEndian的该结构体,我们可以直接使用。
var LittleEndian littleEndian type littleEndian struct{} func (littleEndian) Uint16(b []byte) uint16 { _ = b[1] // 编译器的边界检测提示 return uint16(b[0]) | uint16(b[1])<<8 } func (littleEndian) PutUint16(b []byte, v uint16) { _ = b[1] // early bounds check to guarantee safety of writes below b[0] = byte(v) b[1] = byte(v >> 8) } func (littleEndian) Uint32(b []byte) uint32 { _ = b[3] // bounds check hint to compiler; see golang.org/issue/14808 return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24 } func (littleEndian) PutUint32(b []byte, v uint32) { _ = b[3] // early bounds check to guarantee safety of writes below b[0] = byte(v) b[1] = byte(v >> 8) b[2] = byte(v >> 16) b[3] = byte(v >> 24) } func (littleEndian) Uint64(b []byte) uint64 { _ = b[7] // bounds check hint to compiler; see golang.org/issue/14808 return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56 } func (littleEndian) PutUint64(b []byte, v uint64) { _ = b[7] // early bounds check to guarantee safety of writes below b[0] = byte(v) b[1] = byte(v >> 8) b[2] = byte(v >> 16) b[3] = byte(v >> 24) b[4] = byte(v >> 32) b[5] = byte(v >> 40) b[6] = byte(v >> 48) b[7] = byte(v >> 56) } func (littleEndian) String() string { return "LittleEndian" } func (littleEndian) GoString() string { return "binary.LittleEndian" }
在上面定义的方法也比较简单,就是字节序列与无符号数之间的转换。例如Uint16这个方法,在这里是小端字节序,因此低字节存储在低地址空间中,随着切片的索引的增大,地址空间也是增大的,所以b[1]所在空间是高地址,因此将b[1]左移八位后与b[0]位与就可以得到uint16类型的数据了。
bigEndian:
大端与小端相反:
var BigEndian bigEndian type bigEndian struct{} func (bigEndian) Uint16(b []byte) uint16 { _ = b[1] // bounds check hint to compiler; see golang.org/issue/14808 return uint16(b[1]) | uint16(b[0])<<8 } func (bigEndian) PutUint16(b []byte, v uint16) { _ = b[1] // early bounds check to guarantee safety of writes below b[0] = byte(v >> 8) b[1] = byte(v) } func (bigEndian) Uint32(b []byte) uint32 { _ = b[3] // bounds check hint to compiler; see golang.org/issue/14808 return uint32(b[3]) | uint32(b[2])<<8 | uint32(b[1])<<16 | uint32(b[0])<<24 } func (bigEndian) PutUint32(b []byte, v uint32) { _ = b[3] // early bounds check to guarantee safety of writes below b[0] = byte(v >> 24) b[1] = byte(v >> 16) b[2] = byte(v >> 8) b[3] = byte(v) } func (bigEndian) Uint64(b []byte) uint64 { _ = b[7] // bounds check hint to compiler; see golang.org/issue/14808 return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 | uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56 } func (bigEndian) PutUint64(b []byte, v uint64) { _ = b[7] // early bounds check to guarantee safety of writes below b[0] = byte(v >> 56) b[1] = byte(v >> 48) b[2] = byte(v >> 40) b[3] = byte(v >> 32) b[4] = byte(v >> 24) b[5] = byte(v >> 16) b[6] = byte(v >> 8) b[7] = byte(v) } func (bigEndian) String() string { return "BigEndian" } func (bigEndian) GoString() string { return "binary.BigEndian" }
2、binary.Read
Read方法从一个reader中读取数据到data中,data必须是一个指针或一个固定大小的值或切片:
该方法也可以将reader中读取的数据赋值给结构体的各个字段中。
func Read(r io.Reader, order ByteOrder, data interface{}) error { // Fast path for basic types and slices. if n := intDataSize(data); n != 0 { bs := make([]byte, n) if _, err := io.ReadFull(r, bs); err != nil { return err } switch data := data.(type) { case *bool: *data = bs[0] != 0 case *int8: *data = int8(bs[0]) case *uint8: *data = bs[0] case *int16: *data = int16(order.Uint16(bs)) case *uint16: *data = order.Uint16(bs) case *int32: *data = int32(order.Uint32(bs)) case *uint32: *data = order.Uint32(bs) case *int64: *data = int64(order.Uint64(bs)) case *uint64: *data = order.Uint64(bs) case *float32: *data = math.Float32frombits(order.Uint32(bs)) case *float64: *data = math.Float64frombits(order.Uint64(bs)) case []bool: for i, x := range bs { // Easier to loop over the input for 8-bit values. data[i] = x != 0 } case []int8: for i, x := range bs { data[i] = int8(x) } case []uint8: copy(data, bs) case []int16: for i := range data { data[i] = int16(order.Uint16(bs[2*i:])) } case []uint16: for i := range data { data[i] = order.Uint16(bs[2*i:]) } case []int32: for i := range data { data[i] = int32(order.Uint32(bs[4*i:])) } case []uint32: for i := range data { data[i] = order.Uint32(bs[4*i:]) } case []int64: for i := range data { data[i] = int64(order.Uint64(bs[8*i:])) } case []uint64: for i := range data { data[i] = order.Uint64(bs[8*i:]) } case []float32: for i := range data { data[i] = math.Float32frombits(order.Uint32(bs[4*i:])) } case []float64: for i := range data { data[i] = math.Float64frombits(order.Uint64(bs[8*i:])) } default: n = 0 // fast path doesn't apply } if n != 0 { return nil } } // Fallback to reflect-based decoding. v := reflect.ValueOf(data) size := -1 switch v.Kind() { case reflect.Ptr: v = v.Elem() size = dataSize(v) case reflect.Slice: size = dataSize(v) } if size < 0 { return errors.New("binary.Read: invalid type " + reflect.TypeOf(data).String()) } d := &decoder{order: order, buf: make([]byte, size)} if _, err := io.ReadFull(r, d.buf); err != nil { return err } d.value(v) return nil }
3、binary.Write
Write方法将数据的二进制写入一个Writer中,data必须为一个固定值的值或者切片或指向该类数据的一个指针:
func Write(w io.Writer, order ByteOrder, data interface{}) error { // Fast path for basic types and slices. if n := intDataSize(data); n != 0 { bs := make([]byte, n) switch v := data.(type) { case *bool: if *v { bs[0] = 1 } else { bs[0] = 0 } case bool: if v { bs[0] = 1 } else { bs[0] = 0 } case []bool: for i, x := range v { if x { bs[i] = 1 } else { bs[i] = 0 } } case *int8: bs[0] = byte(*v) case int8: bs[0] = byte(v) case []int8: for i, x := range v { bs[i] = byte(x) } case *uint8: bs[0] = *v case uint8: bs[0] = v case []uint8: bs = v case *int16: order.PutUint16(bs, uint16(*v)) case int16: order.PutUint16(bs, uint16(v)) case []int16: for i, x := range v { order.PutUint16(bs[2*i:], uint16(x)) } case *uint16: order.PutUint16(bs, *v) case uint16: order.PutUint16(bs, v) case []uint16: for i, x := range v { order.PutUint16(bs[2*i:], x) } case *int32: order.PutUint32(bs, uint32(*v)) case int32: order.PutUint32(bs, uint32(v)) case []int32: for i, x := range v { order.PutUint32(bs[4*i:], uint32(x)) } case *uint32: order.PutUint32(bs, *v) case uint32: order.PutUint32(bs, v) case []uint32: for i, x := range v { order.PutUint32(bs[4*i:], x) } case *int64: order.PutUint64(bs, uint64(*v)) case int64: order.PutUint64(bs, uint64(v)) case []int64: for i, x := range v { order.PutUint64(bs[8*i:], uint64(x)) } case *uint64: order.PutUint64(bs, *v) case uint64: order.PutUint64(bs, v) case []uint64: for i, x := range v { order.PutUint64(bs[8*i:], x) } case *float32: order.PutUint32(bs, math.Float32bits(*v)) case float32: order.PutUint32(bs, math.Float32bits(v)) case []float32: for i, x := range v { order.PutUint32(bs[4*i:], math.Float32bits(x)) } case *float64: order.PutUint64(bs, math.Float64bits(*v)) case float64: order.PutUint64(bs, math.Float64bits(v)) case []float64: for i, x := range v { order.PutUint64(bs[8*i:], math.Float64bits(x)) } } _, err := w.Write(bs) return err } // Fallback to reflect-based encoding. v := reflect.Indirect(reflect.ValueOf(data)) size := dataSize(v) if size < 0 { return errors.New("binary.Write: invalid type " + reflect.TypeOf(data).String()) } buf := make([]byte, size) e := &encoder{order: order, buf: buf} e.value(v) _, err := w.Write(buf) return err }
4、binary.Read和binary.Write的应用
当我们使用tcp传输数据时,常常会遇到粘包的现象,因此为了解决粘包我们需要告诉对方我们发送的数据包的大小。一般是使用TLV类型的数据协议,分别是Type、Len、Value,Type和Len为数据头,可以将这个两个字段都固定为四个字节。读取数据时,先将Type和Len读取出来,然后再根据Len来读取剩余的数据:
例如我们使用客户端向一个服务端发送数据:
client:
package main import ( "bytes" "encoding/binary" "fmt" "net" ) // 对数据进行编码 func Encode(id uint32, msg []byte) []byte { var dataLen uint32 = uint32(len(msg)) // *Buffer实现了Writer buffer := bytes.NewBuffer([]byte{}) // 将id写入字节切片 if err := binary.Write(buffer, binary.LittleEndian, &id); err != nil { fmt.Println("Write to buffer error:", err) } // 将数据长度写入字节切片 if err := binary.Write(buffer, binary.LittleEndian, &dataLen); err != nil { fmt.Println("Write to buffer error:", err) } // 最后将数据添加到后面 msg = append(buffer.Bytes(), msg...) return msg } func main() { dial, err := net.Dial("tcp4", "127.0.0.1:6666") if err != nil { fmt.Println("Dial tcp error:", err) } // 向服务端发送hello,world! msg := []byte("hello,world!") var id uint32 = 1 data := Encode(id, msg) dial.Write(data) dial.Close() } // 运行结果: Receive Data, Type:1, Len:12, Message:hello,world! Connection has been closed by client
server:
package main import ( "bytes" "encoding/binary" "fmt" "io" "net" ) // 解码,从字节切片中获取id和len func Decode(encoded []byte) (id uint32, l uint32) { buffer := bytes.NewBuffer(encoded) if err := binary.Read(buffer, binary.LittleEndian, &id); err != nil { fmt.Println("Read from buffer error:", err) } if err := binary.Read(buffer, binary.LittleEndian, &l); err != nil { fmt.Println("Read from buffer error:", err) } return id, l } const MAX_PACKAGE = 4096 func DealConn(conn net.Conn) { defer conn.Close() head := make([]byte, 8) for { // 先读取8个字节的头部,也就是id和dataLen _, err := io.ReadFull(conn, head) if err != nil { if err == io.EOF { fmt.Println("Connection has been closed by client") } else { fmt.Println("Read error:", err) } return } id, l := Decode(head) if l > MAX_PACKAGE { fmt.Println("Received data grater than MAX_PACKAGE") return } // 然后读取剩余数据 data := make([]byte, l) _, err = io.ReadFull(conn, data) if err != nil { if err == io.EOF { fmt.Println("Connection has been closed by client") } else { fmt.Println("Read error:", err) } return } // 打印收到的数据 fmt.Printf("Receive Data, Type:%d, Len:%d, Message:%s\n", id, l, string(data)) } } func main() { listener, err := net.Listen("tcp", "127.0.0.1:6666") if err != nil { fmt.Println("Listen tcp error:", err) return } for { conn, err := listener.Accept() if err != nil { fmt.Println("Accept error:", err) break } // 启动一个协程处理客户端 go DealConn(conn) } }
运行结果:
Receive Data, Type:1, Len:12, Message:hello,world!
Connection has been closed by client
到此这篇关于Golang标准库binary详解的文章就介绍到这了,更多相关Golang标准库binary内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
Golang程序漏洞检测器govulncheck的安装和使用
govulncheck 是一个命令行工具,可以帮助 Golang 开发者快速找到项目代码和依赖的模块中的安全漏洞,该工具可以分析源代码和二进制文件,识别代码中对这些漏洞的任何直接或间接调用,本文就给大家介绍一下govulncheck安装和使用,需要的朋友可以参考下2023-09-09
最新评论