- 将 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 迁移与旧代码地图
13 KiB
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
2.x - Newtonsoft.Json
- MessagePack for C#
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/。
🚀 快速上手
第一步:声明消息
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;
}
第二步:声明处理器
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
});
}
}
第三步:启动服务并自动注册
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,应显式注入调度器:
var dispatchQueue = new ShrinkNetworkDispatchQueue(2048);
service.DispatchScheduler = dispatchQueue;
// 在 Unity 主线程的 Update 中按预算泵出任务
dispatchQueue.PumpAsync(128).Forget();
队列满时默认拒绝新任务,并通过 GetDiagnosticsSnapshot().DispatchQueueRejectedCount 计数。实时状态可以选择丢弃策略;登录、控制命令和 RPC 不应与可丢弃状态共用同一个队列。
第四步:发送消息或发起 RPC
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一一对应,不要复用- 客户端与服务端共用同一消息类型定义
自动注册
运行时支持两类自动化:
service.AutoRegisterAttributedMessages(); // 消费编译期消息清单
service.AutoRegisterStaticHandlers(); // 消费编译期静态处理器清单
service.AutoRegisterAll(); // 两者都做
这套机制适合插件化模块与独立服务器共用同一批消息合同。
当前版本中:
- 静态
[ShrinkNetworkMessage]/[ShrinkNetworkSubscriber]由编译期注册表ShrinkNetworkGeneratedRegistry收口 RegisterHandlers(object target)仍保留实例注册路径,继续支持模组实例和运行时对象
权限模型
处理器可通过 [ShrinkNetworkSubscribe] 直接声明来源和权限:
[ShrinkNetworkSubscribe(
Authority = ShrinkNetworkAuthority.ClientOnly,
Permission = "room.join")]
常见用途:
- 限制某消息只能由客户端发起
- 登录后再开放某些协议
- 独立服务器按
Permission做集中裁决
状态同步范式
如果你希望独立服务器生成器直接产出“可运行”的同步模块,推荐额外声明:
[ShrinkNetworkStateSync("lan", ShrinkNetworkStateSyncRole.JoinRequest)]
当前内置识别的角色有:
JoinRequestJoinResponseCommandStateDeltaLeaveNoticeHeartbeat
示例:
[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
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
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
new ShrinkRpcCallOptions
{
TimeoutMs = 10000,
RouteOverride = "custom/route",
RequestTokenOverride = (ShrinkRequestToken)123,
DebugLabel = "LoginRpc"
}
序列化
new ShrinkJsonNetworkSerializer()
new ShrinkMessagePackNetworkSerializer()
✅ 最佳实践
- 所有消息统一用
[ShrinkNetworkMessage]声明,不要只靠手写RegisterMessage<T>() - 客户端与服务端共用合同类型,不要维护两套“看起来一样”的 DTO
- 对高频实时协议优先考虑
MessagePack + KCP - 对生成器可识别的同步模块,优先补齐
[ShrinkNetworkStateSync] - 如果项目接入了桥接包,优先让
ShrinkNetworkService走自动接入,不要到处手工注册扩展
⚠️ 注意事项
BindTransport(...)只负责绑定传输与启动监听,不会替你注册业务 handlerTriggerEvent类桥接若依赖可选程序集,当前通过运行时反射自动接入;未安装扩展包时会静默跳过GeneratedServers/*/Generated/中的产物允许重建,不要把人工业务逻辑直接写进自动生成文件;模板托管区虽有哈希保护,也应通过独立业务文件扩展- 运行依赖中的
Kcp-CSharp.dll与System.Runtime.CompilerServices.Unsafe.dll需要与 Unity 平台兼容
✅ 已验证
当前命令行验证已通过:
dotnet build .\Assembly-CSharp.csprojdotnet build .\Assembly-CSharp-Editor.csprojdotnet build .\GeneratedServers\ShrinkNetwork.ServerHost\ShrinkNetwork.ServerHost.csproj /nr:false
当前未覆盖:
- 在 Unity 编辑器中实际点击菜单,重新生成完整独立服务器工程后的运行时回归
📄 License
会话令牌
ShrinkNetworkPacket现已携带SessionToken与SessionTokenExpiresAtUnixTimeSeconds。ShrinkNetworkSession会在收到服务端响应包时自动学习并更新会话令牌,后续SendAsync / NotifyAsync / RpcAsync会自动把令牌带回去。- 宿主侧如启用了会话令牌校验,推荐流程是:
- 先调用
server/auth/login - 拿到登录响应后继续正常发包
- 在到达续期窗口后调用
server/auth/refresh
- 先调用
- 当前实现是“宿主内存态会话令牌”:
- 适合单进程独立服
- 不包含跨进程共享、外部身份提供商或持久化会话存储
TCP TLS
ShrinkTcpClientTransport与ShrinkTcpServerTransport现已支持可选 TLS。- 客户端通过
ShrinkTcpTlsOptions传入:EnabledTargetHostAllowInvalidServerCertificateCheckCertificateRevocationEnabledProtocols
- 服务端额外需要:
ServerCertificatePathServerCertificatePassword
- 当前范围仅覆盖“服务端证书 + 客户端校验”主链,不包含双向证书认证。
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"
});