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

自动解析和自动编码实现计划

用户需求

  1. 自动解析服务端在给定协议下自动解析数据到header和structHandler中直接使用
  2. 自动编码:服务端写数据时自动编码

设计目标

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()

优势

  1. 自动化数据自动解析Handler直接使用结构体
  2. 类型安全:使用强类型,编译时检查
  3. 简洁Handler代码简洁无需手动解码
  4. 灵活:支持有帧头和无帧头协议
  5. 可配置:通过路由配置指定类型

注意事项

  1. 性能:反射操作需要优化,可以考虑使用类型缓存
  2. 错误处理:解析失败时的错误处理
  3. 向后兼容:逐步迁移,不破坏现有代码
  4. 灵活性:支持不指定类型的路由(使用原始字节)