分布式系统的构建一般有两种模式,一是基于消息(如Tcp,http等),一是基于方法调用(如RPC、WebService、Remoting)。深入想一想,它们其实是一回事。如果你了解过.NET的Proxy,那么你会发现,方法调用和消息请求/回复实际上是可以相互转换的,.NET的Proxy的实现,就是在方法调用的堆栈帧和消息之间相互转换的过程。

ESFramework 4.0 进阶(06)-- 正规消息发送器一文中,我们已经知道了如何发送消息,下面我们来关注一下客户端与服务端进行交互时最常见的一种情况:客户端发送一个请求给服务端,服务端处理后,返回回复消息。比如像这样,服务端提供一个加法运算的服务,客户端请求加法服务的消息类型为1001,消息体中包含加法运算所需的两个参数;服务端计算完成后给出的回复消息的类型为1002,回复消息的消息体中包含加法运算的结果。

一.类比方法调用

   这个例子如果用C#方法来表达将非常直观明了:

    public static int Add(int para1, int para2)
    {
        ......
    }

加法方法接收两个参数,然后返回运算结果。

接下来,我们可以调用该方法:

   int result = Add(1, 2);

太easy了,不是吗。如果ESFramework提供一种机制,在客户端通过类似的方法调用直接返回应答消息,该是多么地方便啊。就像这样:

   IMessage CommitRequest(IMessage requestMsg);

就如最开始所描述的:客户端使用消息类型为1001、且消息体中包含两个参数的requestMsg来调用CommitRequest方法,该方法就返回消息类型为1002、且消息体中包含加法运算结果的应答Message。这就和Add方法所表达的含义一模一样了。

但是,要完全达到类似上面的效果,并不是非常的容易,困难在哪里了?

我们刚才使用了习以为常的方式来调用Add方法,实际上,我们进行的是同步调用。所谓同步调用,就是调用线程与方法执行的线程是同一个线程,调用方必须等到方法执行完毕后才会继续进行后面的动作,否则调用方将一直阻塞;还有一种方式叫异步调用,即调用线程与方法执行的线程是不同的线程,调用发生后,调用方不会阻塞以等待被调用的方法执行完毕,而是立刻执行后续的动作。比如,我们可以以异步的方式来调用Add方法:

public delegate int Cb(int a, int b);
 
   Cb cb = new Cb(Add);
   cb.BeginInvoke(1, 2, null, null);

  现在回过头来,再看我们的刚才示例中的请求消息与应答消息:客户端在调用线程中向服务器发送请求消息,而在接收线程中收到服务器的回复消息,通常,调用线程与接收线程肯定不是同一个线程,所以,从最原始来说,请求消息与回复消息位于不同的线程中。这种模式更像是方法异步调用。

异步调用的好处是当前调用线程不会阻塞,而同步调用的好处是编程模型非常直观简单。所以,最好两种模型都支持,使用者需要用哪个就用哪个。毫无疑问,在通信框架中,原始的模型就是异步调用模型,而ESFramework也增加了同步调用的机制,使得编程模型更加丰富。接下来,我们看ESFramework是如何实现同步调用的。

二.回复消息管理器IResponseManager

ESFramework要将异步消息模型转换为同步消息模型,需要做的一件最重要的事情,就是将回复消息与请求消息匹配起来。但是,由于请求消息与回复消息位于不同的线程,所以需要先找个地方将回复消息保存起来,然后等待调用线程来把回复消息取走。这个保存回复消息的地方就是IResponseManager。其定义如下:


    public interface IResponseManager : IDisposable
    {
        void Initialize() ;         /// <summary>
        /// 将需要被提取的回复消息压入到管理器中,等待调用线程的提取。
        /// </summary>        
        void PushResponse(IMessage response) ;         /// <summary>
        /// 在TimeoutSec时间内不断搜索符合条件的消息,找到后立即返回,如果超时则抛出Timeout异常。
        /// </summary>        
        IMessage PickupResponse(int messageType ,int messageID) ;         /// <summary>
        /// 如果一个回复在管理器中存在的时间超过ResponseTTL,则会被删除。单位为s。
        /// 如果ResponseTTL为0,则表示不进行生存期管理
        /// </summary>        
        int ResponseTTL{get ;set ;}         /// <summary>
        /// 如果在TimeoutSec内,PickupResponse仍然接收不到期望的回复,则抛出异常。取0时,表示不设置超时
        /// </summary>
        int TimeoutSec{get ;set ; }     
    }

(1)当我们发送完请求消息后,就可以立即调用PickupResponse来获取回复消息。

(2)当接收线程接收到一个消息后,如果确认其为回复消息(可以通过消息类型MessageType来判断),则调用PushResponse将其放入管理器中。

