Golang标准库binary详解

 更新时间:2023年05月20日 08:50:34   作者:CoreDump丶  
这篇文章主要介绍了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内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • GO语言实现文件上传代码分享

    GO语言实现文件上传代码分享

    本文给大家分享的是一则使用golang实现文件上传的代码,主要是使用os.Create创建文件,io.Copy来保存文件,思路非常清晰,这里推荐给大家,有需要的小伙伴参考下吧。
    2015-03-03
  • Go语言通过http抓取网页的方法

    Go语言通过http抓取网页的方法

    这篇文章主要介绍了Go语言通过http抓取网页的方法,实例分析了Go语言通过http操作页面的技巧,需要的朋友可以参考下
    2015-03-03
  • Go语言清除文件中空行的方法

    Go语言清除文件中空行的方法

    这篇文章主要介绍了Go语言清除文件中空行的方法,实例分析了Go语言针对文件的操作技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-02-02
  • 详解如何使用Golang扩展Envoy

    详解如何使用Golang扩展Envoy

    这篇文章主要为大家介绍了详解如何使用Golang扩展Envoy实现示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-06-06
  • golang如何使用struct的tag属性的详细介绍

    golang如何使用struct的tag属性的详细介绍

    这篇文章主要介绍了golang如何使用struct的tag属性的详细介绍,从例子说起,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-11-11
  • Go语言中的指针运算实例分析

    Go语言中的指针运算实例分析

    这篇文章主要介绍了Go语言中的指针运算技巧,实例分析了Go语言指针运算的实现方法,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-02-02
  • go使用支付宝沙箱实现支付宝支付的操作步骤

    go使用支付宝沙箱实现支付宝支付的操作步骤

    支付宝沙箱支付是支付宝提供的一个测试环境,用于开发者在不影响真实交易的情况下进行支付接口的开发和调试,本文给大家介绍了go使用支付宝沙箱实现支付宝支付的操作步骤,文中有详细的代码示例和图文供大家参考,需要的朋友可以参考下
    2024-03-03
  • Golang等多种语言转数组成字符串举例详解

    Golang等多种语言转数组成字符串举例详解

    今天写代码遇到数组转换成字符串操作,下面这篇文章主要给大家介绍了关于Golang等多种语言转数组成字符串的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-05-05
  • 分析Go语言中CSP并发模型与Goroutine的基本使用

    分析Go语言中CSP并发模型与Goroutine的基本使用

    我们都知道并发是提升资源利用率最基础的手段,尤其是当今大数据时代,流量对于一家互联网企业的重要性不言而喻。串流显然是不行的,尤其是对于web后端这种流量的直接载体。并发是一定的,问题在于怎么执行并发。常见的并发方式有三种,分别是多进程、多线程和协程
    2021-06-06
  • golang多次读取http request body的问题分析

    golang多次读取http request body的问题分析

    这篇文章主要给大家分析了golang多次读取http request body的问题,文中通过代码示例和图文介绍的非常详细,对大家的学习或工作有一定的帮助,需要的朋友可以参考下
    2024-01-01

最新评论