- 将 ShrinkEventBus、ShrinkDataSaver 及其 EventBus 集成从 gitlink 转为仓库直接维护的完整 UPM 包,补齐运行时、编辑器工具、测试与文档 - 新增 Command 和 Network 的 App 集成组件,支持 ContextLoader 服务发布、可逆注销及 Network Loopback 生命周期管理 - 更新 Starter 与演示组合逻辑,缺失模块时可注册、已有兼容安装器时可覆盖,并补充宿主启动断言 - 升级内部包依赖与 Shared CodeGen 包定义,放宽 Integration.App 包的 Git 忽略规则 - 将独立服务器生成器改为基于已编译程序集的语义扫描,支持 partial、复杂泛型、命名冲突检测及模板 SHA-256 覆写保护 - 新增 Network 语义扫描、模板保护和 App 组件生命周期测试 - 新增真实 UPM 消费工程验证脚本,校验内部版本一致性、程序集加载及 EditMode 测试 - 重构当前架构文档并归档已完成的 Cordis 迁移与旧代码地图
368 lines
13 KiB
Markdown
368 lines
13 KiB
Markdown
# ShrinkNetwork
|
|
|
|
一个为 Unity C# 项目设计的轻量网络框架。重点不是“再包一层 Socket”,而是把消息声明、权限校验、RPC、序列化、传输层,以及独立服务器生成流程收拢到一套统一范式里。
|
|
|
|
## ✨ 特性概览
|
|
|
|
| 特性 | 说明 |
|
|
|------|------|
|
|
| 🔒 **强类型消息** | 基于 `[ShrinkNetworkMessage]` 声明消息、请求、响应,客户端与服务端共享同一套合同 |
|
|
| ⚡ **RPC 内建** | `RpcAsync` / `CallAsync` 请求响应模型,超时、错误码、路由重载统一处理 |
|
|
| 🧭 **权限与来源校验** | `ShrinkNetworkAuthority` + `Permission` 双重约束,处理器注册时直接绑定 |
|
|
| 🔁 **多传输切换** | 内置 `Loopback / TCP / KCP`,同一套业务层代码可复用 |
|
|
| 📦 **双序列化实现** | `JSON / MessagePack` 可切换,适配调试与正式环境 |
|
|
| 🧩 **特性自动注册** | 编译期注册表 + 运行时实例注册并存,静态消息与静态处理器默认不再做全域反射扫描 |
|
|
| 🏗️ **独立服务器生成** | Unity 菜单扫描项目代码,生成完整 .NET 独立服务器工程 |
|
|
| 🌉 **桥接扩展友好** | 可选接入 `ShrinkNetwork.Integration.EventBus` 等桥接层,运行时自动发现并接入 |
|
|
|
|
## 📦 依赖
|
|
|
|
- Unity 2022.3+
|
|
- [UniTask](https://github.com/Cysharp/UniTask) `2.x`
|
|
- [Newtonsoft.Json](https://docs.unity3d.com/Packages/com.unity.nuget.newtonsoft-json@3.2/manual/index.html)
|
|
- [MessagePack for C#](https://github.com/MessagePack-CSharp/MessagePack-CSharp) `3.1.4`(模板工程已同步)
|
|
|
|
## ⚙️ 目录结构
|
|
|
|
`Assets/Modules/ShrinkNetwork/` 当前按职责拆分为:
|
|
|
|
| 目录 | 说明 |
|
|
|------|------|
|
|
| `Runtime/Core/` | `ShrinkNetworkService`、`ShrinkNetworkSession`、上下文与日志 |
|
|
| `Runtime/Metadata/` | 消息合同、属性、权限、RPC、传输事件等基础类型 |
|
|
| `Runtime/Routing/` | 注册表、反射辅助、路由派发 |
|
|
| `Runtime/Serialization/` | 序列化接口与 `JSON / MessagePack` 实现 |
|
|
| `Runtime/Transport/` | `Loopback / TCP / KCP` 传输层 |
|
|
| `Editor/Scaffolding/` | 独立服务器模板与生成器 |
|
|
| `Samples/PingClient/` | 最小客户端示例 |
|
|
| `Plugins/` | 运行时第三方 DLL |
|
|
|
|
说明:
|
|
|
|
- `Assets/Scenes/` 下的演示控制器仍放在场景目录,不并入插件本体。
|
|
- 项目专属独立服务器逻辑不常驻仓库,而是通过扫描结果生成到 `GeneratedServers/`。
|
|
|
|
## 🚀 快速上手
|
|
|
|
### 第一步:声明消息
|
|
|
|
```csharp
|
|
using ShrinkNetwork;
|
|
|
|
[ShrinkNetworkMessage(1001, "auth/ping")]
|
|
public sealed class PingRequest : IShrinkNetworkRequest
|
|
{
|
|
public string Text { get; set; } = string.Empty;
|
|
}
|
|
|
|
[ShrinkNetworkMessage(1002, "auth/ping_response")]
|
|
public sealed class PingResponse : ShrinkRpcResponseBase
|
|
{
|
|
public string Reply { get; set; } = string.Empty;
|
|
}
|
|
```
|
|
|
|
### 第二步:声明处理器
|
|
|
|
```csharp
|
|
using Cysharp.Threading.Tasks;
|
|
using ShrinkNetwork;
|
|
|
|
[ShrinkNetworkSubscriber]
|
|
public static class PingHandlers
|
|
{
|
|
[ShrinkNetworkSubscribe(
|
|
Authority = ShrinkNetworkAuthority.ClientOnly,
|
|
Permission = "rpc.ping")]
|
|
private static UniTask<PingResponse> HandlePing(
|
|
ShrinkNetworkContext context,
|
|
PingRequest request)
|
|
{
|
|
return UniTask.FromResult(new PingResponse
|
|
{
|
|
Reply = "pong:" + request.Text
|
|
});
|
|
}
|
|
}
|
|
```
|
|
|
|
### 第三步:启动服务并自动注册
|
|
|
|
```csharp
|
|
var service = new ShrinkNetworkService(
|
|
new ShrinkMessagePackNetworkSerializer(),
|
|
new ShrinkNetworkMessageRegistry(),
|
|
new ShrinkNetworkRouter());
|
|
|
|
service.AutoRegisterAll();
|
|
service.BindTransport(new ShrinkTcpClientTransport("127.0.0.1", 17777));
|
|
```
|
|
|
|
### 跨线程派发与背压
|
|
|
|
`ShrinkNetworkService` 默认使用 inline 调度,适合独立服务器。传输层可能在后台线程触发事件;Unity 侧不要让网络线程直接进入会访问 Unity API 的业务 handler,应显式注入调度器:
|
|
|
|
```csharp
|
|
var dispatchQueue = new ShrinkNetworkDispatchQueue(2048);
|
|
service.DispatchScheduler = dispatchQueue;
|
|
|
|
// 在 Unity 主线程的 Update 中按预算泵出任务
|
|
dispatchQueue.PumpAsync(128).Forget();
|
|
```
|
|
|
|
队列满时默认拒绝新任务,并通过 `GetDiagnosticsSnapshot().DispatchQueueRejectedCount` 计数。实时状态可以选择丢弃策略;登录、控制命令和 RPC 不应与可丢弃状态共用同一个队列。
|
|
|
|
### 第四步:发送消息或发起 RPC
|
|
|
|
```csharp
|
|
await session.SendAsync(new PingRequest
|
|
{
|
|
Text = "hello"
|
|
});
|
|
|
|
var response = await session.RpcAsync<PingRequest, PingResponse>(
|
|
new PingRequest { Text = "hello" });
|
|
```
|
|
|
|
## 📖 核心概念
|
|
|
|
### 消息模型
|
|
|
|
ShrinkNetwork 把网络载荷分成三类:
|
|
|
|
| 类型 | 基接口 / 基类 | 用途 |
|
|
|------|---------------|------|
|
|
| 普通消息 | `IShrinkNetworkMessage` | 单向通知、状态广播 |
|
|
| 请求 | `IShrinkNetworkRequest` | 发起 RPC |
|
|
| 响应 | `ShrinkRpcResponseBase` | 返回错误码、错误消息与业务载荷 |
|
|
|
|
推荐规则:
|
|
|
|
- 所有网络消息都显式声明 `[ShrinkNetworkMessage(opcode, route)]`
|
|
- `opcode` 与 `route` 一一对应,不要复用
|
|
- 客户端与服务端共用同一消息类型定义
|
|
|
|
### 自动注册
|
|
|
|
运行时支持两类自动化:
|
|
|
|
```csharp
|
|
service.AutoRegisterAttributedMessages(); // 消费编译期消息清单
|
|
service.AutoRegisterStaticHandlers(); // 消费编译期静态处理器清单
|
|
service.AutoRegisterAll(); // 两者都做
|
|
```
|
|
|
|
这套机制适合插件化模块与独立服务器共用同一批消息合同。
|
|
|
|
当前版本中:
|
|
|
|
- 静态 `[ShrinkNetworkMessage]` / `[ShrinkNetworkSubscriber]` 由编译期注册表 `ShrinkNetworkGeneratedRegistry` 收口
|
|
- `RegisterHandlers(object target)` 仍保留实例注册路径,继续支持模组实例和运行时对象
|
|
|
|
### 权限模型
|
|
|
|
处理器可通过 `[ShrinkNetworkSubscribe]` 直接声明来源和权限:
|
|
|
|
```csharp
|
|
[ShrinkNetworkSubscribe(
|
|
Authority = ShrinkNetworkAuthority.ClientOnly,
|
|
Permission = "room.join")]
|
|
```
|
|
|
|
常见用途:
|
|
|
|
- 限制某消息只能由客户端发起
|
|
- 登录后再开放某些协议
|
|
- 独立服务器按 `Permission` 做集中裁决
|
|
|
|
### 状态同步范式
|
|
|
|
如果你希望独立服务器生成器直接产出“可运行”的同步模块,推荐额外声明:
|
|
|
|
```csharp
|
|
[ShrinkNetworkStateSync("lan", ShrinkNetworkStateSyncRole.JoinRequest)]
|
|
```
|
|
|
|
当前内置识别的角色有:
|
|
|
|
- `JoinRequest`
|
|
- `JoinResponse`
|
|
- `Command`
|
|
- `StateDelta`
|
|
- `LeaveNotice`
|
|
- `Heartbeat`
|
|
|
|
示例:
|
|
|
|
```csharp
|
|
[ShrinkNetworkMessage(4111, "lan/player_state_delta")]
|
|
[ShrinkNetworkStateSync("lan", ShrinkNetworkStateSyncRole.StateDelta)]
|
|
public sealed class LanPlayerStateDelta : IShrinkNetworkMessage
|
|
{
|
|
public string PlayerId { get; set; } = string.Empty;
|
|
public string DisplayName { get; set; } = string.Empty;
|
|
public float X { get; set; }
|
|
public float Y { get; set; }
|
|
public float VX { get; set; }
|
|
public float VY { get; set; }
|
|
public bool IsGrounded { get; set; }
|
|
public long Version { get; set; }
|
|
}
|
|
```
|
|
|
|
说明:
|
|
|
|
- 优先推荐显式特性声明
|
|
- 旧演示仍保留基于 route 的回退识别
|
|
- `opcode` / `route` 冲突不会被框架吞掉,生成阶段会直接报错并指出来源
|
|
|
|
## 🏗️ 独立服务器生成
|
|
|
|
Unity 菜单:
|
|
|
|
- `ShrinkSDK/Network/生成完整独立服务器工程`
|
|
- `ShrinkSDK/Network/刷新独立服务器 Generated 合同`
|
|
|
|
生成结果默认输出到:
|
|
|
|
`GeneratedServers/ShrinkNetwork.ServerHost/`
|
|
|
|
生成内容分两部分:
|
|
|
|
- 模板提供:宿主、TCP / KCP 服务端传输、基础鉴权与扩展点
|
|
- 语义扫描生成:从 Unity 已编译的 Player 程序集读取类型、特性、继承与属性元数据,产出 `Generated/*.g.cs` 项目专属消息合同、处理器模板和自动模块
|
|
|
|
完整工程生成会用 SHA-256 清单保护模板托管文件。再次生成前先校验全部文件仍等于上次生成版本;检测到人工修改时不会写入或删除任何模板文件。`Generated/*.g.cs` 仍属于可重建区域。
|
|
|
|
当前生成器会优先识别三类高价值范式:
|
|
|
|
- `[ShrinkNetworkStateSync]` 标记的状态同步消息组
|
|
- `[ShrinkNetworkEvent]` 标记的广播型 / 裁决型 / 增量型网络事件
|
|
- `[ShrinkNetworkSubscribe]` 标记的权限声明
|
|
|
|
扫描以编译后类型为准,因此支持 `partial` 和复杂泛型。导出合同会去掉命名空间;不同命名空间存在同名消息或同名依赖类型时,生成器会列出冲突来源并中止,而不是静默保留第一项。仅手写 `RegisterMessage<T>()` 不属于服务器生成输入。
|
|
|
|
这意味着即便不手写额外适配层,只要项目遵守同一套特性范式,独立服务器也能先生成出一套可编译、可注册、可继续补业务的骨架。
|
|
|
|
## 🔧 API 参考
|
|
|
|
### ShrinkNetworkService
|
|
|
|
```csharp
|
|
service.BindTransport(IShrinkNetworkTransport transport)
|
|
service.RegisterMessage<TMessage>(int opcode, string route = null)
|
|
service.RegisterHandler<TMessage>(Func<ShrinkNetworkContext, TMessage, UniTask> handler, requirement)
|
|
service.RegisterRequestHandler<TRequest, TResponse>(Func<ShrinkNetworkContext, TRequest, UniTask<TResponse>> handler, requirement)
|
|
service.AutoRegisterAttributedMessages()
|
|
service.AutoRegisterStaticHandlers()
|
|
service.AutoRegisterAll()
|
|
```
|
|
|
|
### ShrinkNetworkSession
|
|
|
|
```csharp
|
|
session.SendAsync<TMessage>(message, route = null)
|
|
session.NotifyAsync<TMessage>(message, route = null)
|
|
session.RpcAsync<TRequest, TResponse>(request, route = null)
|
|
session.RpcAsync<TRequest, TResponse>(request, ShrinkRpcCallOptions options)
|
|
```
|
|
|
|
### ShrinkRpcCallOptions
|
|
|
|
```csharp
|
|
new ShrinkRpcCallOptions
|
|
{
|
|
TimeoutMs = 10000,
|
|
RouteOverride = "custom/route",
|
|
RequestTokenOverride = (ShrinkRequestToken)123,
|
|
DebugLabel = "LoginRpc"
|
|
}
|
|
```
|
|
|
|
### 序列化
|
|
|
|
```csharp
|
|
new ShrinkJsonNetworkSerializer()
|
|
new ShrinkMessagePackNetworkSerializer()
|
|
```
|
|
|
|
## ✅ 最佳实践
|
|
|
|
- 所有消息统一用 `[ShrinkNetworkMessage]` 声明,不要只靠手写 `RegisterMessage<T>()`
|
|
- 客户端与服务端共用合同类型,不要维护两套“看起来一样”的 DTO
|
|
- 对高频实时协议优先考虑 `MessagePack + KCP`
|
|
- 对生成器可识别的同步模块,优先补齐 `[ShrinkNetworkStateSync]`
|
|
- 如果项目接入了桥接包,优先让 `ShrinkNetworkService` 走自动接入,不要到处手工注册扩展
|
|
|
|
## ⚠️ 注意事项
|
|
|
|
- `BindTransport(...)` 只负责绑定传输与启动监听,不会替你注册业务 handler
|
|
- `TriggerEvent` 类桥接若依赖可选程序集,当前通过运行时反射自动接入;未安装扩展包时会静默跳过
|
|
- `GeneratedServers/*/Generated/` 中的产物允许重建,不要把人工业务逻辑直接写进自动生成文件;模板托管区虽有哈希保护,也应通过独立业务文件扩展
|
|
- 运行依赖中的 `Kcp-CSharp.dll` 与 `System.Runtime.CompilerServices.Unsafe.dll` 需要与 Unity 平台兼容
|
|
|
|
## ✅ 已验证
|
|
|
|
当前命令行验证已通过:
|
|
|
|
- `dotnet build .\Assembly-CSharp.csproj`
|
|
- `dotnet build .\Assembly-CSharp-Editor.csproj`
|
|
- `dotnet build .\GeneratedServers\ShrinkNetwork.ServerHost\ShrinkNetwork.ServerHost.csproj /nr:false`
|
|
|
|
当前未覆盖:
|
|
|
|
- 在 Unity 编辑器中实际点击菜单,重新生成完整独立服务器工程后的运行时回归
|
|
|
|
## 📄 License
|
|
|
|
## 会话令牌
|
|
|
|
- `ShrinkNetworkPacket` 现已携带 `SessionToken` 与 `SessionTokenExpiresAtUnixTimeSeconds`。
|
|
- `ShrinkNetworkSession` 会在收到服务端响应包时自动学习并更新会话令牌,后续 `SendAsync / NotifyAsync / RpcAsync` 会自动把令牌带回去。
|
|
- 宿主侧如启用了会话令牌校验,推荐流程是:
|
|
1. 先调用 `server/auth/login`
|
|
2. 拿到登录响应后继续正常发包
|
|
3. 在到达续期窗口后调用 `server/auth/refresh`
|
|
- 当前实现是“宿主内存态会话令牌”:
|
|
- 适合单进程独立服
|
|
- 不包含跨进程共享、外部身份提供商或持久化会话存储
|
|
|
|
## TCP TLS
|
|
|
|
- `ShrinkTcpClientTransport` 与 `ShrinkTcpServerTransport` 现已支持可选 TLS。
|
|
- 客户端通过 `ShrinkTcpTlsOptions` 传入:
|
|
- `Enabled`
|
|
- `TargetHost`
|
|
- `AllowInvalidServerCertificate`
|
|
- `CheckCertificateRevocation`
|
|
- `EnabledProtocols`
|
|
- 服务端额外需要:
|
|
- `ServerCertificatePath`
|
|
- `ServerCertificatePassword`
|
|
- 当前范围仅覆盖“服务端证书 + 客户端校验”主链,不包含双向证书认证。
|
|
|
|
```csharp
|
|
var clientTransport = new ShrinkTcpClientTransport(
|
|
"127.0.0.1",
|
|
17777,
|
|
tlsOptions: new ShrinkTcpTlsOptions
|
|
{
|
|
Enabled = true,
|
|
TargetHost = "game.example.com"
|
|
});
|
|
|
|
var serverTransport = new ShrinkTcpServerTransport(
|
|
System.Net.IPAddress.Any,
|
|
17777,
|
|
tlsOptions: new ShrinkTcpTlsOptions
|
|
{
|
|
Enabled = true,
|
|
ServerCertificatePath = "certs/server.pfx",
|
|
ServerCertificatePassword = "change-me"
|
|
});
|
|
```
|
|
|
|
## 📄 License
|
|
|
|
[MIT](LICENSE)
|