(3)当期望的消息出现在管理器中,PickupResponse会立即返回。

(4)如果超过TimeoutSec时间,PickupResponse还未找到期望的回复消息,则抛出Timeout异常。

(5)如果一个回复消息在管理器中存在的时间超过了ResponseTTL,则就会从管理器中移除。

  ESFramework规定,如果两个消息为匹配的一对“请求/回复”组合,则它们的MessageID必须是一样的。在这个规则的帮助下,我们就知道所期望的回复消息的特征:首先,如果我的请求消息的MessageID 为1920,则回复消息的MessageID也必须为1920;另外,回复消息的类型也作为标志之一,比如上面的示例中,我们的回复消息的类型就是1002。根据MessageID和MessageType来作为完整的匹配依据,就不会出现匹配错乱的情况了。

所以,服务端在实现消息处理器时,处理完请求给出的回复消息的MessageID要设为与请求消息的MessageID相同的值。这点很重要。

三.服务器代理IServerAgent

ESFramework提供了ESFramework.Passive.IServerAgent来支持消息同步调用。其接口定义如下:


    public interface IServerAgent
    {
        /// <summary>
        /// 向服务器提交请求消息,并返回回复消息。如果超时仍然没有回复,则抛出Timeout异常。        
        /// </summary>
        IMessage CommitRequest(IMessage requestMsg ,DataPriority dataPriority , bool checkRespond);             /// <summary>
        /// 查找MessageType为resMessageType的回复,如果超时仍然没有回复,则抛出超时异常。
        /// </summary>
        IMessage CommitRequest(IMessage requestMsg ,DataPriority dataPriority , int? resMessageType);         IRegularSender RegularSender { set; }
        IResponseManager ResponseManager { set; }       
    }

(1)第一个CommitRequest方法用于请求消息的类型与回复消息的类型相同时的同步调用,也就是说,请求消息的类型与回复消息的类型是可以相同的,比如,消息类型1000,如果是客户端发给服务器的,则一定是请求消息,如果是服务器发给客户端的一定是回复消息,所以此时可以公用一个消息类型。但是,并不是所有的情况下都可以公用,比如,“P2P类型的请求/回复”(即客户端提交的请求不是由服务器处理的,而是由另一个在线客户端处理的)就必须使用两个消息类型将请求和回复区分开来,否则,客户端就无法区分接收到的消息究竟是请求消息还是回复消息了。checkRespond参数表示是否需要搜索回复,如果不需要或根本就没有回复,则方法立即返回null。

(2)第二个CommitRequest方法用于请求消息的类型与回复消息的类型不同时的同步调用,明确指定期望的回复消息的类型。如果resMessageType为null,则表示不需要或根本就没有回复,方法将立即返回null。

(3)IServerAgent依赖于正规消息发送器IRegularSender,表示其要借助于IRegularSender来向服务器提交请求消息。

(4)IServerAgent依赖于刚刚介绍的回复管理器IResponseManager ,表示其会从IResponseManager中提取回复消息。

到此为止,还剩下最后一个问题,那就是回复消息如何被放入回复管理器中,ESFramework可以自动完成这一工作吗?当然可以的。

四.客户端容器类型的消息处理器ContainerStylePassiveProcesser 

  ESFramework提供了ESFramework.Passive.ContainerStylePassiveProcesser类作为默认的客户端消息处理器,客户端使用它来处理接收到的所有消息。其类图如下:

(1)ContainerStylePassiveProcesser处理器是ContainerStyle,即它不仅实现了IMessageProcesser接口,而且通过ProcesserList属性可以挂接很多个IMessageProcesser实例,这些实例才是真正处理业务逻辑的处理器。

(2)ContainerStylePassiveProcesser可以通过PushKeyDispersiveKeyScope属性配置一组MessageType的集合来定义所有回复消息的类型,然后它就会把对应类型的消息放入回复管理器中,以支持消息同步调用机制。当然,我们也可以通过AddPushKey方法来添加回复消息的类型。

(3)ContainerStylePassiveProcesser支持接收消息的队列,通过AsynMessageQueueEnabled属性以控制是否开启异步消息处理模型(即,不在接收线程中处理消息,而是在一个单独的后台线程中处理所有消息)。注意,如果将AsynMessageQueueEnabled设为true,就没必要再将客户端引擎的HandleMessageAsynchronismly属性设为true了。

(4)ContainerStylePassiveProcesser还依赖于IMessageTransceiver,主要是因为,如果真正的业务处理器有返回消息,则ContainerStylePassiveProcesser会通过IMessageTransceiver将消息发送出去。下篇文章中,我们将会详细介绍IMessageTransceiver。

五.示范代码

