# nnet 路由系统详尽示例 ## 快速开始:使用 PresetBuilder ```go builder := nnet.NewPreset(). WithAddress("tcp://:8080"). WithRouteString("/hello", func(ctx nnet.Context) error { return ctx.Response().WriteString("hello nnet\n") }) server, err := builder.Build() if err != nil { panic(err) } if err := server.Start(); err != nil { panic(err) } ``` PresetBuilder 会自动配置 nnet 协议、注册路由,并提供 `WithMetricsHandler` / `WithHealthHandler` 方便挂载监控端点。以下章节继续展示底层路由系统的工作原理。 ## 一、路由系统设计说明 ### 1.1 为什么需要 field 和 operator? 在传统的HTTP框架(如Gin、Fiber)中,路由匹配是基于URL字符串的,例如: ```go router.GET("/user/login", handler) ``` 但在nnet中,我们需要支持更灵活的匹配方式,特别是对于**二进制协议**和**自定义协议**: 1. **二进制协议通常没有URL概念**: - 消息可能是二进制格式 - 路由需要通过协议帧头中的字段来匹配(如:消息类型、命令码) - 例如:消息类型=0x01 表示登录请求 2. **需要灵活的匹配条件**: - 不仅仅是相等匹配(==) - 可能需要范围匹配(>、<、>=、<=) - 可能需要包含匹配(in、contains) - 可能需要正则匹配(regex) 3. **field(字段名)的作用**: - 指定要匹配的协议帧头字段名 - 例如:"message_type"、"command_code"、"version"等 - 系统会根据字段名从协议帧头中提取值进行匹配 4. **operator(操作符)的作用**: - 指定匹配操作的类型 - 支持的操作符:`==`、`!=`、`>`、`<`、`>=`、`<=`、`in`、`contains`、`regex`等 - 提供灵活的匹配逻辑 ### 1.2 帧头匹配的工作流程 ``` 接收到的数据 ↓ [协议识别] → 确定协议类型 ↓ [解码] → 解析协议帧头 ↓ [提取字段] → 根据field名提取字段值 ↓ [匹配判断] → 使用operator和value进行匹配 ↓ [路由选择] → 匹配成功则选择对应handler ``` ## 二、路由匹配示例 ### 2.1 字符串匹配(传统方式) 适用于:WebSocket、HTTP-like协议、文本协议 ```go package main import ( "github.com/noahlann/nnet/pkg/nnet" "github.com/noahlann/nnet/pkg/router" ) func main() { server, err := nnet.NewServer(&nnet.Config{ Addr: "tcp://:8080", }) if err != nil { panic(err) } // 方式1:使用便捷方法 server.Router().RegisterString("/user/login", loginHandler) server.Router().RegisterString("/user/info", userInfoHandler) // 方式2:使用匹配器 matcher := router.NewStringMatcher("/user/login") server.Router().Register(matcher, loginHandler) // 支持通配符 server.Router().RegisterString("/user/*", userHandler) server.Router().RegisterString("/api/v1/*", apiV1Handler) server.Start() } func loginHandler(ctx router.Context) error { // 处理登录逻辑 return nil } ``` ### 2.2 帧头匹配(二进制协议) 适用于:自定义二进制协议、游戏协议、物联网协议 #### 示例1:基于消息类型匹配 假设协议格式: ``` [消息类型(1B)][命令码(1B)][长度(4B)][数据(NB)] ``` ```go package main import ( "github.com/noahlann/nnet/pkg/nnet" "github.com/noahlann/nnet/pkg/router" ) // 定义协议帧头结构 type MessageHeader struct { MessageType uint8 // 消息类型 CommandCode uint8 // 命令码 Length uint32 // 数据长度 } func main() { server := nnet.NewServer(&nnet.Config{ Addr: "tcp://:8080", Protocol: "custom", // 自定义协议 }) // 方式1:使用便捷方法 // 匹配消息类型 == 0x01 (登录请求) server.Router().RegisterFrameHeader( "message_type", // field: 字段名 "==", // operator: 操作符 0x01, // value: 匹配值 loginHandler, ) // 匹配消息类型 == 0x02 (心跳) server.Router().RegisterFrameHeader( "message_type", "==", 0x02, heartbeatHandler, ) // 方式2:使用匹配器(更灵活) matcher := router.NewFrameHeaderMatcher( "message_type", // 字段名 "==", // 操作符 0x01, // 值 ) server.Router().Register(matcher, loginHandler) // 方式3:范围匹配(消息类型在 0x10-0x1F 范围内) server.Router().RegisterFrameHeader( "message_type", "in", []uint8{0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F}, systemMessageHandler, ) server.Start() } func loginHandler(ctx router.Context) error { // 从Context中获取已解析的消息 header := ctx.Get("header").(*MessageHeader) data := ctx.Get("data").([]byte) // 处理登录逻辑 return nil } ``` #### 示例2:基于命令码匹配 ```go // 匹配命令码 == 0x10 (获取用户信息) server.Router().RegisterFrameHeader( "command_code", "==", 0x10, getUserInfoHandler, ) // 匹配命令码 >= 0x80 (系统命令) server.Router().RegisterFrameHeader( "command_code", ">=", 0x80, systemCommandHandler, ) // 匹配命令码在指定范围内 server.Router().RegisterFrameHeader( "command_code", "in", []uint8{0x01, 0x02, 0x03}, commonCommandHandler, ) ``` #### 示例3:组合匹配(多个字段) ```go // 需要同时满足多个条件 matcher1 := router.NewFrameHeaderMatcher("message_type", "==", 0x01) matcher2 := router.NewFrameHeaderMatcher("command_code", "==", 0x10) comboMatcher := router.NewComboMatcher(matcher1, matcher2) // AND逻辑 server.Router().Register(comboMatcher, handler) ``` ### 2.3 帧数据匹配(基于消息内容) 适用于:JSON协议、需要根据消息体内容路由的场景 #### 示例1:JSON协议匹配 假设消息格式: ```json { "action": "login", "username": "user1", "password": "pass1" } ``` ```go package main import ( "github.com/noahlann/nnet/pkg/nnet" "github.com/noahlann/nnet/pkg/router" ) func main() { server := nnet.NewServer(&nnet.Config{ Addr: "tcp://:8080", Protocol: "json", }) // 方式1:使用便捷方法 // 匹配 action 字段 == "login" server.Router().RegisterFrameData( "action", // path: JSON路径(支持点号分隔,如 "user.id") "==", // operator: 操作符 "login", // value: 匹配值 loginHandler, ) // 匹配 action == "logout" server.Router().RegisterFrameData( "action", "==", "logout", logoutHandler, ) // 匹配 user.role == "admin" (嵌套字段) server.Router().RegisterFrameData( "user.role", "==", "admin", adminHandler, ) // 方式2:使用匹配器 matcher := router.NewFrameDataMatcher( "action", "==", "login", ) server.Router().Register(matcher, loginHandler) // 方式3:范围匹配(user.age >= 18) server.Router().RegisterFrameData( "user.age", ">=", 18, adultUserHandler, ) // 方式4:包含匹配(tags contains "important") server.Router().RegisterFrameData( "tags", "contains", "important", importantMessageHandler, ) server.Start() } func loginHandler(ctx router.Context) error { // 从Context中获取已解析的JSON数据 data := ctx.Get("data").(map[string]interface{}) action := data["action"].(string) // 处理登录逻辑 return nil } ``` #### 示例2:二进制协议中的字段匹配 假设协议格式: ``` [消息类型(1B)][用户ID(4B)][数据长度(4B)][数据(NB)] ``` 需要根据用户ID范围进行路由: ```go // 匹配用户ID >= 1000 (VIP用户) server.Router().RegisterFrameData( "user_id", // 字段路径(从消息体中提取) ">=", 1000, vipUserHandler, ) // 匹配用户ID在指定范围内 server.Router().RegisterFrameData( "user_id", "in", []uint32{1001, 1002, 1003, 1004, 1005}, specificUsersHandler, ) ``` ### 2.4 自定义匹配器 适用于:复杂的匹配逻辑、特殊业务需求 ```go package main import ( "github.com/noahlann/nnet/pkg/nnet" "github.com/noahlann/nnet/pkg/router" ) func main() { server := nnet.NewServer(&nnet.Config{ Addr: "tcp://:8080", }) // 自定义匹配器:根据消息长度和内容特征匹配 customMatcher := router.NewCustomMatcher(func(input router.MatchInput, ctx router.Context) bool { data := input.Raw // 自定义匹配逻辑 if len(data) < 10 { return false } // 检查消息头特征 if data[0] == 0xFF && data[1] == 0xFE { return true } // 检查特定模式 if contains(data, []byte("SPECIAL")) { return true } return false }) server.Router().Register(customMatcher, specialHandler) server.Start() } func contains(data, pattern []byte) bool { // 简单的包含检查 for i := 0; i <= len(data)-len(pattern); i++ { if string(data[i:i+len(pattern)]) == string(pattern) { return true } } return false } ``` ### 2.5 路由分组 ```go package main import ( "github.com/noahlann/nnet/pkg/nnet" "github.com/noahlann/nnet/pkg/router" ) func main() { server := nnet.NewServer(&nnet.Config{ Addr: "tcp://:8080", }) // 创建路由分组 apiGroup := server.Router().Group() // 分组级别的中间件(认证) apiGroup.Use(authMiddleware) // 分组内的路由 apiGroup.RegisterString("/user/login", loginHandler) apiGroup.RegisterString("/user/info", userInfoHandler) // 另一个分组(系统管理) adminGroup := server.Router().Group() adminGroup.Use(adminAuthMiddleware) // 管理员认证 // 基于帧头的分组 systemGroup := server.Router().Group() systemGroup.RegisterFrameHeader("message_type", ">=", 0x80, systemHandler) server.Start() } ``` ### 2.6 组合使用示例 ```go package main import ( "github.com/noahlann/nnet/pkg/nnet" "github.com/noahlann/nnet/pkg/router" ) func main() { server := nnet.NewServer(&nnet.Config{ Addr: "tcp://:8080", Protocol: "custom", }) router := server.Router() // 1. 字符串匹配(用于WebSocket升级后的HTTP请求) router.RegisterString("/ws", websocketUpgradeHandler) // 2. 帧头匹配(用于二进制协议) router.RegisterFrameHeader("message_type", "==", 0x01, loginHandler) router.RegisterFrameHeader("message_type", "==", 0x02, heartbeatHandler) // 3. 帧数据匹配(用于JSON协议) router.RegisterFrameData("action", "==", "login", jsonLoginHandler) router.RegisterFrameData("action", "==", "logout", jsonLogoutHandler) // 4. 自定义匹配(特殊场景) customMatcher := router.NewCustomMatcher(func(input router.MatchInput, ctx router.Context) bool { data := input.Raw // 复杂匹配逻辑 return len(data) > 100 && data[0] == 0xAA }) router.Register(customMatcher, specialHandler) server.Start() } ``` ## 三、操作符说明 ### 3.1 支持的操作符 | 操作符 | 说明 | 示例 | |--------|------|-------| | `==` | 相等 | `message_type == 0x01` | | `!=` | 不等 | `message_type != 0x00` | | `>` | 大于 | `user_id > 1000` | | `<` | 小于 | `user_id < 100` | | `>=` | 大于等于 | `user_id >= 1000` | | `<=` | 小于等于 | `user_id <= 100` | | `in` | 在列表中 | `message_type in [0x01, 0x02, 0x03]` | | `contains` | 包含(字符串/数组) | `tags contains "important"` | | `regex` | 正则匹配 | `username regex "^user.*"` | | `starts_with` | 前缀匹配 | `path starts_with "/api"` | | `ends_with` | 后缀匹配 | `path ends_with ".json"` | ### 3.2 操作符使用示例 ```go // 相等匹配 router.RegisterFrameHeader("status", "==", 1, handler) // 不等匹配 router.RegisterFrameHeader("status", "!=", 0, handler) // 范围匹配 router.RegisterFrameHeader("level", ">=", 10, highLevelHandler) router.RegisterFrameHeader("level", "<=", 5, lowLevelHandler) // 列表匹配 router.RegisterFrameHeader("type", "in", []uint8{1, 2, 3}, typeHandler) // 包含匹配 router.RegisterFrameData("tags", "contains", "vip", vipHandler) // 正则匹配 router.RegisterFrameData("email", "regex", "^.*@example\\.com$", exampleEmailHandler) ``` ## 四、实际应用场景 ### 4.1 游戏服务器 ```go // 游戏协议:消息类型 + 命令码 router.RegisterFrameHeader("message_type", "==", 0x01, loginHandler) // 登录 router.RegisterFrameHeader("message_type", "==", 0x02, moveHandler) // 移动 router.RegisterFrameHeader("message_type", "==", 0x03, attackHandler) // 攻击 router.RegisterFrameHeader("message_type", "==", 0x04, chatHandler) // 聊天 // 系统消息(0x80-0xFF) router.RegisterFrameHeader("message_type", ">=", 0x80, systemMessageHandler) ``` ### 4.2 物联网设备 ```go // 根据设备类型路由 router.RegisterFrameData("device_type", "==", "sensor", sensorHandler) router.RegisterFrameData("device_type", "==", "actuator", actuatorHandler) // 根据设备ID范围路由 router.RegisterFrameData("device_id", ">=", 1000, deviceGroup1Handler) router.RegisterFrameData("device_id", "<", 1000, deviceGroup2Handler) ``` ### 4.3 混合协议支持 ```go // 同时支持多种协议 router.RegisterString("/api/v1/login", httpLoginHandler) // HTTP/WebSocket router.RegisterFrameHeader("msg_type", "==", 0x01, binaryLoginHandler) // 二进制协议 router.RegisterFrameData("action", "==", "login", jsonLoginHandler) // JSON协议 ``` ## 五、最佳实践 1. **优先使用帧头匹配**:帧头匹配性能更好,因为不需要解析完整消息 2. **合理设置优先级**:精确匹配优先于范围匹配 3. **使用路由分组**:按功能模块分组,便于管理 4. **避免过度复杂的匹配逻辑**:复杂逻辑使用自定义匹配器 5. **考虑性能**:帧头匹配 > 帧数据匹配 > 自定义匹配 --- **文档版本**: v1.0 **最后更新**: 2024