ET框架---ActorProxyComponent学习笔记(ActorProxy)

原创 2018年04月16日 20:45:52

ActorProxyComponent

请大家关注我的微博:@NormanLin_BadPixel坏像素


我们看到,这个组件订阅了Start事件。而且是一个异步启动的方法,作用如作者注释的,每10s扫描一次过期的actorproxy进行回收,过期时间是1分钟。代码也很好懂,这里就不解释了。不过从这里我们大概能猜到,这其实就是一个管理所有ActorProxy的组件。后面的GetRemove方法就不讲了,需要注意的是,当Get方法查询到字典里没有指定Id的ActorProxy时,会根据Id创建一个新的存入字典。

ActorProxy

在看更多代码之前,我们需要搞懂新的东西ActorTask是什么,

ActorTask

我们看到了熟悉的MessageObject,我们在ClientFrameComponent学习笔记里面稍微提到了一下。其实就是一个基类,具体要看其派生类。

ActorRequestActorResponse就是自定义的消息类型。但是我们现在还不知道这个Actor到底什么用,就当它是一个通信消息类型。我们得看MessageObject具体是什么消息才能知道它的作用。

ActorResponse response = (ActorResponse)await this.proxy.RealCall(request, this.proxy.CancellationTokenSource.Token);

通过ActorProxy异步发送请求并且等待回复。我们可以回到ActorProxy了。

ActorProxy

作者对几个属性的注释很详细了。我这里再提一下,RunningTasksWaitingTasks定是重要的,两者共同管理Actor消息的发送。而LastSendTime决定了这个ActorProxy被自动释放的时刻。

我们先来看看Start方法。

public async void Start()
{
    int appId = await Game.Scene.GetComponent<LocationProxyComponent>().Get(this.Id);
    this.Address = Game.Scene.GetComponent<StartConfigComponent>().Get(appId).GetComponent<InnerConfig>().IPEndPoint;

    this.UpdateAsync();
}

这里的新家伙太多,我们分个P。LocationProxyComponent学习笔记

我们知道,在往地址服务器里注册新数据的时候,传入的是实体对象的Id。但是ActorProxy是Component,这里是通过this.Id来获取。不要着急,我们先来看看,我们都是怎么添加ActorProxy组件的。

所有的ActorProxy都是通过ActorProxyComponentGet方法获取的。

public ActorProxy Get(long id)
{
    if (this.ActorProxys.TryGetValue(id, out ActorProxy actorProxy))
    {
        return actorProxy;
    }

    actorProxy = ComponentFactory.CreateWithId<ActorProxy>(id);
    this.ActorProxys[id] = actorProxy;
    return actorProxy;
}

而这个方法是指定Id的,所以,我们在创建的时候只要把实体的ID传入就可以了。

private async void UpdateAsync()
{
    while (true)
    {
        ActorTask actorTask = await this.GetAsync();
        if (this.IsDisposed)
        {
            return;
        }
        try
        {
            this.RunTask(actorTask);
        }
        catch (Exception e)
        {
            Log.Error(e.ToString());
            return;
        }
    }
}

我们看到,在创建一个ActorProxy之后,就会开始异步处理所有针对该ActorProxyActorTask任务。

首先是异步获取到对这个ActorProxy的任务,因为有可能这个ActorProxy被自然回收了(10s内没接收到新的消息),在Dispose里面会返回一个空的ActorProxy来结束这个异步操作。所以,在这个时候会判断该对象已经被释放了,不进行任何操作直接返回。

如果有任务,则通过RunTask来处理任务。

当我们要添加一个任务给这个Actor代理时,会先把任务存入等待处理的队列。在尝试从等待队列中获取任务并处理,如果之前的任务失败了并且在重试,则不处理。

在什么情况下我们无法从等待队列中获取到任务呢?
1. 没有获取的需求。this.tcs == null,this.tcs会在GetAsync里面实例化。
2. 等待队列里没有数据。
3. 在执行的任务数量大于等于最大并行执行数。(这里是1)

否则,我们可以从等待队列中获取到任务,并把它压入RunningTasks队列当中。重置this.tcs,并且返回获取到的ActorTask以完成UpdateAsync里面的异步等待。