下面我们就构造一个典型的支持消息同步调用的客户端骨架流程,最主要是要将ContainerStylePassiveProcesser处理器挂接到骨架上。


    IRegularSender regularSender = ......;
    IMessageTransceiver messageTransceiver = ......;     IMessageProcesser messageProcesser1 = ......;
    IMessageProcesser messageProcesser2 = ......;
    IMessageProcesser[] messageProcessers = new IMessageProcesser[] { messageProcesser1, messageProcesser2 };
    
    ResponseManager responseManager = new ResponseManager(5,30);//设定消息在管理器中的生存期为5秒,超时为30秒。
    responseManager.Initialize();
    ContainerStylePassiveProcesser containerStylePassiveProcesser = new ContainerStylePassiveProcesser(messageProcessers, responseManager);
    containerStylePassiveProcesser.AsynMessageQueueEnabled = true;
    containerStylePassiveProcesser.MessageTransceiver = messageTransceiver;
    containerStylePassiveProcesser.AddPushKey(1002); //添加回复消息类型1002
    containerStylePassiveProcesser.Initialize();
    IProcesserFactory processerFactory = new ProcesserFactory(containerStylePassiveProcesser);               
    IServerAgent serverAgent = new ServerAgent(responseManager, regularSender);     //IMessage response = serverAgent.CommitRequest(requestMessage,DataPriority.Common ,true);

   如此,便可以通过serverAgent进行消息同步调用了。 关于如何实例化一个IRegularSender,可以参见ESFramework 4.0 进阶(06)-- 正规消息发送器一文中的代码示例。而IMessageTransceiver实例的构造,我们将在下篇文章中介绍。

六.Rapid引擎对消息同步调用的简化

直接使用ESFramework.Passive.IServerAgent进行消息同步调用,我们还需要构造请求的IMessage并解析返回的回复消息,这个工作还可以简化,ESPlus提供的客户端Rapid引擎让我们可以直接提交数据请求和返回回复数据。大家一定还记得ESPlus.Application.CustomizeInfo.Passive.ICustomizeInfoOutter接口的CommitRequest方法,这个方法用于向服务器提交同步调用:


   /// <summary>
   /// 向服务器提交请求信息,并返回应答信息。(类似于方法的同步调用)
   /// </summary>      
   /// <param name="requestInfoType">自定义请求信息的类型</param>
   /// <param name="requestInfo">请求信息</param>
   /// <returns>服务器的应答信息</returns>
   byte[] CommitRequest(int requestInfoType, byte[] requestInfo);

ICustomizeInfoOutter接口还有一个CommitP2PRequest方法,用于向其它的在线用户提交同步调用(即请求由其它在线用户处理):


    /// <summary>
    /// 向在线目标用户提交请求信息,并返回应答信息。如果目标用户不在线,或超时没有应答则将抛出Timeout异常。
    /// </summary>      
    /// <param name="targetUserID">接收并处理请求消息的目标用户ID</param>
    /// <param name="informationType">自定义请求信息的类型</param>
    /// <param name="info">请求信息</param>
    /// <returns>应答信息</returns>
    byte[] CommitP2PRequest(string targetUserID, int informationType, byte[] info);

上述的两个同步调用都是由客户端发出的,而在服务端,我们还可以通过ICustomizeInfoController接口的QueryClient方法来向在线客户端提交同步调用:


    /// <summary>
    /// 询问在线客户端,并返回应答信息。如果目标用户不在线,或超时没有应答则将抛出Timeout异常。
    /// </summary>      
    /// <param name="userID">接收并处理服务器询问的目标用户ID</param>
    /// <param name="informationType">自定义请求信息的类型</param>
    /// <param name="info">请求信息</param>
    /// <returns>客户端给出的应答信息</returns>
    byte[] QueryClient(string userID, int informationType, byte[] info);

Rapid引擎内部已经为我们打理好了一切,我们只要调用方法得到结果就OK了。

