Бинарный пакет Go и его применение

Порядок байтов компьютера и сетевой порядок байтов
Порядок байтов — это порядок хранения многобайтовых типов данных (int, float и т. д.) в памяти. Его можно разделить на порядок с обратным порядком байтов, в младшем конце адреса хранится старший байт, в обратном порядке с прямым порядком байтов, а в конце младшего адреса хранится младший байт.

Внутри компьютера прямой порядок байтов широко используется для хранения данных внутри современных процессоров, в то время как прямой порядок байтов используется в других сценариях, таких как передача по сети и хранение файлов.

При использовании прямого порядка байтов вы можете изменить размер памяти, занимаемой числом, без перемещения байтов без стартового бита адреса памяти. Например, если я хочу преобразовать четырехбайтовое целое число int32 в восьмибайтовое целое число int64, мне нужно только добавить нули в конце последовательности с прямым порядком байтов. Описанная выше операция расширения или сжатия целочисленных переменных очень полезна на уровне компилятора, но не на уровне сетевого протокола.

При работе с двоичными числами на уровне сетевого протокола принято использовать порядок с обратным порядком байтов, который является методом, используемым для передачи байтов по сети. Поскольку самый старший байт последовательности с обратным порядком байтов является первым (конец с младшим адресом хранит старший байт), его можно отсортировать в соответствии со словарем, поэтому мы можем сравнить каждый байт двоично-кодированного числа.

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
}

littleEndian:

littleEndian не может быть создан в других пакетах, но структура под названием 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" }

Когда мы используем tcp для передачи данных, мы часто сталкиваемся с явлением липких пакетов, поэтому для решения проблем с липкими пакетами нам нужно сообщить другой стороне размер отправленных нами пакетов данных. Как правило, используется протокол данных типа TLV, а именно Type, Len, Value, Type и Len являются заголовками данных, и эти два поля могут быть фиксированными до четырех байтов. При чтении данных сначала считывайте Type и Len, а затем считывайте остальные данные в соответствии с Len:

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()
}

сервер:

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)

	}

}

Guess you like

Origin blog.csdn.net/qq_30505673/article/details/130889239
Go