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.

571 lines
14 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 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协议、需要根据消息体内容路由的场景
#### 示例1JSON协议匹配
假设消息格式:
```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