feat(bytepacket): 自 go-hua 迁入二进制封包工具

- 小端序读写, 兼容易语言协议(GBK 字符串/易语言日期)
- 附测试、examples/bytepacket 例程与包 README
This commit is contained in:
2026-09-20 12:25:35 +08:00
parent b478482f21
commit f9f7c7c92f
4 changed files with 481 additions and 0 deletions
+31
View File
@@ -0,0 +1,31 @@
# bytepacket
二进制封包/解包工具(小端序):按偏移读写整数/字符串/字节集,兼容易语言协议——
含 GBK 字符串转换与易语言日期类型。
> 迁移自 go-hua/bytepacket,代码保持原样。
## 用法
```go
import "git.zeroonesoft.cn/golib/zogo/bytepacket"
// 解包:从字节集创建后按序读取
p := bytepacket.NewPacket(data)
v := p.ReadInt32()
s := p.ReadStringGbk() // GBK → UTF-8
t := p.ReadElangDateTime() // 易语言日期 → time.Time
// 封包:按序写入后取结果
w := bytepacket.NewPacket(nil)
w.WriteInt32(100)
w.WriteStringGbk("中文")
wire := w.GetData()
```
完整可运行例程:[examples/bytepacket/main.go](../examples/bytepacket/main.go)
## 注意
- 小端序,与常见网络字节序(大端)不同,跨语言对接时先确认字节序。
- 主要面向易语言系既有协议的兼容场景;新协议建议用本仓库 `frame` 或 `proto`。
+281
View File
@@ -0,0 +1,281 @@
// Package bytepacket 提供二进制封包/解包(小端序,兼容易语言协议,含 GBK 字符串与易语言日期转换)。
package bytepacket
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"math"
"time"
"golang.org/x/text/encoding/simplifiedchinese"
"golang.org/x/text/transform"
)
// Packet 二进制封包读写器(小端序),写入后经 GetData 取出完整报文
type Packet struct {
buffer *bytes.Buffer
}
// NewPacket 新建封包
func NewPacket(b []byte) *Packet {
return &Packet{buffer: bytes.NewBuffer(b)}
}
// SetData 设置数据
func (p *Packet) SetData(b []byte) {
p.buffer = bytes.NewBuffer(b)
}
// GetData 获取数据
func (p *Packet) GetData() []byte {
return p.buffer.Bytes()
}
// ReadInt8 读取int8
func (p *Packet) ReadInt8() int8 {
var val int8 = 0
binary.Read(p.buffer, binary.LittleEndian, &val)
return val
}
// WriteInt8 写入int8
func (p *Packet) WriteInt8(val int8) {
binary.Write(p.buffer, binary.LittleEndian, &val)
}
// ReadUInt8 读取uint8
func (p *Packet) ReadUInt8() uint8 {
var val uint8 = 0
binary.Read(p.buffer, binary.LittleEndian, &val)
return val
}
// WriteUInt8 写入uint8
func (p *Packet) WriteUInt8(val uint8) {
binary.Write(p.buffer, binary.LittleEndian, &val)
}
// ReadInt16 读取int16
func (p *Packet) ReadInt16() int16 {
var val int16 = 0
binary.Read(p.buffer, binary.LittleEndian, &val)
return val
}
// WriteInt16 写入int16
func (p *Packet) WriteInt16(val int16) {
binary.Write(p.buffer, binary.LittleEndian, &val)
}
// ReadUInt16 读取uint16
func (p *Packet) ReadUInt16() uint16 {
var val uint16 = 0
binary.Read(p.buffer, binary.LittleEndian, &val)
return val
}
// WriteUInt16 写入uint16
func (p *Packet) WriteUInt16(val uint16) {
binary.Write(p.buffer, binary.LittleEndian, &val)
}
// ReadInt32 读取int32
func (p *Packet) ReadInt32() int32 {
var val int32 = 0
binary.Read(p.buffer, binary.LittleEndian, &val)
return val
}
// WriteUInt32 写入uint32
func (p *Packet) WriteUInt32(val uint32) {
binary.Write(p.buffer, binary.LittleEndian, &val)
}
// ReadUInt32 读取uint32
func (p *Packet) ReadUInt32() uint32 {
var val uint32 = 0
binary.Read(p.buffer, binary.LittleEndian, &val)
return val
}
// WriteInt32 写入int32
func (p *Packet) WriteInt32(val int32) {
binary.Write(p.buffer, binary.LittleEndian, &val)
}
// ReadInt64 读取int64
func (p *Packet) ReadInt64() int64 {
var val int64 = 0
binary.Read(p.buffer, binary.LittleEndian, &val)
return val
}
// WriteInt64 写入int64
func (p *Packet) WriteInt64(val int64) {
binary.Write(p.buffer, binary.LittleEndian, &val)
}
// ReadUInt64 读取uint64
func (p *Packet) ReadUInt64() uint64 {
var val uint64 = 0
binary.Read(p.buffer, binary.LittleEndian, &val)
return val
}
// WriteUInt64 写入uint64
func (p *Packet) WriteUInt64(val uint64) {
binary.Write(p.buffer, binary.LittleEndian, &val)
}
// ReadFloat32 读取float32
func (p *Packet) ReadFloat32() float32 {
var val float32 = 0
binary.Read(p.buffer, binary.LittleEndian, &val)
return val
}
// WriteFloat32 写入float32
func (p *Packet) WriteFloat32(val float32) {
binary.Write(p.buffer, binary.LittleEndian, &val)
}
// ReadFloat64 读取float64
func (p *Packet) ReadFloat64() float64 {
var val float64 = 0
binary.Read(p.buffer, binary.LittleEndian, &val)
return val
}
// WriteFloat64 写入float64
func (p *Packet) WriteFloat64(val float64) {
binary.Write(p.buffer, binary.LittleEndian, &val)
}
// ReadStringGbk 读取GBK字符串
func (p *Packet) ReadStringGbk() (string, error) {
var len int16 = 0
binary.Read(p.buffer, binary.LittleEndian, &len)
buf := p.buffer.Next(int(len))
if len == 0 {
return "", nil
}
if len < 0 {
return "", fmt.Errorf("invalid string length: %d", len)
}
bytes, err := GbkToUtf8(buf)
if err != nil {
return "", err
}
return string(bytes), nil
}
// WriteStringGbk 写入GBK字符串
func (p *Packet) WriteStringGbk(str string) error {
bytes, err := Utf8ToGbk([]byte(str))
if err != nil {
return err
}
var len int16 = int16(len(bytes))
err = binary.Write(p.buffer, binary.LittleEndian, &len)
if err != nil {
return err
}
_, err = p.buffer.Write(bytes)
if err != nil {
return err
}
return err
}
// ReadString 读取字符串
func (p *Packet) ReadString() (string, error) {
var len int16 = 0
err := binary.Read(p.buffer, binary.LittleEndian, &len)
if err != nil {
return "", err
}
if len == 0 {
return "", nil
}
if len < 0 {
return "", fmt.Errorf("invalid string length: %d", len)
}
buf := p.buffer.Next(int(len))
return string(buf), nil
}
// WriteString 写入字符串
func (p *Packet) WriteString(str string) error {
bytes := []byte(str)
var len int16 = int16(len(bytes))
err := binary.Write(p.buffer, binary.LittleEndian, &len)
if err != nil {
return err
}
_, err = p.buffer.Write(bytes)
if err != nil {
return err
}
return nil
}
// ReadBytes 读取字节集
func (p *Packet) ReadBytes() []byte {
var len int32 = 0
binary.Read(p.buffer, binary.LittleEndian, &len)
if len == 0 {
return nil
}
if len < 0 {
return nil
}
buf := p.buffer.Next(int(len))
return buf
}
// WriteBytes 写入字节集
func (p *Packet) WriteBytes(data []byte) error {
var len int32 = int32(len(data))
err := binary.Write(p.buffer, binary.LittleEndian, &len)
if err != nil {
return err
}
_, err = p.buffer.Write(data)
if err != nil {
return err
}
return err
}
// ReadElangDateTime 读取易语言日期时间
func (p *Packet) ReadElangDateTime() time.Time {
var f float64
binary.Read(p.buffer, binary.LittleEndian, &f)
t := time.Date(1899, 12, 30, 0, 0, 0, 0, time.Local)
msStr := fmt.Sprint("+", uint64(math.Floor(f*86400*1000+0.5)), "ms")
ms, _ := time.ParseDuration(msStr)
return t.Add(ms)
}
// GbkToUtf8 GBK 转 UTF-8
func GbkToUtf8(s []byte) ([]byte, error) {
reader := transform.NewReader(bytes.NewReader(s), simplifiedchinese.GBK.NewDecoder())
d, e := io.ReadAll(reader)
if e != nil {
return nil, e
}
return d, nil
}
// Utf8ToGbk UTF-8 转 GBK
func Utf8ToGbk(s []byte) ([]byte, error) {
reader := transform.NewReader(bytes.NewReader(s), simplifiedchinese.GBK.NewEncoder())
d, e := io.ReadAll(reader)
if e != nil {
return nil, e
}
return d, nil
}
+126
View File
@@ -0,0 +1,126 @@
package bytepacket
import (
"bytes"
"testing"
)
func TestIntReadWrite(t *testing.T) {
p := NewPacket(nil)
p.WriteInt8(-8)
p.WriteUInt8(8)
p.WriteInt16(-16)
p.WriteUInt16(16)
p.WriteInt32(-32)
p.WriteUInt32(32)
p.WriteInt64(-64)
p.WriteUInt64(64)
p.WriteFloat32(1.5)
p.WriteFloat64(2.5)
r := NewPacket(p.GetData())
if got := r.ReadInt8(); got != -8 {
t.Errorf("ReadInt8 = %d, want -8", got)
}
if got := r.ReadUInt8(); got != 8 {
t.Errorf("ReadUInt8 = %d, want 8", got)
}
if got := r.ReadInt16(); got != -16 {
t.Errorf("ReadInt16 = %d, want -16", got)
}
if got := r.ReadUInt16(); got != 16 {
t.Errorf("ReadUInt16 = %d, want 16", got)
}
if got := r.ReadInt32(); got != -32 {
t.Errorf("ReadInt32 = %d, want -32", got)
}
if got := r.ReadUInt32(); got != 32 {
t.Errorf("ReadUInt32 = %d, want 32", got)
}
if got := r.ReadInt64(); got != -64 {
t.Errorf("ReadInt64 = %d, want -64", got)
}
if got := r.ReadUInt64(); got != 64 {
t.Errorf("ReadUInt64 = %d, want 64", got)
}
if got := r.ReadFloat32(); got != 1.5 {
t.Errorf("ReadFloat32 = %v, want 1.5", got)
}
if got := r.ReadFloat64(); got != 2.5 {
t.Errorf("ReadFloat64 = %v, want 2.5", got)
}
}
func TestStringReadWrite(t *testing.T) {
p := NewPacket(nil)
if err := p.WriteString("hello"); err != nil {
t.Fatalf("WriteString: %v", err)
}
if err := p.WriteStringGbk("中文GBK"); err != nil {
t.Fatalf("WriteStringGbk: %v", err)
}
r := NewPacket(p.GetData())
s, err := r.ReadString()
if err != nil || s != "hello" {
t.Errorf("ReadString = %q, %v, want hello", s, err)
}
g, err := r.ReadStringGbk()
if err != nil || g != "中文GBK" {
t.Errorf("ReadStringGbk = %q, %v, want 中文GBK", g, err)
}
}
func TestBytesReadWrite(t *testing.T) {
p := NewPacket(nil)
want := []byte{1, 2, 3, 0xFF}
if err := p.WriteBytes(want); err != nil {
t.Fatalf("WriteBytes: %v", err)
}
got := NewPacket(p.GetData()).ReadBytes()
if !bytes.Equal(got, want) {
t.Errorf("ReadBytes = %v, want %v", got, want)
}
}
func TestSetDataGetData(t *testing.T) {
p := NewPacket(nil)
p.SetData([]byte{9, 9})
if !bytes.Equal(p.GetData(), []byte{9, 9}) {
t.Errorf("GetData = %v, want [9 9]", p.GetData())
}
}
func TestGbkUtf8Convert(t *testing.T) {
src := []byte("编码转换")
gbk, err := Utf8ToGbk(src)
if err != nil {
t.Fatalf("Utf8ToGbk: %v", err)
}
utf8Bytes, err := GbkToUtf8(gbk)
if err != nil {
t.Fatalf("GbkToUtf8: %v", err)
}
if !bytes.Equal(utf8Bytes, src) {
t.Errorf("GBK 往返不一致: %q != %q", utf8Bytes, src)
}
}
func TestReadElangDateTime(t *testing.T) {
// 45292.5 天 ≈ 2024-01-01 12:00:00
p := NewPacket(nil)
p.WriteFloat64(45292.5)
got := NewPacket(p.GetData()).ReadElangDateTime()
if got.Format("2006-01-02 15:04:05") != "2024-01-01 12:00:00" {
t.Errorf("ReadElangDateTime = %s, want 2024-01-01 12:00:00", got.Format("2006-01-02 15:04:05"))
}
}
func TestReadStringEmpty(t *testing.T) {
p := NewPacket(nil)
p.WriteInt16(0)
s, err := NewPacket(p.GetData()).ReadString()
if err != nil || s != "" {
t.Errorf("ReadString 空串 = %q, %v", s, err)
}
}
+43
View File
@@ -0,0 +1,43 @@
// bytepacket 包示例:二进制封包/解包(小端序,兼容易语言协议,含 GBK 字符串与易语言日期)
package main
import (
"fmt"
"git.zeroonesoft.cn/golib/zogo/bytepacket"
)
func main() {
// ===== 写入封包 =====
p := bytepacket.NewPacket(nil)
p.WriteInt32(100)
p.WriteUInt16(200)
_ = p.WriteString("hello")
_ = p.WriteStringGbk("中文GBK")
_ = p.WriteBytes([]byte{1, 2, 3})
data := p.GetData()
fmt.Printf("封包总长: %d 字节\n", len(data))
// ===== 读取封包(按写入顺序读出)=====
r := bytepacket.NewPacket(data)
fmt.Println("ReadInt32:", r.ReadInt32())
fmt.Println("ReadUInt16:", r.ReadUInt16())
s, _ := r.ReadString()
fmt.Println("ReadString:", s)
gbk, _ := r.ReadStringGbk()
fmt.Println("ReadStringGbk:", gbk)
fmt.Println("ReadBytes:", r.ReadBytes())
// ===== GBK / UTF-8 互转 =====
gbkBytes, _ := bytepacket.Utf8ToGbk([]byte("编码转换"))
fmt.Printf("UTF-8→GBK: % X\n", gbkBytes)
utf8Bytes, _ := bytepacket.GbkToUtf8(gbkBytes)
fmt.Println("GBK→UTF-8:", string(utf8Bytes))
// ===== 易语言日期时间(float64 天数,起点 1899-12-30)=====
d := bytepacket.NewPacket(nil)
d.WriteFloat64(45292.5) // ≈ 2024-01-01 12:00:00
t := d.ReadElangDateTime()
fmt.Println("ReadElangDateTime:", t.Format("2006-01-02 15:04:05"))
}