|
|
# 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
|
|
|
|