ESFramework介绍之(9)-- 插件对(Addin Pair)调试“框架”

    使用ESFramework开发C/S(通常为4层、3层也没问题)应用,当需要增加一项新的业务时,我们需要做的仅仅是开发两个插件,一个是服务端的业务功能插件(FunAddin),一个是客户端插件(PassiveAddin),这两个插件合在一起称为Addin Pair。开发这两个插件,只需要关注于业务,而其它与业务无关的比如网络通信、加密、数据安全,都不用管。ESFramework很好的将这些关注点分离开来,使得写“业务”插件的程序员的工作变得非常单纯,在ESFramework介绍之(8)-- 客户端插件IPassiveAddin一文给出的一个开发服务端功能插件和客户端插件的示例中,大家应该已经体会到这点了。

     正如上文结尾处提到,当增加一项新的业务,调试包含了这个新业务实现的插件对(AddinPair)时非常麻烦,因为在ESFramework 4层架构中,服务端功能插件加载于功能服务器(FS)上,客户端插件加载在客户端应用程序上,中间的通信还经过了应用服务器(AS)。这样就要将AS、FS、Client都启动起来,才能对插件Pair进行调试,并且在这样调试的过程中,很多其它的因素会分散我们的注意力,比如多线程的影响、消息Hook的影响等等,这使得调试Addin Pair并不轻松,正是由于这个原因,促使我开发一个插件调试框架来简化这个麻烦的过程。

     我的想法是,在VS.NET调试模式中运行这个“插件调试框架”(一个winForm应用程序),加载要调试的Addin Pair,然后,启动“业务操作窗体”显示出来,后续的交互将在两个插件之间直接进行通信,而VS.NET提供了我们跟踪到加载的插件的源码中的能力,这样就可以调试Addin Pair了。“插件调试框架”应用程序的主界面如下:

 

    测试模式中的“异常(功能服务器掉线)”用于模拟找不到对应服务端功能插件的情况。 

     我们知道,客户插件(PassiveAddin)通过IServerAgent来发送请求获取回复,在客户端应用程序中,IServerAgent是对底层Tcp或Udp的封装。在“插件调试框架”我们提供一个IServerAgent的实现给要调试的PassiveAddin使用,ESFramework给出的实现是AddinBridge类,它实现了IServerAgent接口,并且把PassiveAddin提交的请求直接提交给已加载的业务功能插件处理。AddinBridge实现如下:

AddinBridge
 1 /// <summary>
 2     /// AddinBridge 用于将服务端插件和对应的客户端插件桥接起来,进行调试。
 3     /// zhuweisky
 4     /// </summary>
 5     public class AddinBridge :IServerAgent
 6     {
 7         public AddinBridge()
 8         {            
 9         }
10 
11         #region Property
12         #region FunAddinManagement
13         private IAddinManagement funAddinManagement = null ; 
14         public  IAddinManagement FunAddinManagement
15         {
16             set
17             {
18                 this.funAddinManagement = value ;
19             }
20         }
21 
22         
23         #endregion    
24 
25         #region TestMode
26         private TestMode testMode = TestMode.Normal ; 
27         public TestMode TestMode
28         {
29             set
30             {
31                 this.testMode = value ;
32             }
33         }
34         #endregion
35 
36         #endregion
37 
38         #region IServerAgent 成员
39         public NetMessage CommitRequest(Message requestMsg, DataPriority dataPriority, bool checkRespond)
40         {
41             if(this.testMode == ESFramework.Addins.TestMode.FSOffline)
42             {
43                 requestMsg.Header.MessageBodyLength = 0 ;
44                 requestMsg.Header.Result = ServiceResultType.ServiceIsNotExist ;
45                 return new NetMessage(requestMsg.Header ,null) ;
46             }
47 
48             foreach(IFunAddin funAddin in this.funAddinManagement.AddinList)
49             {
50                 if(funAddin.ServiceKey == requestMsg.Header.ServiceKey)
51                 {
52                     //交给功能插件处理
53                     return funAddin.DealRequestMessage(requestMsg.ToNetMessage()) ;
54                 }
55             }
56 
57             requestMsg.Header.MessageBodyLength = 0 ;
58             requestMsg.Header.Result = ServiceResultType.ServiceIsNotExist ;
59             return new NetMessage(requestMsg.Header ,null) ;
60         }
61 
62         public NetMessage CommitRequest(Message requestMsg, DataPriority dataPriority, int expectResServiceKey)
63         {
64             return this.CommitRequest(requestMsg ,dataPriority ,true) ;
65         }
66 
67         #endregion
68     }
69 
70     /// <summary>
71     /// 调试模式
72     /// </summary>
73     public enum TestMode
74     {
75         Normal ,FSOffline
76     }

    AddinBridge借助了IAddinManagement(插件管理器)来管理已加载的业务功能插件。
    
    有了AddinBridge的支持,“插件调试框架”的实现就非常容易了,我将上图主界面中的按钮点击事件源码列在下面:

        private AddinBridge serverAgent = new AddinBridge() ;
        private IPassiveAddin passiveAddin = null ;

