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.
14 KiB
14 KiB
nnet 路由系统详尽示例
快速开始:使用 PresetBuilder
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字符串的,例如:
router.GET("/user/login", handler)
但在nnet中,我们需要支持更灵活的匹配方式,特别是对于二进制协议和自定义协议:
-
二进制协议通常没有URL概念:
- 消息可能是二进制格式
- 路由需要通过协议帧头中的字段来匹配(如:消息类型、命令码)
- 例如:消息类型=0x01 表示登录请求
-
需要灵活的匹配条件:
- 不仅仅是相等匹配(==)
- 可能需要范围匹配(>、<、>=、<=)
- 可能需要包含匹配(in、contains)
- 可能需要正则匹配(regex)
-
field(字段名)的作用:
- 指定要匹配的协议帧头字段名
- 例如:"message_type"、"command_code"、"version"等
- 系统会根据字段名从协议帧头中提取值进行匹配
-
operator(操作符)的作用:
- 指定匹配操作的类型
- 支持的操作符:
==、!=、>、<、>=、<=、in、contains、regex等 - 提供灵活的匹配逻辑
1.2 帧头匹配的工作流程
接收到的数据
↓
[协议识别] → 确定协议类型
↓
[解码] → 解析协议帧头
↓
[提取字段] → 根据field名提取字段值
↓
[匹配判断] → 使用operator和value进行匹配
↓
[路由选择] → 匹配成功则选择对应handler
二、路由匹配示例
2.1 字符串匹配(传统方式)
适用于:WebSocket、HTTP-like协议、文本协议
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)]
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:基于命令码匹配
// 匹配命令码 == 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:组合匹配(多个字段)
// 需要同时满足多个条件
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协议匹配
假设消息格式:
{
"action": "login",
"username": "user1",
"password": "pass1"
}
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范围进行路由:
// 匹配用户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 自定义匹配器
适用于:复杂的匹配逻辑、特殊业务需求
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 路由分组
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 组合使用示例
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 操作符使用示例
// 相等匹配
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 游戏服务器
// 游戏协议:消息类型 + 命令码
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 物联网设备
// 根据设备类型路由
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 混合协议支持
// 同时支持多种协议
router.RegisterString("/api/v1/login", httpLoginHandler) // HTTP/WebSocket
router.RegisterFrameHeader("msg_type", "==", 0x01, binaryLoginHandler) // 二进制协议
router.RegisterFrameData("action", "==", "login", jsonLoginHandler) // JSON协议
五、最佳实践
- 优先使用帧头匹配:帧头匹配性能更好,因为不需要解析完整消息
- 合理设置优先级:精确匹配优先于范围匹配
- 使用路由分组:按功能模块分组,便于管理
- 避免过度复杂的匹配逻辑:复杂逻辑使用自定义匹配器
- 考虑性能:帧头匹配 > 帧数据匹配 > 自定义匹配
文档版本: v1.0
最后更新: 2024