这几天看了不少Remoting文章。明白了不少技术细节,但困惑也不少。简单说来,Remoting是一个分布式处理服务。服务器端首先创建通道(Channel),并自动开启监听通道。根据客户端发出的请求,传递远程对象。
因此,编写Remoting程序,主要分为三部分:
1、被传递的远程对象;
2、服务器端监听程序;
3、客户端请求和处理对象程序;
一、被传递的远程对象
在Remoting中,被传递的远程对象类是有诸多限制的。首先,我们必须清楚,这里所谓的传递是以引用的方式,因此所传递的远程对象类必须继承MarshalByRefObject。
MarshalByRefObject
是那些通过使用代理交换消息来跨越应用程序域边界进行通信的对象的基类。不是从
MarshalByRefObject
继承的对象会以隐式方式按值封送。当远程应用程序引用一个按值封送的对象时,将跨越远程处理边界传递该对象的副本。因为您希望使用代理方法而不是副本方法进行通信,因此需要继承
MarshallByRefObject
。(MSDN)
{
public PersonGetPersonInfo( string name, string sex, int age)
{
Personperson = new Person();
person.Name = name;
person.Sex = sex;
person.Age = age;
return person;
}
}
这个类只实现了最简单的方法,就是设置一个人的基本信息,并返回一个Person类对象。值得注意的是,这里返回的Person类。由于是以引用和远程调用的方式。这里所传递的Person则是以传值的方式来完成。因此必须涉及到一个序列化的问题。
所以,Remoting要求对象类还要调用或传递某个对象,例如类,或者结构,则该类或结构则必须实现串行化Attribute。[Serializable]。
public class Person
{
public Person()
{
}
private string name;
private string sex;
private int age;
public string Name
{
get { return name;}
set {name = value;}
}
public string Sex
{
get { return sex;}
set {sex = value;}
}
public int Age
{
get { return age;}
set {age = value;}
}
}
这个服务器对象,以类库的方式编译成Dll,这个工作就算完成了。
那么这个对象是怎么实现被客户端和服务器端调用的呢?这就是下面我们必须要做的工作:将编译后的DLL分别添加到服务器端和客户端程序的引用中。也就是说,这个服务器对象Dll要拷贝两份,一份放在服务器端,一份放在客户端。为什么要这样?看了后面的代码就知道原因了。
如此一来,会有个问题存在。那就是代码的安全性。如果客户端必须要保持这个对象的Dll,则该对象的实现方式对于客户而言就近乎透明了。另外,这样对于部署也不好。两份同样的dll,如果传递的对象大,也会影响性能的。对于这个问题,我们可以使用接口来解决。显然,服务器端提供接口和具体类的实现,而客户端则只需要接口就可以了(不过,对于客户端激活模式则必须有实现接口的类)。
{
PersonGetPersonInfo( string name, string sex, int age);
}
public class ServerObject:MarshalByRefObject,IServerObject
要注意的是:1、两边生成该对象程序集的名字必须相同,严格地说,是命名空间的名字必须相同。
2、这种方式根据激活方式的不同,实现也不同。如果是服务器端激活(SingleTon和SingCall),那很简单。如上所述的方法即可;
如果是客户端激活,最好是利用抽象工厂,提供创建实例的方法。下面的类图表描述了总体解决方案(MSDN)。
图 1: 混合法的结构
这样就必须在代码中还要加上抽象工厂的接口及实现:
{
IServerObjectCreateInstance();
}
public class ServerObject:MarshalByRefObject,IServerObject,IServerObjFactory
{
public IServerObjectCreateInstance()
{
return new ServerObject();
}
}
但是由于客户端激活的方式,它必须调用类的构造函数来创建实例,因此,在客户端只实现接口似乎是不可能的。
说明:关于对象类继承MarshalByRefObject,我作过测试,使可以间接地继承的。也就是我们可以先通过实现基类来继承它。然后实际所传递的对象在从基类中派生。
最后的代码应该是这样(服务器端,如果是客户端,只需要接口即可。这里我加了抽象工厂,因此该对象应该是以客户端激活模式。如果是服务器端激活模式,应该把抽象工厂接口和实现方法去掉)
namespace ServerRemoteObject
{
[Serializable]
public class Person
{
public Person()
{
}
private string name;
private string sex;
private int age;
public string Name
{
get { return name;}
set {name = value;}
}
public string Sex
{
get { return sex;}
set {sex = value;}
}
public int Age
{
get { return age;}
set {age = value;}
}
}
public interface IServerObject
{
PersonGetPersonInfo( string name, string sex, int age);
}
public interface IServerObjFactory
{
IServerObjectCreateInstance();
}
public class ServerObject:MarshalByRefObject,IServerObject
{
public PersonGetPersonInfo( string name, string sex, int age)
{
Personperson = new Person();
person.Name = name;
person.Sex = sex;
person.Age = age;
return person;
}
}
public class ServerObjFactory:MarshalByRefObject,IServerObjFactory
{
public IServerObjectCreateInstance()
{
return new ServerObject();
}
}
}
要补充说明的是,这里传递的对象显然比Socket更多,它甚至可以传递DataSet对象。其实,我们可以将它理解为WebService。
二、服务器端监听程序
写到这里,我想先介绍一下远程对象的三种激活模式。激活模式分为两大类:服务器端激活和客户端激活。其中服务器端激活又分为:SingleTon和SingleCall两种。
1、服务器端激活,又叫做WellKnow方式。我不想从理论上来讲述,而只是按照自己的理解来解释。简单地说,SingleTon激活方式,是对所有用户都建立一个对象实例,不管这些用户是在同一客户端还是不同客户端。而SingleCall则是对客户端的每个用户都建立一个远程对象实例。至于对象实例的销毁则是由GC自动管理的。举例来说,如果远程对象的一个累加方法(i=0;++i)被多个客户端(例如两个)调用。如果是SingleTon方式,则第一个客户获得值为1,第二个客户获得值为2,因为他们获得的对象实例是同样的。而SingleCall方式,则两个客户获得的都是1。原因不言自明。
2、客户端激活。则是对每个客户端都建立一个实例。粗看起来和SingleCall相同。实际上是由区别的。首先,对象实例创建的时间不一样。客户端激活方式是客户一旦发出调用的请求,就实例化;而SingleCall则是要等到调用对象方法时在创建。其次,WellKnow方式对对象的管理是由GC管理的,而客户端则可以自定义生命周期,管理他的销毁。其三,WellKnow对象是无状态的,客户端激活对象则是有状态的。当然具体到使用上来说,实现的方式也就不一样了。
好了,现在我们就开始创建服务器端监听程序。
Remoting传递远程对象实质上来说还是通过Socket来传递,因此必须有一个传递对象的通道(Channel)。一个通道必须提供一个端口。在Remoting中,通道是由IChannel接口提供。它分别有两种类型:TcpChannel和HttpChannel。Tcp是以二进制的方式来传递,Http则是以Soap的方式来传递。两种通道各有优势,从性能上看,Tcp更好。但Http可以更好地通过防火墙。因此用户可以根据自己情况选择使用通道的方式。(本文使用TcpChannel,事实上两种可以混合使用,现创建TcpChannel,如果连接失败,在创建HttpChannel。)通道实现的类为同名类,命名空间则是在System.Runtime.Remoting.Channel下。通过通道可以传递对象,而且可以一次传递多个对象。对象的传递和选择激活方式,是通过RemotingConfiguratin的静态方法RegisterWellKnownServiceType()(针对服务器激活模式)和RegisterActivedServiceType()(针对客户端激活模式)来实现的。代码如下:
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
namespace ServerRemoting
{
/// <summary>
/// Class1的摘要说明。
/// </summary>
class Server
{
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void Main( string []args)
{
// 创建通道,使用8080端口;
TcpChannelchannel = new TcpChannel( 8080 );
// 注册通道;
ChannelServices.RegisterChannel(channel);
// 传递对象;
RemotingConfiguration.RegisterWellKnownServiceType(
typeof (ServerRemoteObject.ServerObject),
" ServiceMessage " ,WellKnownObjectMode.SingleCall);
Console.WriteLine( " Opentheserverlistener " );
Console.ReadLine();
}
}
}
代码很简单。我是用控制台来提供服务的。这里重点说一样RegisterWellKnownServiceType()方法的参数。第一个参数就是要传递对象的类型。第二个参数是自己定义的远程对象服务名,其实它是作为客户端的Uri的一部分。第三个参数自然是定义激活的方式。WellKnownObjectMode是一个枚举,有SingleCall和SingleTon两个。
如果是客户端激活模式,稍有不同:
RemotingConfiguration.RegisterActivatedServiceType(
typeof (ServerRemoteObject.ServerObject));
几点说明:
1、Dll的引用。要添加第一步所创建的远程对象Dll;添加System.Runtime.Remoting的引用。
2、关于一个通道传递几个对象。其实没什么复杂的,再接着用RegiseterWellKnownServiceType()方法就是了。只要这个对象符合我前面所讲的要求,同时添加了引用。当然客户端也要增加相应的代码。
3、关于多个通道的使用。在Remoting中,可以同时使用多个通道。但要求通道名必须不同。默认建立的TcpChannel名字为Tcp,HttpChannel名字为Http。如果再要建立一个TcpChannel,则必须自己定义一个名字。Channel本身提供ChannelName字段,但该字段是只读的。所以方法有点变化,要使用HashTable和IDictionary(要添加System.Collection命名空间):
channelProps[ " name " ] = " MyTCP " ;
channelProps[ " priority " ] = " 1 " ;
channelProps[ " Port " ] = " 8081 " ;
<
发表评论
评论