按钮事件处理
 1         private void button_server_Click(object sender, System.EventArgs e)
 2         {
 3             this.textBox_server.Text = FileHelper.GetFileToOpen("请选择服务端插件" ,".dll") ;
 4             IAddinManagement addinManager = new AddinManagement() ;
 5             string msg = null ;
 6             bool succeed = addinManager.LoadNewAddin(this.textBox_server.Text ,out msg) ;
 7             if(!succeed || (addinManager.AddinList.Count == 0))
 8             {
 9                 MessageBox.Show(msg + "请重新选择插件!") ;
10                 return ;
11             }
12 
13             this.serverAgent.FunAddinManagement = addinManager ;
14         }
15 
16         private void button_passive_Click(object sender, System.EventArgs e)
17         {
18             this.textBox_passive.Text = FileHelper.GetFileToOpen("请选择客户端插件" ,".dll") ;
19             IAddinManagement addinManager = new AddinManagement() ;
20             string msg = null ;
21             bool succeed = addinManager.LoadNewAddin(this.textBox_passive.Text ,out msg) ;
22             if(!succeed || (addinManager.AddinList.Count == 0))
23             {
24                 MessageBox.Show(msg + "请重新选择插件!") ;
25                 return ;
26             }
27 
28             this.passiveAddin = (IPassiveAddin)addinManager.AddinList[0] ;
29 
30             this.button_bind.Enabled = true ;
31         }
32 
33         private void button_bind_Click(object sender, System.EventArgs e)
34         {
35             this.button_server.Enabled = false ;
36             this.button_passive.Enabled = false ;
37             this.button_start.Enabled = true ;
38             this.button_bind.Enabled = false ;
39         }
40 
41         private void button_start_Click(object sender, System.EventArgs e)
42         {
43             if(this.radioButton_normal.Checked)
44             {
45                 this.serverAgent.TestMode = TestMode.Normal ;
46             }
47             else
48             {
49                 this.serverAgent.TestMode = TestMode.FSOffline ;
50             }
51 
52             //通过反射创建客户业务操作窗体
53             Form addinForm = (Form)Activator.CreateInstance(this.passiveAddin.AddinFormType);
54             ((IPassiveAddinForm)addinForm).Initialize(this.serverAgent ,"000000");
55             addinForm.Show();
56         }
57 
58         private void button_reset_Click(object sender, System.EventArgs e)
59         {            
60             this.textBox_passive.Clear() ;
61             this.textBox_server.Clear() ;
62 
63             this.serverAgent.FunAddinManagement = null ;
64             this.passiveAddin = null ;
65             
66             this.button_server.Enabled = true ;
67             this.button_passive.Enabled = true ;
68             this.button_start.Enabled = false ;
69             this.button_bind.Enabled = false ;
70         }
71     

    

你可以在54行代码处添加断点,然后就可以跟入两个插件的源码中调试了。
     需要注意的是,为了VS.NET能顺利的进入插件的源码,在加载插件时,最好直接加载插件工程Debug目录下的插件dll。 

    感谢关注!

转到  :ESFramework 可复用的通信框架(序) 

时间: 2024-09-20 16:52:32

ESFramework介绍之(9)-- 插件对(Addin Pair)调试“框架”的相关文章

ESFramework介绍之(8)-- 客户端插件IPassiveAddin

    前文已经提到了,在IServerAgent的基础上,客户端也可以采用插件的结构形式,客户端插件需要实现IPassiveAddin接口.    我的想法是,当客户端主程序加载一个新的PassiveAddin时,可以在某个菜单的子Items上添加一项,当双击这个子菜单项时,则弹出该客户端插件提供的"业务操作窗体".这只是使用客户端插件的可行方式之一,你完全可以根据你的应用来决定使用形式.IPassiveAddin接口定义如下:  1     /// <summary> 

ESFramework介绍之(20)―― 插件自动升级

    当我们的服务平台搭建成功后,所需要做的主要事情就是开发服务端功能插件(IFunAddin)和客户端插件(IPassiveAddin),每个插件对(AddinPair)实现了一组相似或相近的需求/功能.     好了,我们已经开发了十多对插件对,然后分别XCopy到了各个服务器节点上,"整个系统"已经投入了运行.通过前面的介绍(回顾),相信大家对我们的"整个系统"有了个大致的映像.我们的IRAS服务器通常只存在于一个节点上,而我们的AS和对应的多个FS通常分布

