feat(bytepacket): 自 go-hua 迁入二进制封包工具
- 小端序读写, 兼容易语言协议(GBK 字符串/易语言日期) - 附测试、examples/bytepacket 例程与包 README
This commit is contained in:
@@ -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`。
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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"))
|
||||
}
|
||||
Reference in New Issue
Block a user