# 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 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( 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()` 不属于服务器生成输入。 这意味着即便不手写额外适配层,只要项目遵守同一套特性范式,独立服务器也能先生成出一套可编译、可注册、可继续补业务的骨架。 ## 🔧 API 参考 ### ShrinkNetworkService ```csharp service.BindTransport(IShrinkNetworkTransport transport) service.RegisterMessage(int opcode, string route = null) service.RegisterHandler(Func handler, requirement) service.RegisterRequestHandler(Func> handler, requirement) service.AutoRegisterAttributedMessages() service.AutoRegisterStaticHandlers() service.AutoRegisterAll() ``` ### ShrinkNetworkSession ```csharp session.SendAsync(message, route = null) session.NotifyAsync(message, route = null) session.RpcAsync(request, route = null) session.RpcAsync(request, ShrinkRpcCallOptions options) ``` ### ShrinkRpcCallOptions ```csharp new ShrinkRpcCallOptions { TimeoutMs = 10000, RouteOverride = "custom/route", RequestTokenOverride = (ShrinkRequestToken)123, DebugLabel = "LoginRpc" } ``` ### 序列化 ```csharp new ShrinkJsonNetworkSerializer() new ShrinkMessagePackNetworkSerializer() ``` ## ✅ 最佳实践 - 所有消息统一用 `[ShrinkNetworkMessage]` 声明,不要只靠手写 `RegisterMessage()` - 客户端与服务端共用合同类型,不要维护两套“看起来一样”的 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)