ESFramework介绍之(13)-- 功能插件处理器工厂

    上文讲述的是AS中的基于连接池的消息处理器,现在我们把焦点转移到功能服务器FS上来,看看FS上消息分派的过程.当FS接收到到一个请求后,会从已加载的功能插件列表中选择一个合适的插件来处理这个消息,而每一个功能插件就相当于一个消息处理器.FS和AS的结构一致:    要注意的是,功能服务器FS上收到的所有消息都应该交给功能插件来处理,不存在其它的处理方式.这是使得FS"纯粹"的必须要求.上图已经很清楚的表示了功能插件处理器工厂的位置和作用.它借助插件管理器实现"工厂&q

ESFramework介绍之(29)―― 插件公共设施 AddinUtil

    (本文适用于 ESFramework V0.2+)    不知你是否还记得,前面我们讲过,ESFramework规定了插件有如下特点: (1)一个插件是一个独立的物理单元.它可以独立的提供一项完整的服务(功能),而不需要依赖于其它插件. (2)插件能自我描述 ―― 插件的所有对外的发布信息都由插件自己内部提供,而不依赖于外部文件或注册表. (3)插件能自我管理 ―― 插件如果需要配置信息,则插件自己能读取和修改配置信息,而不是框架来完成这些事情.(4)插件自我独立   ―― 一个插件不得

ESFramework介绍之(26)-- 支持复杂插件(InnerDealer 和 InnerDispatcher)

    (本文内容适合于 ESFramework V0.2+)    通常,最单纯的情况是一个插件对应某一特定类型的功能请求,但是,在有的应用中也会出现这样的情况,有多种类型的功能请求相互关联.并且可能交叉,如果是这样,对应每种类型的请求都开发一个插件可能会非常困难,因为这可能会牵涉到插件之间的相互引用/访问,这违背了插件的"自治"性.最好的办法还是将它们放在一个插件中,通过ServiceItemIndex(你一定还记得消息头定义中除了ServiceKey外还有个ServiceItem

ESFramework介绍之(14)-- AS与FS通信方案

    前面我们已经多次提到,每个AS都有一组FS为之服务(回顾),AS将接收到的功能请求通过Tcp连接池 或Remoting转发给某个FS处理.下面我们将深入讨论AS和FS之间的通信机制.     首先要解决第一个问题,AS如何知道每个为之服务的FS的地址?    最常见的一种解决方案是,AS处的配置文件中有一个FS地址列表,AS每次启动时,就读取这个列表,然后与列表中的每个FS建立Tcp连接池.这种方案很容易实现,但是有很多缺点.最主要的是当动态的添加/移除FS时,都需要修改AS配置文件中的

ESFramework介绍之(22)―― 服务器系统自动升级

    (本文名字取为"服务器系统自动升级",实际上适用于所有应用程序自动升级的情况.)    前文介绍了在服务器或客户端应用程序运行的过程中,插件如何自动升级.更新.基于前文相同的理由,AS.FS.IRAS也需要有自动升级的功能.     与插件在运行时动态更新不同,服务器系统无法在运行时动态更新,只有在服务器系统重新启动的时候,才是自动升级的切入点.(1)对于功能服务器FS,可以采用持续/逐个更新的方式,即依次重启每个功能服务器.这样可以避免功能服务被中断的情况发生.需要注意的是,

ESFramework介绍之(7)-- 服务器代理IServerAgent

    (本文原作于2006.03.15,第一次修正于2006.06.06,修正后适用于ESFramework V0.3+)     (本文是ESFramework对客户端开发的支持特性之一 ,如果要按顺序阅读,请转到ESFramework介绍(序))         分布式系统的构建一般有两种模式,一是基于消息(如Tcp,http等),一是基于方法调用(如RPC.WebService.Remoting).深入想一想,它们其实是一回事.如果你了解过.NET的Proxy,那么你会发现,方法调用和消

ESFramework介绍之(18)―― Tcp用户管理器组件

    当我们的应用中客户端与AS之间是通过Tcp进行通信的时候,通常,应用也要求管理所有在线的用户.这种管理至少包含以下几点:(1) 当用户上线时,记录上线时间(2) 当用户请求服务时,记录请求服务的时间.服务的类型.本次服务下载的数据量(3) 当用户下线时,记录下线时间.并把本次用户登录.请求服务过程中的所有信息持久化保存(如记录到数据库)     在ESFramework中,实现这种管理的是ITcpUserManager组件,通常,该组件由AS使用(因为在AS.FS.IRAS中只有AS是必