Files

370 lines
13 KiB
Markdown

# ShrinkNetwork
一个可用于 Unity、Godot 和普通 .NET 宿主的轻量网络框架。重点不是“再包一层 Socket”,而是把消息声明、权限校验、RPC、序列化、传输层,以及独立服务器生成流程收拢到一套统一范式里。
Godot 和普通 .NET 项目安装 `ShrinkSDK.Network`;可打包源码位于 `DotNet~`,消息合同和 handler 注册由 `ShrinkSDK.CodeGen` 在构建时织入。
## ✨ 特性概览
| 特性 | 说明 |
|------|------|
| 🔒 **强类型消息** | 基于 `[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/网络/生成完整独立服务器工程`
- `ShrinkSDK/网络/刷新独立服务器生成合同`
生成结果默认输出到:
`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
- EventBus 桥接统一使用 `Post/PostAsync` 和编译期生成注册;未安装 `ShrinkNetwork.Integration.EventBus` 时不会建立桥接,也不会做运行时程序集扫描
- `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)