今天这一篇,我们介绍两个方面的内容:
1.如何使用配置文件来代替通道的设置
2.如何实现服务端向客户端广播消息
我们前面的两篇文章中注册通道的方式都是在代码中注册的。其实,.net remoting允许我们在配置文件中配置通道的相关信息。我们来看一下如何配置:
首先添加应用程序配置文件“App.config”,以客户端为例子,修改如下:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.runtime.remoting> <application> <client name = "MyClient"> <wellknown type="Common.Person,Person" url="tcp://127.0.0.1:8085/SayHello" /> </client> <channels> <channel ref="tcp" port="0"> </channel> </channels> </application> </system.runtime.remoting> </configuration>
大家看里面的配置字段就明白是干什么的了。另外需要在客户端的代码中将原来配置通道的代码删除,并更换为:
RemotingConfiguration.Configure("c.exe.config",true);
怎么样?看起来简单多了吧,而且我们想修改配置信息的时候也不需要重新编译程序了。服务端的配置和客户端类似,我这里就不再贴出来了。大家可以看我下面放出的源码。
在前面的两篇中,我们都是以客户端访问服务端为例子进行讲解的。那么服务端可不可以主动将信息推送到客户端呢?答案当然是可以的。切入点就是使用事件机制。设想一下这样一个场景,在服务端有一个事件,所有的客户端都会去注册这个事件。这样,当服务端执行这个事件的时候,所有注册了这个事件的客户端都会执行相应的操作。想像的不错,不知道真正实现起来如何呢?不妨来试试看!
因为这个事件服务端和客户端都会使用到,那么肯定我们要将其写在Common中,于是我们就在Person类中增加一个事件:
public class Person : MarshalByRefObject { public delegate void CHandle(string n); public event CHandle beginEvent; /// <summary> /// 执行事件 /// </summary> /// <param name="info"></param> public void BroadCastInfo(string info) { if (beginEvent!=null) { beginEvent( info); } } public Person() { Console.WriteLine("服务端被初始化"); } }
接着,我们在客户端中注册这个事件,代码如下:
class Program { static void Main(string[] args) { //从配置文件中注册通道信息 RemotingConfiguration.Configure("c.exe.config",true); Person obj1 = (Person)Activator.GetObject( typeof(Person), "tcp://localhost:8085/SayHello"); if (obj1 == null) { System.Console.WriteLine( "连接TCP服务器失败"); } obj1.beginEvent += delegate { Console.WriteLine("客户端执行事件"); }; Console.Read(); } }
接着我们在服务端调用这个事件看看到底会不会得到执行?代码如下:
class Program { static void Main(string[] args) { RemotingConfiguration.Configure("ConsoleApplication7.exe.config", true); var person = new Person(); var obj = RemotingServices.Marshal(person, "SayHello"); System.Console.WriteLine("服务器正在等待信息,按任意键退出!"); while (true) { string yes = System.Console.ReadLine(); if (yes == "yes") { //执行服务端事件 person.BroadCastInfo("hello"); } } } }
执行结果如下:
可以看到,并没有像我们想象的那样成功通知客户端。不过值得注意的是,我们看到错误信息是“调用的目标发生了异常”。这说明服务端是调用了这个事件的,不过调用的目标错误。那么为什么会错误呢?我们应该想到上面的那个obj1其实是在服务端执行事件的,而我们却将它的执行事件的代码放在了客户端,所以才会报出“调用的目标发生了异常”的错误。但是,每个客户端都不同,我们又不可能将处理的事件移到服务端执行,这样也不符合我们设计的初衷。现在的问题就是,服务端要执行事件的服务端代码,而客户端的事件代码也要放在客户端。在这样两难的情况下,有人就想到了使用一个中间层来解决之间的冲突。
具体是这样做的,在Common中间层里面增加一个和Person一样的Wapper中间类,这个类中也有和Person一样的事件。这个类既可以被服务端访问也可以被客户端访问。这样只要使用这个类充当中间层过渡一下就可以解决上面说的问题了。具体只要在客户端代码中做如下更改就行了:
class Program { static void Main(string[] args) { RemotingConfiguration.Configure("c.exe.config",true); Person obj1 = (Person)Activator.GetObject( typeof(Person), "tcp://localhost:8085/SayHello"); if (obj1 == null) { System.Console.WriteLine( "连接TCP服务器失败"); } //中间交换类 Wapper wapper = new Wapper(); //中间交换类的事件绑定本地方法 wapper.beginEvent += wapper_beginEvent; //服务端的事件执行他“认识的”方法,而这个方法 //又会执行客户端“认识”的方法 obj1.beginEvent += wapper.BroadCastInfo; Console.Read(); } static void wapper_beginEvent(string d) { Console.WriteLine("客户端执行时间" + d); } }
这样一来,服务端就可以成功接收到服务端发来的消息了。另外一点需要主要的是,因为.net为了安全考虑不允许远程事件,所以我们需要在配置文件中增加一些配置信息,使得服务端能够广播事件。具体的配置信息见配置文件。
最后运行的截图如下: