技术开发 频道

.Net Remoting专题之事件处理

【IT168技术文档】

  前言:在Remoting中处理事件其实并不复杂,但其中有些技巧需要你去挖掘出来。正是这些技巧,仿佛森严的壁垒,让许多人望而生畏,或者是不知所谓,最后放弃了事件在Remoting的使用。关于这个主题,在网上也有很多讨论,相关的技术文章也不少,遗憾的是,很多文章概述的都不太全面。我在研究Remoting的时候,也对事件处理发生了兴趣。经过参考相关的书籍、文档,并经过反复的试验,深信自己能够把这个问题阐述清楚了。
 
  应用Remoting技术的分布式处理程序,通常包括三部分:远程对象、服务端、客户端。因此从事件的方向上看,就应该有三种形式:
 
  1、服务端订阅客户端事件
  2、客户端订阅服务端事件
  3、客户端订阅客户端事件
 
  服务端订阅客户端事件,即由客户端发送消息,服务端捕捉该消息,然后响应该事件,相当于下级向上级发传真。反过来,客户端订阅服务端事件,则是由服务端发送消息,此时,所有客户端均捕获该消息,激发事件,相当于是一个系统广播。而客户端订阅客户端事件呢?就类似于聊天了。由某个客户端发出消息,其他客户端捕获该消息,激发事件。可惜的是,我并没有找到私聊的解决办法。当客户端发出消息后,只要订阅了该事件的,都会获得该信息。
 
  然而不管是哪一种方式,究其实质,真正包含事件的还是远程对象。原理很简单,我们想一想,在Remoting中,客户端和服务端传递的内容是什么呢?毋庸置疑,是远程对象。因此,我们传递的事件消息,自然是被远程对象所包裹。这就像EMS快递,远程对象是运送信件的汽车,而事件消息就是汽车所装载的信件。至于事件传递的方向,只是发送者和订阅者的角色发生了改变而已。
 
  服务端订阅客户端事件
 
  服务端订阅客户端事件,相对比较简单。我们就以发传真为例。首先,我们必须具备传真机和要传真的文件,这就好比我们的远程对象。而且这个传真机上必须具备发送的操作按钮。这就好比是远程对象中的一个委托。当客户发送传真时,就需要在客户端上激活一个发送消息的方法,这就好比我们按了发送按钮。消息发送到服务端后,触发事件,这个事件正是服务端订阅的。服务端获得该事件消息后,再处理相关业务。这就好比接收传真的人员,当传真收到后,会听到接通的声音,此时选择接收后,该消息就被捕获了。
 
  客户端订阅服务端事件
 
  吃甘蔗要先吃甜的一段,做事情我也喜欢先做容易的。现在,好日子过去了,该吃点苦头了。我们先回忆一下刚才的实现方法,再来思考怎么实现客户端订阅服务端事件?
 
  在前一节,事件被放到远程对象中,客户端激活对象后,就可以发送消息了。而在服务端,只需要订阅该事件就可以。现在思路应该反过来,由客户端订阅事件,服务端发送消息。就这么简单吗?先不要高兴得太早。我们想一想,发送消息的任务是谁来完成的?是远程对象。而远程对象是什么时候创建的呢?我们仔细思考Remoting的几种激活方式,不管是服务端激活,还是客户端激活,他们的工作原理都是:客户端决定了服务器创建远程对象实例的时机,例如调用了远程对象的方法。而服务端所作的工作则是注册该远程对象。
 
  我们要明白一个事实:就是服务端和客户端是处于两个不同的应用程序域中。因此在Remoting中,客户端获得的远程对象实际是服务端注册对象的代理。如果我们在注册后,人工去创建一个实例,而非Remoting在激活后自动创建的对象,那么客户端获得的对象与服务端人工创建的实例是两个迥然不同的对象。客户端获得的代理对象并没有指向你刚才创建的obj实例。所以obj发送的消息,客户端根本无法捕捉。

  我在前面的事件处理中,使用的都是默认的EventArgs。如果要定义自己的EventArgs,就不相同了。因为该信息是传值序列化,因此必须加上[Serializable],且必须放到公共程序集中,部署到服务端和客户端。例如:

[Serializable] public class BroadcastEventArgs:EventArgs { private string msg = null; public BroadcastEventArgs(string message) { msg = message; } public string Message { get {return msg;} } }
  持续改进
 
  在发送事件消息的时候,事件的订阅者会触发事件,然后响应该事件。然而当事件的订阅者发生错误的时候呢?例如,发送事件消息的时候,才发现根本没有事件订阅者;或者事件的订阅者出现故障,如断电、或异常关机。此时,发送事件一方会因为找不到正确的事件订阅者,而发生异常。当我们分别打开服务端和客户端程序的时候,此时广播信息正常。然而,当我们关闭客户端后,由于该客户端没有取消订阅,此时异常发生。
 
  如果这个时候我们同时打开了多个客户端,那么其他客户端就会因为这一个客户端关闭造成的错误,而无法收到广播信息。那么让我们先做第一步改进:
 
  1、先考虑正常情况。在我的客户端,虽然提供了取消订阅的操作,但并没有考虑用户关闭客户端的情况。即,关闭客户端时,并未取消事件的订阅,所以我们应该在关闭客户端窗体中写入:
private void ClientForm_Closing(object sender, System.ComponentModel.CancelEventArgs e) { watch.BroadCastEvent -= new BroadCastEventHandler (wrapper.BroadCasting); }
  2、仅仅是这样还不够。如果客户端并没有正常关闭,而是因为突然断电而导致客户端关闭呢?此时,客户端还没有来得及取消事件订阅呢。在这种情况下,我们需要用到OneWayAttribute。
 
  前面说到,发送事件一方如果找不到正确的事件订阅者,会发生异常。也就是说,这个事件是unreachable的。幸运的是,OneWayAttribute恰好解决了这个问题。其实从该特性的命名OneWay,大约也能猜到其中的含义。当事件不可到达,无法发送时,正常情况下,会返回一个异常信息。如果加上OneWayAttribute,这个事件的发送就变成单向的了。假如此时发生异常,那么系统会自动抛掉该异常信息。由于没有异常信息的返回,发送信息方会认为发送信息成功了。程序会正常运行,错误的客户端被忽略,而正确的客户端仍然能够收到广播信息。
 
  因此,远程对象的代码就应该是这样:
public event BroadCastEventHandler BroadCastEvent; IBroadCast 成员#region IBroadCast 成员 //[OneWay] public void BroadCastingInfo(string info) { if (BroadCastEvent != null) { BroadCastEvent(info); } } #endregion public override object InitializeLifetimeService() { return null; }
  3、最后的改进
 
  使用OneWay固然可以解决上述的问题,但不够友好。因为对于广播消息的一方来说,象被蒙上了眼睛一样,对于客户端发生的事情懵然不知。这并不是一个好的idea。在Ingo Rammer的Advanced .NET Remoting一书中,Ingo Rammer先生提出了一个更好的办法,就是在发送信息一方时,检查了委托链。并在委托链的遍历中来捕获异常。当其中一个委托发生异常时,显示提示信息。然后继续遍历后面的委托,这样既保证了异常信息的提示,又保证了其他订阅者正常接收消息。因此,我对本例的远程对象进行了修改,注释掉[OneWay]。

0
相关文章