2026-09-05 02:34:02 +08:00
2026-09-05 02:34:02 +08:00

ShrinkNetwork

一个为 Unity C# 项目设计的轻量网络框架。重点不是“再包一层 Socket”,而是把消息声明、权限校验、RPC、序列化、传输层,以及独立服务器生成流程收拢到一套统一范式里。

特性概览

特性 说明
🔒 强类型消息 基于 [ShrinkNetworkMessage] 声明消息、请求、响应,客户端与服务端共享同一套合同
RPC 内建 RpcAsync / CallAsync 请求响应模型,超时、错误码、路由重载统一处理
🧭 权限与来源校验 ShrinkNetworkAuthority + Permission 双重约束,处理器注册时直接绑定
🔁 多传输切换 内置 Loopback / TCP / KCP,同一套业务层代码可复用
📦 双序列化实现 JSON / MessagePack 可切换,适配调试与正式环境
🧩 特性自动注册 编译期注册表 + 运行时实例注册并存,静态消息与静态处理器默认不再做全域反射扫描
🏗️ 独立服务器生成 Unity 菜单扫描项目代码,生成完整 .NET 独立服务器工程
🌉 桥接扩展友好 可选接入 ShrinkNetwork.Integration.EventBus 等桥接层,运行时自动发现并接入

📦 依赖

⚙️ 目录结构

Assets/Modules/ShrinkNetwork/ 当前按职责拆分为:

目录 说明
Runtime/Core/ ShrinkNetworkServiceShrinkNetworkSession、上下文与日志
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)]
  • opcoderoute 一一对应,不要复用
  • 客户端与服务端共用同一消息类型定义

自动注册

运行时支持两类自动化:

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)]

当前内置识别的角色有:

  • JoinRequest
  • JoinResponse
  • Command
  • StateDelta
  • LeaveNotice
  • Heartbeat

示例:

[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

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(...) 只负责绑定传输与启动监听,不会替你注册业务 handler
  • EventBus 桥接统一使用 Post/PostAsync 和编译期生成注册;未安装 ShrinkNetwork.Integration.EventBus 时不会建立桥接,也不会做运行时程序集扫描
  • GeneratedServers/*/Generated/ 中的产物允许重建,不要把人工业务逻辑直接写进自动生成文件;模板托管区虽有哈希保护,也应通过独立业务文件扩展
  • 运行依赖中的 Kcp-CSharp.dllSystem.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 现已携带 SessionTokenSessionTokenExpiresAtUnixTimeSeconds
  • ShrinkNetworkSession 会在收到服务端响应包时自动学习并更新会话令牌,后续 SendAsync / NotifyAsync / RpcAsync 会自动把令牌带回去。
  • 宿主侧如启用了会话令牌校验,推荐流程是:
    1. 先调用 server/auth/login
    2. 拿到登录响应后继续正常发包
    3. 在到达续期窗口后调用 server/auth/refresh
  • 当前实现是“宿主内存态会话令牌”:
    • 适合单进程独立服
    • 不包含跨进程共享、外部身份提供商或持久化会话存储

TCP TLS

  • ShrinkTcpClientTransportShrinkTcpServerTransport 现已支持可选 TLS。
  • 客户端通过 ShrinkTcpTlsOptions 传入:
    • Enabled
    • TargetHost
    • AllowInvalidServerCertificate
    • CheckCertificateRevocation
    • EnabledProtocols
  • 服务端额外需要:
    • ServerCertificatePath
    • ServerCertificatePassword
  • 当前范围仅覆盖“服务端证书 + 客户端校验”主链,不包含双向证书认证。
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

S
Description
ShrinkSDK Unity UPM package.
Readme
168 KiB
Languages
C# 100%