ActorTask actorTask = await this.GetAsync();

之后,我们就要对这个任务进行处理。

我们看到,会先运行ActorTaskRun方法。在该方法里,会通过发送这个请求的ActorProxy的Id跟消息组成的ActorRequest。发送的方法在ActorProxy.RealCall 里面。快速看一下这个方法

public async Task<IResponse> RealCall(ActorRequest request, CancellationToken cancellationToken)
{
    try
    {
        //Log.Debug($"realcall {MongoHelper.ToJson(request)} {this.Address}");
        request.Id = this.Id;
        Session session = Game.Scene.GetComponent<NetInnerComponent>().Get(this.Address);
        IResponse response = await session.Call(request, cancellationToken);
        return response;
    }
    catch (RpcException e)
    {
        Log.Error($"{this.Address} {e}");
        throw;
    }
}

我们看到,是通过NetInnerComponent组件获得与该ActorProxy所在服务器的会话Session,并且发送请求。无论成功失败都会返回一个结果。

如果请求成功,则更新该ActorProxy最后一次通话的时间和一些数据。

// 发送成功
this.LastSendTime = TimeHelper.Now();
this.failTimes = 0;
if (this.WindowSize < MaxWindowSize)
{
    ++this.WindowSize;
}

this.RunningTasks.Dequeue();
this.AllowGet();

并且会尝试再获取任务处理。如果等待队列还有,并且满足可处理的条件,则会再次进行一次上面的逻辑。

如果请求失败,则要尝试重新发送请求。

this.CancellationTokenSource.Cancel();

重新发送请求这个需求就体现出this.CancellationTokenSource这个的作用了。不知道大家还记不记得,在我们学习Session的时候。每次我们Call一个Rpc请求的时候,RPCId是唯一的(递增)。我们重新发送请求,已经是一个新的请求了,两者的RPCId已经不同了。

而且我们会根据RPCId来注册一个收到结果后的回调方法。这个方法是在发送的时候就注册了,但是如果我们请求失败了,我们并不希望它去调用这个回调。所以,我们调用Seesion.Call的时候传入一个CancellationTokenSource,并且注册了任务取消的回调。也就是移除我们之前注册的收到结果后的回调方法。

Seesion.Call
public Task<IResponse> Call(IRequest request, CancellationToken cancellationToken)
{
    uint rpcId = ++RpcId;
    ...
    this.requestCallback[rpcId] = (packetInfo) =>{...}
    ...
    cancellationToken.Register(()=>this.requestCallback.Remove(rpcId));
    ...
}

之后我对作者的代码进行事无巨细的注释吧。

IResponse response = await task.Run();
// 如果没找到Actor,发送窗口减少为1,重试
if (response.Error == ErrorCode.ERR_NotFoundActor)
{
    //移除之前注册的收到回复的回调。
    this.CancellationTokenSource.Cancel();
    //发送窗口减少为1
    this.WindowSize = 1;
    //标记失败次数
    ++this.failTimes;
    //讲等待队列里的任务全部压入RunningTasks队列,为后面做准备。
    while (this.WaitingTasks.Count > 0)
    {
        ActorTask actorTask = this.WaitingTasks.Dequeue();
        this.RunningTasks.Enqueue(actorTask);
    }
    //这是一个交换数据的方法,因为我们的等待队列和执行队列的数据结构是Quene
    //先进先出,所以这样对调不会对顺序造成改变。
    ObjectHelper.Swap(ref this.RunningTasks, ref this.WaitingTasks);

    // 失败3次则清空actor发送队列,返回失败
    if (this.failTimes > 3)
    {
        while (this.WaitingTasks.Count > 0)
        {
            ActorTask actorTask = this.WaitingTasks.Dequeue();
            actorTask.RunFail(response.Error);
        }

        // 失败直接删除actorproxy
        Game.Scene.GetComponent<ActorProxyComponent>().Remove(this.Id);
        return;
    }
    // 等待一会再发送
    await Game.Scene.GetComponent<TimerComponent>().WaitAsync(this.failTimes * 500);
    //重新获取定位信息
    int appId = await Game.Scene.GetComponent<LocationProxyComponent>().Get(this.Id);
    this.Address = Game.Scene.GetComponent<StartConfigComponent>().Get(appId).GetComponent<InnerConfig>().IPEndPoint;
    this.CancellationTokenSource = new CancellationTokenSource();
    //重新发送
    this.AllowGet();
    return;
}   

