二、 客户端订阅服务端事件
嘿嘿,吃甘蔗要先吃甜的一段,做事情我也喜欢先做容易的。现在,好日子过去了,该吃点苦头了。我们先回忆一下刚才的实现方法,再来思考怎么实现客户端订阅服务端事件?
在前一节,事件被放到远程对象中,客户端激活对象后,就可以发送消息了。而在服务端,只需要订阅该事件就可以。现在思路应该反过来,由客户端订阅事件,服务端发送消息。就这么简单吗?先不要高兴得太早。我们想一想,发送消息的任务是谁来完成的?是远程对象。而远程对象是什么时候创建的呢?我们仔细思考Remoting的几种激活方式,不管是服务端激活,还是客户端激活,他们的工作原理都是:客户端决定了服务器创建远程对象实例的时机,例如调用了远程对象的方法。而服务端所作的工作则是注册该远程对象。
回忆这三种激活方式在服务端的代码:
SingleCall激活方式:
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(BroadCastObj),"BroadCastMessage.soap",
WellKnownObjectMode.Singlecall);
SingleTon激活方式:
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(BroadCastObj),"BroadCastMessage.soap",
WellKnownObjectMode.Singleton);
客户端激活方式:
RemotingConfiguration.ApplicationName = “BroadCastMessage.soap”
RemotingConfiguration.RegisterActivatedServiceType(typeof(BroadCastObj));
请注意Register这个词语,它表达的含义就是注册。也就是说,在服务端并没有显示的创建远程对象实例。没有该实例,又如何广播消息呢?
或许有人会想,在注册远程对象之后,显式实例该对象不就可以了吗?也就是说,在注册后加上这一段代码:
BroadCastObj obj = new BroadCastObj();
然而,我们要明白一个事实:就是服务端和客户端是处于两个不同的应用程序域中。因此在Remoting中,客户端获得的远程对象实际是服务端注册对象的代理。如果我们在注册后,人工去创建一个实例,而非Remoting在激活后自动创建的对象,那么客户端获得的对象与服务端人工创建的实例是两个迥然不同的对象。客户端获得的代理对象并没有指向你刚才创建的obj实例。所以obj发送的消息,客户端根本无法捕捉。
那么,我们只有望洋兴叹,束手无策了吗?别着急,别忘了在服务器注册对象方法中,还有一种方法,即Marshal方法啊。还记得Marshal的实现方式吗?
BroadCastObj Obj = new BroadCastObj();
ObjRef objRef = RemotingServices.Marshal(Obj,"BroadCastMessage.soap");
这个方法与前不一样。前面的三种方式,远程对象是根据客户端调用的方式,来自动创建的。而Marshal方法呢?则显式地创建了远程对象实例,然后将其Marshal到通道中,形成ObjRef指向对象的代理。只要生命周期没有结束,这个对象就一直存在。而此时客户端获得的对象,正是创建的Obj实例的代理。
OK,这个问题解决了,我们来看看具体实现。
公共程序集和远程对象与前相似,就不再赘述,只附上代码:
公共程序集:
public delegate void BroadCastEventHandler(string info);
public interface IBroadCast
{
event BroadCastEventHandler BroadCastEvent;
void BroadCastingInfo(string info);
}
远程对象类:
public event BroadCastEventHandler BroadCastEvent;
#region IBroadCast 成员
//[OneWay]
public void BroadCastingInfo(string info)
{
if (BroadCastEvent != null)
{
BroadCastEvent(info);
}
}
#endregion
public override object InitializeLifetimeService()
{
return null;
}
下面,该实现服务端了。在实现之前,我还想罗嗦几句。在第一节中,我们实现了服务端订阅客户端事件。由于订阅事件是在服务端发生的,因此事件本身并未被传送。被序列化的仅仅是传递的消息,即Fax而已。现在,方向发生了改变,传送消息的是服务端,客户端订阅了事件。但这个事件是放在远程对象中的,因此事件必须被序列化。而在.net Framework1.1中,微软对序列化的安全级别进行了限制。有关委托和事件的序列化、反序列化默认是禁止的,所以我们应该将TypeFilterLevel的属性值设置为Full枚举值。因此在服务端注册通道的方式就发生了改变:
private void StartServer()
{
BinaryServerFormatterSinkProvider servERProvider = new
BinaryServerFormatterSinkProvider();
BinaryClientFormatterSinkProvider clientProvider = new
BinaryClientFormatterSinkProvider();
servERProvider.TypeFilterLevel = TypeFilterLevel.Full;
IDictionary props = new Hashtable();
props["port"] = 8080;
HttpChannel channel = new HttpChannel(props,clientProvider,serverProvider);
ChannelServices.RegisterChannel(channel);
Obj = new BroadCastObj();
ObjRef objRef = RemotingServices.Marshal(Obj,"BroadCastMessage.soap");
}
注意语句serverProvider.TypeFilterLevel = TypeFilterLevel.Full;此语句即设置序列化安全级别的。要使用TypeFilterLevel属性,必须申明命名空间:
using System.Runtime.Serialization.Formatters;
而后面两条语句就是注册远程对象。由于在我的广播程序中,发送广播消息是放在另一个窗口中,因此我将该远程对象声明为公共静态对象:
public static BroadCastObj Obj = null;
然后在调用窗口事件中加入:
private void ServerForm_Load(object sender, System.EventArgs e)
{
StartServer();
lbMonitor.Items.Add("Server started!");
}
来看看界面,首先启动服务端主窗口:

