You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
6.2 KiB
6.2 KiB
自动解析和自动编码实现计划
用户需求
- 自动解析:服务端在给定协议下自动解析数据到header和struct,Handler中直接使用
- 自动编码:服务端写数据时自动编码
设计目标
Handler使用方式
// 定义请求和响应类型
type LoginRequest struct {
Username string `json:"username"`
Password string `json:"password"`
}
type LoginResponse struct {
Code int `json:"code"`
Token string `json:"token"`
}
// 注册路由(指定请求类型)
router.RegisterString(
"login",
loginHandler,
router.WithRequestType(&LoginRequest{}),
)
// Handler实现(数据已自动解析)
func loginHandler(ctx context.Context) error {
req := ctx.Request()
resp := ctx.Response()
// 直接使用解析后的数据(无需手动解码)
loginReq := req.Data().(*LoginRequest)
// 读取协议帧头(如果有)
header := req.Header()
if header != nil {
messageType := header.Get("message_type")
}
// 处理业务逻辑
token := authenticate(loginReq.Username, loginReq.Password)
// 设置响应帧头(如果有)
resp.Header().Set("message_type", 0x02)
// 写入响应(自动编码)
return resp.Write(LoginResponse{
Code: 200,
Token: token,
})
}
实现步骤
第一步:更新Context接口 ✅
- 移除数据读写方法(Write、WriteAny等)
- 添加Request和Response对象
- Context只负责状态管理
第二步:实现Request和Response
-
实现Request接口
- 存储原始数据、协议帧头、解析后的Data
- 实现Data()方法返回解析后的结构体
- 实现Header()方法返回协议帧头
-
实现Response接口
- 实现Write()方法自动编码
- 实现WriteWithCodec()方法指定编解码器
- 实现Header()方法设置协议帧头
第三步:更新路由接口
-
添加RouteOption支持
- WithRequestType() - 指定请求类型
- WithResponseType() - 指定响应类型(可选)
- WithCodec() - 指定编解码器(可选)
-
更新Route接口
- RequestType() - 获取请求类型
- ResponseType() - 获取响应类型
- Codec() - 获取编解码器
第四步:实现自动解析逻辑
- 在服务器接收到数据后
- 识别协议(根据配置或数据特征)
- 协议解码(Protocol.Decode())
- 识别编解码器(根据配置或数据特征)
- 数据解码(Codec.Decode())
- 创建Request对象并设置解析后的数据
第五步:实现自动编码逻辑
- 在Response.Write()方法中
- 编解码器编码(Codec.Encode())
- 协议编码(Protocol.Encode())
- 写入连接(Connection.Write())
第六步:更新协议实现
-
更新nnet协议实现
- 实现HasHeader()方法
- 更新Encode()方法支持FrameHeader参数
- 更新Decode()方法返回FrameHeader
-
更新TCP/UDP/WebSocket协议实现
- 实现无帧头协议支持
- 实现HasHeader()方法返回false
第七步:更新服务器代码
-
更新TCP服务器
- 实现自动解析逻辑
- 创建Request和Response对象
- 更新Context创建逻辑
-
更新UDP服务器
-
更新WebSocket服务器
-
更新Unix服务器
第八步:测试和文档
- 编写测试用例
- 更新文档
- 编写使用示例
关键技术点
1. 类型识别和反射
// 从路由配置获取请求类型
requestType := route.RequestType()
if requestType != nil {
// 创建结构体实例
body := reflect.New(requestType.Elem()).Interface()
// 解码数据
codec.Decode(bodyBytes, body)
}
2. 协议识别
// 根据配置或数据特征识别协议
func identifyProtocol(data []byte, config *Config) Protocol {
// 方式1:根据配置
if config.Protocol != "" {
return protocolManager.Get(config.Protocol, config.ProtocolVersion)
}
// 方式2:根据数据特征(如Magic字段)
if len(data) >= 4 && string(data[0:4]) == "NNET" {
return protocolManager.Get("nnet", "1.0")
}
// 方式3:默认协议
return protocolManager.GetDefault()
}
3. 编解码器识别
// 根据配置或数据特征识别编解码器
func identifyCodec(data []byte, config *Config) Codec {
// 方式1:根据配置
if config.Codec != nil && config.Codec.DefaultCodec != "" {
return codecRegistry.Get(config.Codec.DefaultCodec)
}
// 方式2:根据数据特征(如JSON格式)
if isJSON(data) {
return codecRegistry.Get("json")
}
// 方式3:默认编解码器
return codecRegistry.Default()
}
数据流程
读数据(自动解析)
1. 数据到达 → Connection.Read()
↓
2. 识别协议 → Protocol.Identify()
↓
3. 协议解码 → Protocol.Decode() → Header + DataBytes
↓
4. 识别编解码器 → Codec.Identify()
↓
5. 获取请求类型 → Route.RequestType()
↓
6. 数据解码 → Codec.Decode() → Go Struct
↓
7. 创建Request对象 → Request.SetData(struct)
↓
8. Handler执行 → req.Data().(*MyStruct)
写数据(自动编码)
1. Handler调用 → resp.Write(struct)
↓
2. 获取编解码器 → Codec (从配置或路由)
↓
3. 编解码器编码 → Codec.Encode() → []byte
↓
4. 获取协议 → Protocol (从配置)
↓
5. 协议编码 → Protocol.Encode() → Header + Data
↓
6. 写入连接 → Connection.Write()
优势
- 自动化:数据自动解析,Handler直接使用结构体
- 类型安全:使用强类型,编译时检查
- 简洁:Handler代码简洁,无需手动解码
- 灵活:支持有帧头和无帧头协议
- 可配置:通过路由配置指定类型
注意事项
- 性能:反射操作需要优化,可以考虑使用类型缓存
- 错误处理:解析失败时的错误处理
- 向后兼容:逐步迁移,不破坏现有代码
- 灵活性:支持不指定类型的路由(使用原始字节)