除此之外,我们看到还有两个方法

public void Send(IMessage message)
{
    ActorTask task = new ActorTask();
    task.message = (MessageObject)message;
    task.proxy = this;
    this.Add(task);
}

public Task<IResponse> Call(IRequest request)
{
    ActorTask task = new ActorTask();
    task.message = (MessageObject)request;
    task.proxy = this;
    task.Tcs = new TaskCompletionSource<IResponse>();
    this.Add(task);
    return task.Tcs.Task;
}

这两个方法并不是立马发送消息或者立马发送请求,而是把这些动作以待办任务的方式存入等待队列。

ET-MVC框架

  • 2015年05月28日 09:05
  • 2.51MB
  • 下载

通过LandlordsCore 学习ET框架

ET框架学习笔记 请大家关注一下我的微博 @NormanLin_BadPixel坏像素 LandlordsCore LandlordsCore是ET交流群里的一位大佬用ET框架写的一个联机斗...
  • Norman_Lin
  • Norman_Lin
  • 2018-03-26 23:36:13
  • 116

ET框架-服务端-Program学习笔记

Program学习笔记 请大家关注我的微博:@NormanLin_BadPixel坏像素 在写服务端之前,我是先看的客户端代码。而ET框架,服务端和客户端的代码很多都是共用的,这也是ET方便的...
  • Norman_Lin
  • Norman_Lin
  • 2018-04-18 16:21:15
  • 22

unity开源框架ET 实战篇 之 框架demo介绍(一)

ET框架是什么我就不罗嗦了:请查看:https://github.com/egametang/Egametang 我自己介绍一下:https://gitee.com/beyonehu/manual_d...
  • beyonehu
  • beyonehu
  • 2017-11-15 11:46:28
  • 1077

ET框架---UnitComponent学习笔记

UnitComponent学习笔记 请大家关注我的微博:@NormanLin_BadPixel坏像素 跟管理Player的PlayerComponent很像,不过这里管理的是Unit。“装置”...
  • Norman_Lin
  • Norman_Lin
  • 2018-04-04 20:12:20
  • 18

ET框架---OptionComponent学习笔记

OptionComponent 请大家关注我的微博:@NormanLin_BadPixel坏像素 关于这个组件,大家位移需要学的就是,CommandLine的作用。 C#开发的控制台程...
  • Norman_Lin
  • Norman_Lin
  • 2018-04-11 12:03:18
  • 14

ET框架---BenchmarkComponent学习笔记

BenchmarkComponent 请大家关注我的微博:@NormanLin_BadPixel坏像素 这就是一个测试用的,用来Ping远端地址的。而且一Ping就发送1,0000,0000次...
  • Norman_Lin
  • Norman_Lin
  • 2018-04-16 20:57:52
  • 1

ET框架---MatcherComponent学习笔记

MatcherComponent 请大家关注我的微博:@NormanLin_BadPixel坏像素 这是管理匹配对象的组件。 private readonly Dictionary&am...
  • Norman_Lin
  • Norman_Lin
  • 2018-04-16 20:59:44
  • 5

ET框架---ConfigComponent学习笔记

ConfigComponent学习笔记 请大家关注我的微博:@NormanLin_BadPixel坏像素 这是管理配置信息的组件,我们来看一下配置信息具体是什么。 private Dic...
  • Norman_Lin
  • Norman_Lin
  • 2018-04-07 23:12:30
  • 12

ET框架--AllotMapComponent学习笔记

AllotMapComponent 请大家关注我的微博:@NormanLin_BadPixel坏像素 /// &amp;lt;summary&amp;gt; /// 分配房间服务器组件,逻...
  • Norman_Lin
  • Norman_Lin
  • 2018-04-16 21:00:35
  • 4
收藏助手
不良信息举报
您举报文章:ET框架---ActorProxyComponent学习笔记(ActorProxy)
举报原因:
原因补充:

(最多只允许输入30个字)