嘿嘿,吃甘蔗要先吃甜的一段,做事情我也喜欢先做容易的。现在,好日子过去了,该吃点苦头了。我们先回忆一下刚才的实现方法,再来思考怎么实现客户端订阅服务端事件?
在前一节,事件被放到远程对象中,客户端激活对象后,就可以发送消息了。而在服务端,只需要订阅该事件就可以。现在思路应该反过来,由客户端订阅事件,服务端发送消息。就这么简单吗?先不要高兴得太早。我们想一想,发送消息的任务是谁来完成的?是远程对象。而远程对象是什么时候创建的呢?我们仔细思考Remoting的几种激活方式,不管是服务端激活,还是客户端激活,他们的工作原理都是:客户端决定了服务器创建远程对象实例的时机,例如调用了远程对象的方法。而服务端所作的工作则是注册该远程对象。
回忆这三种激活方式在服务端的代码:
SingleCall激活方式:
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(BroadCastObj),"BroadCastMessage.soap",
WellKnownObjectMode.Singlecall);
SingleTon激活方式:
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(BroadCastObj),"BroadCastMessage.soap",
WellKnownObjectMode.Singleton);
客户端激活方式:
RemotingConfiguration.ApplicationName = “BroadCastMessage.soap”
RemotingConfiguration.RegisterActivatedServiceType(typeof(BroadCastObj));
请注意Register这个词语,它表达的含义就是注册。也就是说,在服务端并没有显示的创建远程对象实例。没有该实例,又如何广播消息呢?
或许有人会想,在注册远程对象之后,显式实例该对象不就可以了吗?也就是说,在注册后加上这一段代码:
BroadCastObj obj = new BroadCastObj();
然而,我们要明白一个事实:就是服务端和客户端是处于两个不同的应用程序域中。因此在Remoting中,客户端获得的远程对象实际是服务端注册对象的代理。如果我们在注册后,人工去创建一个实例,而非Remoting在激活后自动创建的对象,那么客户端获得的对象与服务端人工创建的实例是两个迥然不同的对象。客户端获得的代理对象并没有指向你刚才创建的obj实例。所以obj发送的消息,客户端根本无法捕捉。
那么,我们只有望洋兴叹,束手无策了吗?别着急,别忘了在服务器注册对象方法中,还有一种方法,即Marshal方法啊。还记得Marshal的实现方式吗?
BroadCastObj Obj = new BroadCastObj();
ObjRef objRef = RemotingServices.Marshal(Obj,"BroadCastMessage.soap");
这个方法与前不一样。前面的三种方式,远程对象是根据客户端调用的方式,来自动创建的。而Marshal方法呢?则显式地创建了远程对象实例,然后将其Marshal到通道中,形成ObjRef指向对象的代理。只要生命周期没有结束,这个对象就一直存在。而此时客户端获得的对象,正是创建的Obj实例的代理。
OK,这个问题解决了,我们来看看具体实现。
公共程序集和远程对象与前相似,就不再赘述,只附上代码:
公共程序集:
public delegate void BroadCastEventHandler(string info);
public interface IBroadCast
{
event BroadCastEventHandler BroadCastEvent;
void BroadCastingInfo(string info);
}
远程对象类:
public event BroadCastEventHandler BroadCastEvent;
#region IBroadCast 成员
//[OneWay]
public void BroadCastingInfo(string info)
{
if (BroadCastEvent != null)
{
BroadCastEvent(info);
}
}
#endregion
public override object InitializeLifetimeService()
{
return null;
}
下面,该实现服务端了。在实现之前,我还想罗嗦几句。在第一节中,我们实现了服务端订阅客户端事件。由于订阅事件是在服务端发生的,因此事件本身并未被传送。被序列化的仅仅是传递的消息,即Fax而已。现在,方向发生了改变,传送消息的是服务端,客户端订阅了事件。但这个事件是放在远程对象中的,因此事件必须被序列化。而在.net Framework1.1中,微软对序列化的安全级别进行了限制。有关委托和事件的序列化、反序列化默认是禁止的,所以我们应该将TypeFilterLevel的属性值设置为Full枚举值。因此在服务端注册通道的方式就发生了改变:
private void StartServer()
{
BinaryServerFormatterSinkProvider servERProvider = new
BinaryServerFormatterSinkProvider();
BinaryClientFormatterSinkProvider clientProvider = new
BinaryClientFormatterSinkProvider();
servERProvider.TypeFilterLevel = TypeFilterLevel.Full;
IDictionary props = new Hashtable();
props["port"] = 8080;
HttpChannel channel = new HttpChannel(props,clientProvider,serverProvider);
ChannelServices.RegisterChannel(channel);
Obj = new BroadCastObj();
ObjRef objRef = RemotingServices.Marshal(Obj,"BroadCastMessage.soap");
}
注意语句serverProvider.TypeFilterLevel = TypeFilterLevel.Full;此语句即设置序列化安全级别的。要使用TypeFilterLevel属性,必须申明命名空间:
using System.Runtime.Serialization.Formatters;
而后面两条语句就是注册远程对象。由于在我的广播程序中,发送广播消息是放在另一个窗口中,因此我将该远程对象声明为公共静态对象:
public static BroadCastObj Obj = null;
然后在调用窗口事件中加入:
private void ServerForm_Load(object sender, System.EventArgs e)
{
StartServer();
lbMonitor.Items.Add("Server started!");
}
来看看界面,首先启动服务端主窗口:
