SkillAgentSearch skills...

TinyRPC

为 Unity 准备的使用 TCP + JsonUtility + Task 实现的极简的现代网络框架,支持 RPC 和常规消息收发。使用 upm 管理,对项目文件 0 侵入;A minimalistic modern networking framework prepared for Unity using TCP, JsonUtility, and Task to implement support for RPC and regular message sending/receiving. Managed via UPM with zero intrusion into the project files.

Install / Use

/learn @Bian-Sh/TinyRPC
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

本说明有配图,请确保你的网络畅通,图文模式下阅读更佳。

<a id="chinese">English</a>

TinyRPC

TinyRPC 是一个为 Unity 引擎准备的,没有第三方插件依赖、仅使用 TCP + JsonUtility + Task实现的极简 RPC 网络框架,支持 RPC/常规 消息分发。它的目标是提供一个轻量级、易于使用的网络通信解决方案。

这个网络框架很多地方学习、参考了 ET ,在此表示感谢。

基于 TinyRPC 的示例项目 :unity-drones-multiplayer-tinyrpc

图片借用 unity-drones-multiplayer的配图, 图示内容不代表最终效果!

特性

  • 支持局域网网络发现,提供了一个简单的测试用例,自动发现并连接到局域网内的服务器。

  • 支持async await 异步逻辑同步写的语法糖,让你的代码更加简洁优雅且易读。

  • 支持客户端和服务器互发 RPC 请求。

  • 支持常规网络消息的收发

  • 支持基于 TCP 建立连接之上的网络通信,完整的生命周期管理,断线即刻感知。

  • 2 种消息处理的注册方案:监听模式 、Attribute 标注模式

功能

消息的发送

  • 使用 Send(message) 发送普通网络消息

  • 使用 var response = await Call(request) 发送 RPC 请求并等待响应

观察者模式注册的消息处理器

  • 使用 UnityEngine.Component.AddNetworkSignal<Session,T>() 注册一个普通的网络处理器

  • 使用 UnityEngine.Component.AddNetworkSignal<Session,TRequest,TResponse>() 注册一个 RPC 处理器

通过反射自动注册消息处理器

  • 使用 [MessageHandlerProviderAttribute] 标记一个消息处理器容器(类型)
  • 使用 [MessageHandlerAttribute(MessageType.Normal)] 标记一个消息处理器
  • 使用 [MessageHandlerAttribute(MessageType.RPC)] 标记一个 RPC 消息处理器

消息类一键生成

使用基于 proto3 精简版语法的 .proto 文件,可以一键生成消息类。如果存在多个 .proto 文件则会将消息生成在以 .proto 文件名命名的文件夹中。

支持将生成的消息存在 Assets、Project 同级以及 Packages 文件夹中。他们的优越性在于生成在 Packages 文件夹中不会对用户工程目录有任何侵入性;存在 Project 同级目录将最大化的实现消息文件在多个工程中的复用(本项目架构情形)。

运行时参数配置界面

提供了一个可以编辑器下修改、运行时生效的配置界面,可以配置日志过滤器;心跳间隔和重试次数;同时也会自动记录消息处理器所在的程序集信息

RPC 原理

  • TinyRPC 使用 System.Threading.Tasks 命名空间下的 TaskCompletionSource 来实现 RPC 的异步等待。

  • 使用 System.Threading.Tasks 命名空间下的 CancellationTokenSource.CancelAfter(delay) 来实现 RPC 的超时控制。

  • 使用对 UnityEngine.Component 新增扩展方法的方式实现在 MonoBehaviour 实例中像使用本地函数一样注册消息处理器

  • 使用 System.Reflection 命名空间下的 MethodInfo 配合 Delegate.CreateDelegate(typeof(Action<Session, T>), method) 实现对 MessageHandlerAttribute(MessageType.Normal) 标注的常规消息处理器的自动注册

  • 使用 System.Reflection 命名空间下的 MethodInfo 配合 Delegate.CreateDelegate(typeof(Func<Session, TRequest, TResponse, Task>), method) 实现对 MessageHandlerAttribute(MessageType.RPC) 标注的 RPC 消息处理器的自动注册

  • 使用 MessageWrapper 记录消息的类型信息及其 Json 数据来实现通过 JsonUtility 也能对多继承消息的序列化/反序列化

安装

通过 git URL 安装

  1. 点击 Window->Package Manager 打开 Package Manager 窗口

  2. https://github.com/Bian-Sh/TinyRPC.git/?path=Common/TinyRPC 粘贴到 Package Manager 中(大小写敏感)

  3. 想要支持从 git URL 安装,需要使用 Unity 2019.3.4f1 或更高版本

  4. 在中国使用 git URL 安装的成功率较低,请自行选择其他安装方式。

