feat(frame): go-hua/common 帧协议独立成包并修复两处缺陷
- SetData 超长时 fmt.Errorf 结果未 return,超 65535 字节静默放行 → 改为返回错误 - ReadFrame 原实现 conn.Read 直读帧头(短读错位)且一次性 bufio.Reader 会丢弃 缓冲中的后续帧字节 → 统一改 io.ReadFull 精确读取,新增连续两帧回归测试 - 补齐单元测试(往返/流水线/超长/Marshal/RawHeader)、examples/frame 例程与 README
This commit is contained in:
+172
@@ -0,0 +1,172 @@
|
||||
// Package frame 应用层帧协议:头 8 字节(版本1 + 命令1 + 长度2 + 流ID4,小端序)+ 数据,
|
||||
// 提供帧与 net.Conn 间的读写封装。
|
||||
//
|
||||
// 迁移自 go-hua/common,迁移时修复两处问题:
|
||||
// 1. SetData 超长时未返回错误(fmt.Errorf 结果未 return);
|
||||
// 2. ReadFrame 原实现用临时 bufio.Reader,帧头短读会错位、
|
||||
// 一次 Peek 之外的缓冲字节会被丢弃(连续帧丢数据),现改用 io.ReadFull 精确读取。
|
||||
package frame
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
)
|
||||
|
||||
const FrameVersion byte = 1
|
||||
|
||||
// 帧头部
|
||||
const (
|
||||
sizeOfVer = 1
|
||||
sizeOfCmd = 1
|
||||
sizeOfLength = 2
|
||||
sizeOfSid = 4
|
||||
headerSize = sizeOfVer + sizeOfCmd + sizeOfSid + sizeOfLength
|
||||
)
|
||||
|
||||
const (
|
||||
frameBufSize = 0x10000
|
||||
)
|
||||
|
||||
// Frame 应用层帧:头 8 字节(版本1 + 命令1 + 长度2 + 流ID4,小端序)+ 数据
|
||||
type Frame struct {
|
||||
ver byte
|
||||
cmd byte
|
||||
sid uint32
|
||||
data []byte
|
||||
}
|
||||
|
||||
// NewFrame 创建帧,version 为协议版本,cmd 为命令字,sid 为流 ID
|
||||
func NewFrame(version byte, cmd byte, sid uint32) Frame {
|
||||
return Frame{ver: version, cmd: cmd, sid: sid}
|
||||
}
|
||||
|
||||
// ReadFrame 从连接读取一帧(阻塞直到完整帧读出;头/体不完整或连接中断返回错误)
|
||||
func ReadFrame(conn net.Conn) (Frame, error) {
|
||||
f := NewFrame(0, 0, 0)
|
||||
hdr := RawHeader{}
|
||||
// 读取头(io.ReadFull 防止头部被 TCP 分段导致的短读)
|
||||
if _, err := io.ReadFull(conn, hdr[:]); err != nil {
|
||||
return f, err
|
||||
}
|
||||
|
||||
f.ver = hdr.Version()
|
||||
f.cmd = hdr.Cmd()
|
||||
f.sid = hdr.StreamID()
|
||||
// 读取数据
|
||||
data := make([]byte, hdr.Length())
|
||||
if _, err := io.ReadFull(conn, data); err != nil {
|
||||
return f, err
|
||||
}
|
||||
f.data = data
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// WriteFrame 将帧写入连接
|
||||
func WriteFrame(conn net.Conn, frame Frame) error {
|
||||
hdr := RawHeader{}
|
||||
hdr[0] = frame.ver
|
||||
hdr[1] = frame.cmd
|
||||
binary.LittleEndian.PutUint16(hdr[2:], uint16(len(frame.data)))
|
||||
binary.LittleEndian.PutUint32(hdr[4:], uint32(frame.sid))
|
||||
|
||||
buf := hdr.Bytes()
|
||||
buf = append(buf, frame.data...)
|
||||
_, err := conn.Write(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Version 返回协议版本
|
||||
func (f *Frame) Version() byte {
|
||||
return f.ver
|
||||
}
|
||||
|
||||
// Cmd 返回命令字
|
||||
func (f *Frame) Cmd() byte {
|
||||
return f.cmd
|
||||
}
|
||||
|
||||
// Length 返回数据长度
|
||||
func (f *Frame) Length() uint16 {
|
||||
return uint16(len(f.data))
|
||||
}
|
||||
|
||||
// Data 返回数据字节集
|
||||
func (f *Frame) Data() []byte {
|
||||
return f.data
|
||||
}
|
||||
|
||||
// StreamID 返回流 ID
|
||||
func (f *Frame) StreamID() uint32 {
|
||||
return f.sid
|
||||
}
|
||||
|
||||
// SetData 设置数据(超出 65535 字节返回错误)
|
||||
func (f *Frame) SetData(data []byte) error {
|
||||
if len(data) > frameBufSize-1 {
|
||||
return fmt.Errorf("超出最大长度:%d", frameBufSize-1)
|
||||
}
|
||||
f.data = data
|
||||
return nil
|
||||
}
|
||||
|
||||
// Marshal 将 v 序列化为 JSON 后写入数据
|
||||
func (f *Frame) Marshal(v any) error {
|
||||
buf, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return f.SetData(buf)
|
||||
}
|
||||
|
||||
// Unmarshal 将数据按 JSON 反序列化为 v
|
||||
func (f *Frame) Unmarshal(v any) error {
|
||||
if f.data == nil || len(f.data) == 0 {
|
||||
return errors.New("没有数据")
|
||||
}
|
||||
return json.Unmarshal(f.data, v)
|
||||
}
|
||||
|
||||
// String 返回帧的描述字符串
|
||||
func (f *Frame) String() string {
|
||||
return fmt.Sprintf("Version:%d Cmd:%d StreamID:%d Length:%d",
|
||||
f.Version(), f.Cmd(), f.StreamID(), f.Length())
|
||||
}
|
||||
|
||||
// RawHeader 原始帧头(8 字节数组)
|
||||
type RawHeader [headerSize]byte
|
||||
|
||||
// Version 返回协议版本
|
||||
func (h RawHeader) Version() byte {
|
||||
return h[0]
|
||||
}
|
||||
|
||||
// Cmd 返回命令字
|
||||
func (h RawHeader) Cmd() byte {
|
||||
return h[1]
|
||||
}
|
||||
|
||||
// Length 返回数据长度
|
||||
func (h RawHeader) Length() uint16 {
|
||||
return binary.LittleEndian.Uint16(h[2:])
|
||||
}
|
||||
|
||||
// StreamID 返回流 ID
|
||||
func (h RawHeader) StreamID() uint32 {
|
||||
return binary.LittleEndian.Uint32(h[4:])
|
||||
}
|
||||
|
||||
// Bytes 返回帧头字节集
|
||||
func (h RawHeader) Bytes() []byte {
|
||||
buf := make([]byte, headerSize)
|
||||
for i, _ := range buf {
|
||||
buf[i] = h[i]
|
||||
}
|
||||
return buf
|
||||
}
|
||||
Reference in New Issue
Block a user