【OpenSourceC#】C#服务端RPC框架-DaServer

前言

Unity框架JEngine作者开发的C#服务端RPC框架,看着设计挺新颖的,服务端使用ECS模式。
这个框架使用了作者自己写的Nino序列化方案,要替换成protobuf的话我看需要改一下TcpServer和消息处理方式,如果以后有需求可以改一下,不过如果作为个人开发,使用Nino影响不大。(就怕踩坑)
附上官方链接:DaServer

架构

github首页画出来uml图,不过得先看懂代码才能看懂这个uml图…
首先理解框架的结构,分成了三个程序集,Share、Server、Client。
Share是客户端和服务端共用代码。
Server是服务端代码。
Client是客户端。

框架的核心概念在DaServer.Core里,如下图
image.png

Entity

Entity实体,实际就是一个服务,里面有个Timer,每10ms调用Update()

Component

这里的component 是Entity 的componnet,在服务端还有另外一套Actor-System-Component模式。

Session

一条连接的封装

RemoveCall

rpc请求的封装

Network

TcpServer和TcpClient,tcp底层的封装,通信协议定义为 4字节长度+消息体,消息体用Nino序列化。

Message

网络通信的消息,Request和Response都要继承自IMessage,并通过属性标明消息Id,具体见示例

DaServer

DaShare只是把用到的基础功能搭好了,服务端程序启动入口是在DaServer.Program.cs
image.png
入口很简单,启动了一个Entity,增加了三个组件,不同的组件提供不同的功能。
在增加组件时会以调用组件的create方法。之后Entity的update的循环中会依次调用各组件的update。

TcpComponent & RemoteCallComponent

在这个组件中启动 TCP监听。注意下面这段代码,当收到数据的时候,会解析成RemoteCall结构,加入收到消息队列。在Update中再调用 RemoteCallComponent组件,在这个组件也是先放入一个队列,在update中再处理。

OnClientDataReceived += (id, data) =>
{
    
    
    //get session
    if (_sessions.TryGetValue(id, out _))
    {
    
    
        //process data
        _queue.Enqueue((id, MessageFactory.GetRemoteCall(data)));
    }
};

ActorSystemComponent

这个组件实现了system component机制。
一个actor可以理解为一个玩家。
这个组件负责管理所有玩家。
当玩家发送rpc请求的时候。如果该组件没有该玩家的actor,就会创建一个。
在该组件创建过程中,会起一个线程死循环进行调用actor的update。
在本组件的update中发送信号,启动actor线程的更新。
actor的更新实际上是调用所有的actor system。如果这个system是可以更新的,就调用对应的update。
System的update实际上是调用了该System持有的各个组件的update。
组件有更新间隔,超过10ms才会更新。

总结一下
这里的A-S-C的关系,就是C知道自己属于哪个S和哪个A,S持有所有Actor所有该类型的组件,Actor通过ActorSystemComponent 组件来操作自己的S和C。
更新的时候,ActorSystemComponent 更新调用所有System更新,System依次调用自己持有的Component的更新,不关注这个Component属于哪个Actor。

目前实现里两个Actor组件,分别是SessionComponent和RequestComponent,SessionComponent实现连接断开后Actor的删除,RequestComponent就是处理RPC请求的组件。

处理RPC

在上述的RemoteCallComponent组件更新中会根据session找到actor,把rpc请求放到对应actor的RequestComponent 组件中。
在Request组件更新中调用响应方法,并不阻塞返回响应结果

RPC示例

参考DaServer.UnitTest,直接写对应消息的处理方法就行

DaClient

这里只是演示一下客户端怎么使用,如下例,连上后并行发送了100个同样的请求,await异步等待响应

client.OnConnected += () =>
{
    
    
    Logger.Info("客户端连上了服务端");
    Parallel.For(0, 100, async (i, _) =>
    {
    
    
        await Task.Delay(i * 10);
        var response = await Request<MTestRequest, MTestResponse>(client, new MTestRequest()
        {
    
    
            Txt = "hello"
        });
        Logger.Info("客户端收到了服务端的回应: {@c}", response);
        Logger.Info("多线程任务{i} 收到回应后的线程ID: {c}", i, Thread.CurrentThread.ManagedThreadId);
    });
};

需要注意的是,使用了TaskCompletionSource保证了响应在同一个线程里

Request里会调用GetResponse 新建TaskCompletionSource 对象,设置响应超时。
在收到响应的处理中,会调用AddResponse,设置这个tcs.SetResult,这样await Request就能成功返回,并且和请求时在同一个线程。

猜你喜欢

转载自blog.csdn.net/LvPartner/article/details/129341556
今日推荐