快速开始

  1. 克隆此仓库到你的本地机器上。

  2. 在 Unity 编辑器中分别打开 ClientServer 项目

  3. 先运行 Server ,此时会自动创建服务器

  4. 再运行 Client , 点击 Play 后在 Game 窗口可以连接/断开服务器,通过 SendRPC 测试 RPC 会话。

  5. 本项目使用 Unity 2021.3.11f2 开发 ,请使用此版本或者更高阶版本

  1. 你也可以把 TinyRPC 提供的 Server 示例 打包成 Dedicated Server 部署在 Dedicated Server 支持的系统或平台上!

Dedicated Server

Tinyrpc Ds

登录、消息发送

在 Unity 客户端中,我们使用了以下关键 API:

  • client.ConnectAsync(): 这个方法用于连接到服务器。
  • client.Send(message): 这个方法用于发送一个普通消息。
  • await client.Call<TestRPCResponse>(request): 这个方法用于发送一个 RPC 请求并等待响应。

登录逻辑

下面是 TinyRPC 连接服务器逻辑

也模拟了网络不好情况下的登录表现,展示了如何通过 async 异步对登录流程的优雅控制。

  private async void StartConnectAsync()
  {
      connect.interactable = false;

      //模拟网络延迟情况下的登录
      //1. 显示登录中...
      var tcs = new CancellationTokenSource();
      _ = TextLoadingEffectAsync(tcs);

      //2. 模拟一个延迟完成的登录效果
      var delay = Task.Delay(3000);
      var task = client.ConnectAsync();
      await Task.WhenAll(delay, task);

      //3. 取消登录中...的显示
      tcs.Cancel();

      //4. 转换 connect 字样为 disconnect
      connect.GetComponentInChildren<Text>().text = "Disconnect";
      connect.interactable = true;
  }

这段逻辑中,我使用 Task.WhenAll + Task.Delay 实现了一个长时间的登录效果,这样文本组件就有足够时间展示 Connect... 动画了,当登录完成,就将文本改为 Disconnect,方便下个回合的交互。

当然,你还可以对这段逻辑 Try Catch,处理登录失败的情况,这里我就不誊写啦,更多交互细节请运行示例项目体验。

RPC 消息发送

下面是 TinyRPC 发送 RPC 并等待回应的逻辑,同样,得益于 RPC 的使用,与服务器的对话再也不需要调用 监听者模式这种割裂的交互方式了(TinyRPC也支持监听模式,毕竟还有常规消息要处理嘛)

        public async void SendRPCAsync()
        {
            if (client != null && client.IsConnected)
            {
                var request = new TestRPCRequest();
                request.name = "request from tinyrpc client";
                var response = await client.Call<TestRPCResponse>(request);
            }
        }

这段逻辑中,我先构建了一个请求,并告知服务请求的信息,接着等待服务器返回的数据。

常规消息发送

下面是 Tiny RPC 发送常规消息的逻辑,构建一个 Normal 消息,调用 Send 就好啦。

        private void SendNormalMessage()
        {
            if (client != null && client.IsConnected)
            {
                var message = new TestMessage
                {
                    message = "normal message from tinyrpc client",
                    age = 999
                };
                client.Send(message);
            }
        }

消息处理

在服务器端,我们使用了消息处理器来处理接收到的各类消息:Normal 消息、RPC 消息、Ping 消息。

当然,在客户端也支持通过注册消息处理器来处理 Normal 消息、RPC 消息 (Ping 消息除外)进而实现用户业务逻辑的展开。

Ping 消息处理器

Ping 消息是一个内置的自响应消息,交由系统自己处理,用户无需关注

#region Ping Message Handler 
private static async Task OnPingRecevied(Session session, Ping request, Ping response) 
{ 
    response.Id = request.Id; 
    response.time = ServerTime; 
    await Task.CompletedTask(); 
} 
#endregion 

RPC 消息处理器

  1. 下面的示例脚本演示如何通过 MessageHandlerProviderAttributeMessageHandlerAttribute 声明 RPC 消息处理器
[MessageHandlerProvider]
class Foo
{
    [MessageHandler(MessageType.RPC)]
    private static async Task RPCMessageHandler(Session session, TestRPCRequest request, TestRPCResponse response)
    {
        Debug.Log($"{nameof(TestServer)}: Receive {session} request {request}");
        await Task.Delay(1000);
        response.name = "response  from  tinyrpc server !";
    }
}

这个消息处理器收到 RPC 请求后,间隔了一秒钟,然后向请求端发出响应信息: “response from tinyrpc server ”。

  1. 下面示例脚本中演示如何使用 UnityEngine.Component.AddNetworkSignal<Session,TRequest,TResponse>() 注册/注销 RPC 消息处理器
