Files
ShrinkNetwork/README.md
T
cneicy 93c2b8520b
Publish UPM package / publish (push) Successful in 2s
Publish NuGet packages / publish (push) Successful in 3m5s
feat(network)!: add v2 framing, pooled buffers and bounded dispatch
2026-09-29 10:14:47 +08:00

381 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 ShrinkJsonNetworkSerializer(),
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()
```
生成式 MessagePack 通过可选包 `Adapters~/MessagePack` 接入。使用 `ShrinkNetwork.MessagePack.ShrinkMessagePackNetworkSerializer(GeneratedResolver.Instance)` 并显式 `Register<T>()`;旧无参反射适配器已移除。消息体只编码一次,外层统一使用协议 v2 的 SHK2 二进制封装。
## ✅ 最佳实践
- 所有消息统一用 `[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)
## 可复用能力
<!-- shrink:capabilities -->
Capability: 消息合同、RPC、权限、TCP/KCP/Loopback 传输与有预算的串行排队
Aliases: 联机 网络 远程 rpc 广播 拥塞 背压 同步
Limits: 协议 v2 拒绝旧封装;TCP/KCP 均为可靠通道;可替换状态仅合并排队项,不用于快照分片
Extension: RegisterMessage/RegisterHandler;IShrinkNetworkBufferSerializer;ShrinkNetworkWorkQueue 状态键
Evidence: [ShrinkNetworkService](Runtime/Core/ShrinkNetworkService.cs); [ShrinkPacketCodec](Runtime/Serialization/ShrinkPacketCodec.cs); [ShrinkNetworkWorkQueue](Runtime/Core/ShrinkNetworkWorkQueue.cs)
<!-- /shrink:capabilities -->