消息同步调用-- ESFramework 4.0 进阶(07)的更多相关文章

  1. 正规消息发送器-- ESFramework 4.0 进阶(06)

    在ESFramework 4.0 进阶(04)-- 驱动力:通信引擎(下)一文末尾我们已经将通信引擎以及整个消息骨架流程组装起来了,只要通信引擎一接收到消息,框架就会按照规定的流程进行运转.到这里,自 ...

  2. 挂接P2P通道-- ESFramework 4.0 进阶(08)

    最新版本的ESFramework/ESPlus提供了基于TCP和UDP的P2P通道,而无论我们是使用基于TCP的P2P通道,还是使用基于UDP的P2P通道,ESPlus保证所有的P2P通信都是可靠的. ...

  3. 驱动力—— 通信引擎(上)—— ESFramework 4.0 进阶(03)

    在ESFramework 4.0 进阶(02)-- 核心:消息处理的骨架流程一文中我们详细介绍了ESFramework中消息处理的骨架流程,并且我们已经知道,ESFramework中的所有通信引擎使用 ...

  4. 好友与组--ESFramework 4.0 进阶(11)

    大部分分布式通信系统中,都会涉及到客户端之间相互通信.以及需要将客户端进行分组的功能,或者是类似这方面的需求.ESFramework对这一常见的任务内置了强大的支持,包括从客户端到服务端.一直到Pla ...

  5. 垂直分割群集模型与多通道引擎 -- ESFramework 4.0 进阶(10)

    在ESFramework 4.0 进阶(09)-- ESPlatform 支持的三种群集模型一文中,我们介绍了ESPlatform支持的三种群集模型 -- 垂直分割模型.水平分割模型.交叉模型.我们看 ...

  6. 在线用户管理--ESFramework 4.0 进阶(05)

    无论我们采用何种通信框架来构建我们的分布式系统,在服务端进行用户管理都是非常重要的一个环节.然而用户管理是否应该隶属于通信框架了?这个并不一定,通常来说,用户管理是与具体应用紧密相关的,应该是由应用解 ...

  7. 核心梳理——消息处理的骨架流程——ESFramework 4.0 进阶(02)

    在ESFramework 4.0 概述一文中,我们提到ESFramework.dll作为通信框架的核心,定义了消息处理的骨架流程,本文我们来详细剖析这个流程以及该骨架中所涉及的各个组件.ESFrame ...

  8. 循序渐进做项目系列(2):最简单的C/S程序——消息异步调用与消息同步调用

    上篇博客 循序渐进做项目系列(1):最简单的C/S程序——让服务器来做加法 实现了一个最简单的C/S程序,即让服务器来做加法.当时为了通俗易懂采用了消息异步调用的方式.今天我们要采用消息同步调用的方式 ...

  9. ESFramework 4.0 进阶(04)-- 驱动力:通信引擎(下)

    在ESFramework 4.0 进阶(03)-- 驱动力:通信引擎(上)一文中,我们对ESFramework提供的每一个通信引擎的接口都做了详细了说明,这篇文章我们将继续探讨这些接口的实现类 -- ...

随机推荐

  1. PHP定义静态方法的原则

    与实例逻辑无关 与类逻辑有关 静态类本质上跟纯函数没区别. 1.static方法是类中的一个成员方法,属于整个类,即使不用创建任何对象也可以直接调用! 2.静态方法效率上要比实例化高,静态方法的缺点是 ...

  2. 在server2012安装tfs遇到的问题:KB2919355

    参考资料:http://blog.csdn.net/wo_984633714/article/details/52869851 安装tfs2015的时候,提示需要安装KB2919355的更新.然后我就 ...

  3. Java: MissingResourceException, "Can't find bundle for base name myMessage, locale zh_CN"

    在java中,在多语言国际化时可以用 *.java 类来作为资源文件使用. 1. 首先定义类, 类必须继承ListResourceBundle 类所在路径为: src/I18N public clas ...

  4. jquery css hover

    <script type="text/javascript"> $(function () { $("#<%=btnSubmit.ClientID%&g ...

  5. awk学习笔记二:调用shell、文件执行(转)

    awk 'BEGIN {print "Hello"}' 不操作文件直接处理数据流 要调用shell则可以用管道命令 如,打印日期awk 'BEGIN {"date&quo ...

  6. MVC之联动学习

    一,数据库表设计 CREATE TABLE [dbo].[HY_Province]( [id] [INT] NOT NULL, [province] [NVARCHAR]() NOT NULL, CO ...

  7. ListBox 如何改变某行的字体颜色

    Option Explicit Private Type RECT Left As Long Top As Long Right As Long Bottom As Long End Type Pri ...

  8. python 之遍历目录树(可匹配输出特定后缀的文件)

    涉及到的模块有os, fnmatch:1.通过os模块中的方法获取dir.subdir.files,通过os.path.join可拼接成完整路径: 2.fnmatch主要通过fnmatch.fnmat ...

  9. Buffett saying

    1. 人生财富就像滚雪球,最重要的是发现很湿的雪和很长的坡. 2. 雪球想滚大,必须要有最坚实的核心:一生坚持的价值投资理念 价值投资一直是巴菲特投资理念的核心,他始终认为投资企业最重要的是要看准企业 ...

  10. SQLSERVER 的联接查询写法

    1.内连接 语法:[JOIN.INNER JOIN] 作用:两个表相连,加上ON匹配两个表的共同条件. 实例1: SELECT tb_o_i.* FROM tb_o_i INNER JOIN tb_o ...