feat(proto): 自 go-hua 迁入长度前缀封包协议编解码
- 8 字节头(4 字节 ID/校验 + 4 字节长度, 小端序) + CRC32 校验版本 - 附测试、examples/proto 例程与包 README(含 Decode reader 复用告诫)
This commit is contained in:
@@ -0,0 +1,49 @@
|
|||||||
|
// proto 包示例:通用封包协议(消息头 8 字节 = ID 4 字节 + 长度 4 字节,小端序),支持 CRC32 校验版本
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"git.zeroonesoft.cn/golib/zogo/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
client, server := net.Pipe()
|
||||||
|
defer client.Close()
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
// ===== 普通封包:Encode / Decode =====
|
||||||
|
go func() {
|
||||||
|
data, err := proto.Encode([]byte("hello proto"))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Encode:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
client.Write(data)
|
||||||
|
}()
|
||||||
|
|
||||||
|
buf, err := proto.Decode(bufio.NewReader(server))
|
||||||
|
fmt.Println("Decode:", string(buf), "错误:", err)
|
||||||
|
|
||||||
|
// ===== CRC32 校验封包:EncodeCrc32 / DecodeCrc32 =====
|
||||||
|
go func() {
|
||||||
|
data, err := proto.EncodeCrc32([]byte("hello crc32"))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("EncodeCrc32:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
client.Write(data)
|
||||||
|
}()
|
||||||
|
|
||||||
|
buf, err = proto.DecodeCrc32(bufio.NewReader(server))
|
||||||
|
fmt.Println("DecodeCrc32:", string(buf), "错误:", err)
|
||||||
|
|
||||||
|
// 实际 TCP 服务中的用法:
|
||||||
|
// reader := bufio.NewReader(conn)
|
||||||
|
// for {
|
||||||
|
// data, err := proto.Decode(reader) // 阻塞读到完整一帧
|
||||||
|
// handle(data)
|
||||||
|
// }
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# proto
|
||||||
|
|
||||||
|
长度前缀封包协议的编解码:8 字节头(4 字节 ID/校验 + 4 字节长度,小端序),
|
||||||
|
支持 CRC32 校验版本,配合 `bufio.Reader` 使用。
|
||||||
|
|
||||||
|
> 迁移自 go-hua/proto,代码保持原样。
|
||||||
|
|
||||||
|
## 帧格式
|
||||||
|
|
||||||
|
```
|
||||||
|
普通版: + id/流ID (4) + length (4) + data (length) + 小端序
|
||||||
|
CRC32 版:+ id (4) + length (4) + crc32 (4) + data (length) + 小端序
|
||||||
|
```
|
||||||
|
|
||||||
|
## 用法
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"git.zeroonesoft.cn/golib/zogo/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 发送端
|
||||||
|
wire, _ := proto.Encode([]byte("payload")) // 另有 EncodeCrc32
|
||||||
|
conn.Write(wire)
|
||||||
|
|
||||||
|
// 接收端(reader 需为 *bufio.Reader,可跨帧复用)
|
||||||
|
r := bufio.NewReader(conn)
|
||||||
|
data, err := proto.Decode(r) // 另有 DecodeCrc32(校验失败返回错误)
|
||||||
|
```
|
||||||
|
|
||||||
|
完整可运行例程:[examples/proto/main.go](../examples/proto/main.go)
|
||||||
|
|
||||||
|
## 注意
|
||||||
|
|
||||||
|
- `Decode` 会阻塞直到读满一帧;reader 请与连接同生命周期复用,勿每帧新建
|
||||||
|
(缓冲中多读的字节会丢失——与本仓库 `frame` 包迁移前问题同理)。
|
||||||
|
- CRC32 版本与非校验版本的头部布局不同,两端必须约定同一种。
|
||||||
+213
@@ -0,0 +1,213 @@
|
|||||||
|
// Package proto 提供长度前缀封包协议的编解码(8 字节头:4 字节 ID/校验 + 4 字节长度,小端序),支持 CRC32 校验版本。
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"hash/crc32"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PakHeadLen 消息头长度:普通版为 ID 4 字节 + 长度 4 字节;CRC32 版为校验值 4 字节 + 长度 4 字节
|
||||||
|
const (
|
||||||
|
PakHeadLen int32 = 8
|
||||||
|
)
|
||||||
|
|
||||||
|
// Encode 编码
|
||||||
|
func Encode(buf []byte) ([]byte, error) {
|
||||||
|
// 读取消息的长度,转换成int32类型(占4个字节)
|
||||||
|
var id int32 = 5
|
||||||
|
var length = int32(len(buf))
|
||||||
|
var pkg = new(bytes.Buffer)
|
||||||
|
// 写入消息头
|
||||||
|
err := binary.Write(pkg, binary.LittleEndian, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = binary.Write(pkg, binary.LittleEndian, length)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// 写入消息实体
|
||||||
|
err = binary.Write(pkg, binary.LittleEndian, []byte(buf))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return pkg.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode 解码
|
||||||
|
func Decode(reader *bufio.Reader) ([]byte, error) {
|
||||||
|
// 读取头部
|
||||||
|
lengthByte, err := reader.Peek(int(PakHeadLen)) // 读取前4个字节的数据
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
lengthBuff := bytes.NewBuffer(lengthByte)
|
||||||
|
var id int32 = 0
|
||||||
|
var length int32 = 0
|
||||||
|
|
||||||
|
err = binary.Read(lengthBuff, binary.LittleEndian, &id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = binary.Read(lengthBuff, binary.LittleEndian, &length)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if length <= 0 {
|
||||||
|
return nil, errors.New("decode length cannot be less than 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
if length > 0x1000000 {
|
||||||
|
return nil, errors.New("decode length cannot be greater than 0x1000000")
|
||||||
|
}
|
||||||
|
|
||||||
|
var data []byte
|
||||||
|
|
||||||
|
// 剩余读长长度
|
||||||
|
unread := int(PakHeadLen + length)
|
||||||
|
readLen := 0
|
||||||
|
for {
|
||||||
|
if unread > 0x10000 {
|
||||||
|
readLen = 0x10000
|
||||||
|
} else {
|
||||||
|
readLen = unread
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = reader.Peek(readLen) // 先窥探数据
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buffered返回缓冲中现有的可读取的字节数。
|
||||||
|
if reader.Buffered() < readLen {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取
|
||||||
|
buf := make([]byte, readLen)
|
||||||
|
_, err = reader.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data = append(data, buf...)
|
||||||
|
|
||||||
|
unread -= readLen
|
||||||
|
//fmt.Println("readLen", readLen)
|
||||||
|
if unread <= 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data) < int(PakHeadLen) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return data[PakHeadLen:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeCrc32 编码 Crc32校验
|
||||||
|
func EncodeCrc32(buf []byte) ([]byte, error) {
|
||||||
|
// 读取消息的长度,转换成int32类型(占4个字节)
|
||||||
|
ieee := crc32.NewIEEE()
|
||||||
|
ieee.Write(buf)
|
||||||
|
var crc = ieee.Sum32()
|
||||||
|
var length = int32(len(buf))
|
||||||
|
var pkg = new(bytes.Buffer)
|
||||||
|
// 写入消息头
|
||||||
|
err := binary.Write(pkg, binary.LittleEndian, crc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = binary.Write(pkg, binary.LittleEndian, length)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// 写入消息实体
|
||||||
|
err = binary.Write(pkg, binary.LittleEndian, []byte(buf))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return pkg.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeCrc32 解码 Crc32校验
|
||||||
|
func DecodeCrc32(reader *bufio.Reader) ([]byte, error) {
|
||||||
|
// 读取头部
|
||||||
|
lengthByte, err := reader.Peek(int(PakHeadLen)) // 读取前4个字节的数据
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
lengthBuff := bytes.NewBuffer(lengthByte)
|
||||||
|
var crc uint32 = 0
|
||||||
|
var length int32 = 0
|
||||||
|
|
||||||
|
err = binary.Read(lengthBuff, binary.LittleEndian, &crc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = binary.Read(lengthBuff, binary.LittleEndian, &length)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if length <= 0 {
|
||||||
|
return nil, errors.New("decode length cannot be less than 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
if length > 0x1000000 {
|
||||||
|
return nil, errors.New("decode length cannot be greater than 0x1000000")
|
||||||
|
}
|
||||||
|
|
||||||
|
var data []byte
|
||||||
|
|
||||||
|
// 剩余读长长度
|
||||||
|
unread := int(PakHeadLen + length)
|
||||||
|
readLen := 0
|
||||||
|
for {
|
||||||
|
if unread > 0x1000 {
|
||||||
|
readLen = 0x1000
|
||||||
|
} else {
|
||||||
|
readLen = unread
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = reader.Peek(readLen) // 先窥探数据
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buffered返回缓冲中现有的可读取的字节数。
|
||||||
|
if reader.Buffered() < readLen {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取
|
||||||
|
buf := make([]byte, readLen)
|
||||||
|
_, err = reader.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data = append(data, buf...)
|
||||||
|
|
||||||
|
unread -= readLen
|
||||||
|
//fmt.Println("readLen", readLen)
|
||||||
|
if unread <= 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data) < int(PakHeadLen) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
ieee := crc32.NewIEEE()
|
||||||
|
ieee.Write(data[PakHeadLen:])
|
||||||
|
if crc != ieee.Sum32() {
|
||||||
|
return nil, errors.New("crc check error")
|
||||||
|
}
|
||||||
|
return data[PakHeadLen:], nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEncodeDecode(t *testing.T) {
|
||||||
|
client, server := net.Pipe()
|
||||||
|
defer client.Close()
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
want := []byte("hello proto")
|
||||||
|
go func() {
|
||||||
|
data, err := Encode(want)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Encode: %v", err)
|
||||||
|
server.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
server.Write(data)
|
||||||
|
}()
|
||||||
|
|
||||||
|
got, err := Decode(bufio.NewReader(client))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Decode: %v", err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(got, want) {
|
||||||
|
t.Errorf("Decode = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeDecodeCrc32(t *testing.T) {
|
||||||
|
client, server := net.Pipe()
|
||||||
|
defer client.Close()
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
want := []byte("hello crc32")
|
||||||
|
go func() {
|
||||||
|
data, err := EncodeCrc32(want)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("EncodeCrc32: %v", err)
|
||||||
|
server.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
server.Write(data)
|
||||||
|
}()
|
||||||
|
|
||||||
|
got, err := DecodeCrc32(bufio.NewReader(client))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("DecodeCrc32: %v", err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(got, want) {
|
||||||
|
t.Errorf("DecodeCrc32 = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecodeCrc32Corrupted(t *testing.T) {
|
||||||
|
client, server := net.Pipe()
|
||||||
|
defer client.Close()
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
data, _ := EncodeCrc32([]byte("payload"))
|
||||||
|
data[len(data)-1] ^= 0xFF // 篡改数据
|
||||||
|
server.Write(data)
|
||||||
|
}()
|
||||||
|
|
||||||
|
_, err := DecodeCrc32(bufio.NewReader(client))
|
||||||
|
if err == nil {
|
||||||
|
t.Error("篡改数据后 DecodeCrc32 应返回错误")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeHeaderFormat(t *testing.T) {
|
||||||
|
// 头 8 字节:[0:4] 为 CRC/ID,[4:8] 为小端长度
|
||||||
|
data, err := EncodeCrc32([]byte("abcd"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("EncodeCrc32: %v", err)
|
||||||
|
}
|
||||||
|
length := int(data[4]) | int(data[5])<<8 | int(data[6])<<16 | int(data[7])<<24
|
||||||
|
if length != 4 {
|
||||||
|
t.Errorf("长度字段 = %d, want 4", length)
|
||||||
|
}
|
||||||
|
if len(data) != int(PakHeadLen)+4 {
|
||||||
|
t.Errorf("报文总长 = %d, want %d", len(data), PakHeadLen+4)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user