using System.Threading.Tasks;
using UnityEngine;
using zFramework.TinyRPC;
using zFramework.TinyRPC.Generated;

public class Foo : MonoBehaviour
{
    private void OnEnable()=>this.AddNetworkSignal<TestRPCRequest, TestRPCResponse>(RPCMessageHandler);

    private void OnDisable()=>this.RemoveNetworkSignal<TestRPCRequest, TestRPCResponse>(RPCMessageHandler);
    
    private static async Task RPCMessageHandler(Session session, TestRPCRequest request, TestRPCResponse response)
    {
        await Task.Delay(500);
        response.name = $"response  from  tinyrpc {(session.IsServerSide ? "SERVER" : "CLIENT")}  !";
    }
}

普通消息处理器

  1. 下面的示例脚本中演示如何使用 MessageHandlerProviderAttributeMessageHandlerAttribute 声明普通消息处理器
[MessageHandlerProvider]
class Foo
{
	[MessageHandler(MessageType.Normal)]
	private static void NormalMessageHandler(Session session, TestMessage message)
	{
		Debug.Log($"{nameof(TestServer)}: Receive {session} message {message}");
	}
}

这个消息处理器收到普通消息后,直接 log 输出到屏幕。

  1. 下面示例脚本中演示如何使用 UnityEngine.Component.AddNetworkSignal<Session,T>() 注册普通消息处理器

using UnityEngine;
using zFramework.TinyRPC;
using zFramework.TinyRPC.Generated;

public class Foo: MonoBehaviour
{
    private void OnEnable()=>this.AddNetworkSignal<TestMessage>(OnTestMessageReceived);

    private void OnDisable()=>this.RemoveNetworkSignal<TestMessage>(OnTestMessageReceived);

    private void OnTestMessageReceived(Session session, TestMessage message)
    {
        Debug.Log($"获取到{(session.IsServerSide ? "客户端" : "服务器")}  {session}  的消息, message = {message}");
    }
}

框架架构

下面是 TinyRPC 的文件系统树,点击可以看到完整网络架构

<details> <summary> 点我 ^_^</summary>
<root>
|   
+---Editor
|   |   
|   +---Analyzer
|   |       MessageHandlerPostprocessor.cs
|   |       
|   +---Async
|   |       TaskDriver.cs
|   |       UpmRequestAwaiter.cs
|   |       UpmRequestExtension.cs
|   |       
|   +---CodeGen
|   |       ProtoContentProcessor.cs
|   |       TinyProtoHandler.cs
|   |       
|   +---Data
|   |       ScriptInfo.cs
|   |       ScriptType.cs
|   |       
|   +---GUI
|   |       EditorSettingsLayout.cs
|   |       RuntimeSettingsLayout.cs
|   |       TinyRpcEditorWindow.cs
|   |       
|   \---Settings
|           EditorSettingWatcher.cs
|           ScriptableSingleton.cs
|           TinyRpcEditorSettings.cs
|           
\---Runtime
    |   
    +---Data
    |       IReusable.cs
    |       MessageType.cs
    |       MessageWrapper.cs
    |       ObjectPool.cs
    |       RpcInfo.cs
    |       SerializeHelper.cs
    |       TinyRpcSettings.cs
    |       
    +---Discovery
    |       DiscoveryClient.cs
    |       DiscoveryServer.cs
    |       
    +---Exception
    |       InvalidSessionException.cs
    |       RpcResponseException.cs
    |       RpcTimeoutException.cs
    |       
    +---Handler
    |   |   
    |   +---Attribute
    |   |       MessageHandlerAttribute.cs
    |   |       MessageHandlerProviderAttribute.cs
    |   |       
    |   +---Base
    |   |       NormalMessageHandler.cs
    |   |       RpcMessageHandler.cs
    |   |       
    |   +---Extension
    |   |       MessageHandlerEx.cs
    |   |       
    |   \---Interface
    |           INormalMessageHandler.cs
    |           IRpcMessageHandler.cs
    |           
    +---Internal
    |       Manager.cs
    |       Session.cs
    |       TinyClient.cs
    |       TinyServer.cs
    |       
    \---Message
        |   
        +---Attribute
        |       ResponseTypeAttribute.cs
        |       
        +---Base
        |       Message.cs
        |       Ping.cs
        |       Request.cs
        |       Response.cs
        |       
        \---Interface
                IMessage.cs
                IRequest.cs
                IResponse.cs
                IRpcMessage.cs
                

Related Skills

View on GitHub
GitHub Stars138
CategoryCustomer
Updated1mo ago
Forks13

Languages

C#

Security Score

85/100

Audited on Feb 14, 2026

No findings