远程对象的事件可以分成客户端触发-》服务器应答,服务端触发-》客户端应答和客户端触发-》客户端应答,第一种就很简单了,后面两种都需要有一个中间件。原因很简单,一般情况下,远程对象的程序集是不会放在客户端,不然就会失败远程对象的意义,同时也是为了考虑安全性。如果不能放在客户端,这时远程对象是无法获得客户端程序集。为了解决这个问题,通常会创建一个代理类(也有称为事件重现器),这个事件的代理类在服务器和客户端都会有,最终造成两端的事件消息传递都是通过这个代理类来完成的。
EventProxy类具有相同或者类似的RemoteObject事件,并且分别存在于客户端和服务器端(准确的说是存在于发布给客户端的远程对象程序集中,这个程序集中可以不存在远程对象具体实现类,只有远程对象的接口)。因为远程对象可以调用到EventProxy类,所以当触发事件后,由EventProxy类的方法来实现,不会有任何问题。同时,EventProxy类又可以被客户端调用,所以当EventProxy类捕获到远程对象触发的事件后,再触发被客户端订阅EventProxy类事件。这样,客户端通过间接的方式捕获远程对象捕获的事件。下面看一段代码。
Ø 事件参数
[Serializable]
public class CalculateEventArgs
{
private string message;
public string Message
{
get
{
return message;
}
set
{
message = value;
}
}
public CalculateEventArgs()
{
}
public CalculateEventArgs(string message)
{
this.message = message;
}
}
由于事件参数需要在网络间传输。因此,事件类必须支持可序列化。
Ø 远程对象
public delegate void CalculateStateHandler(object sender,CalculateEventArgs e);
public class Calculator : MarshalByRefObject
{
public event CalculateStateHandler CalculateStateChanged;
public void LongTimeCalculate()
{
OnCalculateStateChanged(new CalculateEventArgs("开始计算!"));
Thread.Sleep(2000);
OnCalculateStateChanged(new CalculateEventArgs("计算中!"));
Thread.Sleep(2000);
OnCalculateStateChanged(new CalculateEventArgs("计算完成!"));
}
public void OnCalculateStateChanged(CalculateEventArgs e)//这里有一些特别于本地事件处理的机制。
{
//防止客户端因非正常退出,造成服务器触发事件失败。若触发失败,则取消该客户端的事件订阅。
if (CalculateStateChanged != null)
{
foreach(Delegate d in CalculateStateChanged.GetInvocationList())
{
try
{
((CalculateStateHandler)d)(this, e);
}
catch
{
CalculateStateChanged-=(CalculateStateHandler)d;
}
}
}
}
}
Ø 事件代理类
public class CalculateSink : MarshalByRefObject
{
public event CalculateStateHandler CalculateStateChanged;
public CalculateSink()
{
}
public void OnCalculateStateChanged(object sender, CalculateEventArgs e)
{
if(CalculateStateChanged!=null)
{
CalculateStateChanged(sender,e);
}
}
由于EventProxy也是一个远程对象,只不过这个类很特殊,因为它也要远程对象调用,所以必须和远程对象一样继承于MarshalByRefObject。
Ø 客户端代码片断
RemotingConfiguration.Configure("ClientWindowsApplication.exe.config", true);
//获取远程对象
Calculator calculator = new Calculator();
//创建事件代理类,因为本地引用了相应的程序集,所以可以直接创建。
CalculateSink calculateSink = new CalculateSink();
//订阅事件代理类事件,并委托给本地方法处理。
calculateSink.CalculateStateChanged+=new CalculateStateHandler(calculateSink_CalculateStateChanged);
c//订问远程对象事件,并且委托给事件代理类处理。事件代理类捕获到事件后,会触发被本地订阅的事件,让本地方法处理。
alculator.CalculateStateChanged += new CalculateStateHandler(calculateSink.OnCalculateStateChanged);
calculator.LongTimeCalculate();
Ø 服务器端代码
和正常代码没有任何变化。
虽然.NET Remoting事件看上去有点复杂,其实和本地的事件调用和处理基本上都是一样的,关键在于如何设计.NET Remoing的事件,以及对事件异常的处理,这块是在设计和开发过程中难点。比如事件代码中,有一段因客户端程序非正常退出,造成远程对象事件调用列表中,仍然有该客户端的订阅。如果这时触发事件,就会引用服务器端的异常,这块需要特别小心处理。