计算机字节序和网络字节序
字节序 就是多字节数据类型 (int, float 等)在内存中的存储顺序。可分为大端序,低地址端存放高位字节;小端序与之相反,低地址端存放低位字节。
在计算机内部,小端序被广泛应用于现代性 CPU 内部存储数据;而在其他场景譬如网络传输和文件存储使用大端序。
使用小端序时不移动字节就能改变 number 占内存的大小而不需内存地址起始位。比如我想把四字节的 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在其它包中是无法创建的,但是在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" }
当我们使用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()
}
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)
}
}