** Effective C#: 4.使用类厂(Class Factory)模式实现 **
基于接口的客户激活远程对象(下)
陈铭 Microsoft C#/.NET Asia MVP
难度:7/10 上
简便的分布式应用程序开发无疑是 .NET 平台最引人注目的部分。通过使用 .NET Remoting 技术,我们可以轻松的跨越运行环境 (Context) 、线程抑或进程的边界,甚至透过 Internet 访问远在他乡的另一台计算机上的某个对象。而且,这种远程访问是近乎透明的——在完成远程对象的一些初始化工作之后,对其方法的调用与普通本地对象几乎完全相同。
根据生命周期控制方法的不同, .NET Remoting 将远程对象分为 服务器激活对象 ** (SAO, Server Activated Object) ** 和 客户激活对象 ** (CAO, Client Activated Object) ** 两种。顾名思义,服务器掌握着 SAO 对象生杀予夺的大权——更具体地说,将由服务器控制实际生成的远程对象的数量以及每个客户请求究竟由哪个远程对象处理;对于 CAO 对象,服务器会根据客户的请求建独立的远程对象,每一个方法调用都会被指派到与这个客户相关联的远程对象上。 CAO 对象的生存周期则是由客户端通过定期更新它与服务器签订的远程对象的“租用协议”来控制的。
让我们先来看一下 SAO 的情形。以下是一个简单的 .NET Remoting SAO 对象应用的完整程序 :
//share.cs, Remote Object
namespace Effective.Csharp.Chapter4 {
//Must inherit from MarshalByRefObject
public class RemoteObject : System.MarshalByRefObject {
//a very simple method implementation
public String SayHello(String name) {
return "Hello, " + name;
}
}
}
//server.cs, Server side code
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
namespace Effective.Csharp.Chapter4 {
public class Server {
public static int Main() {
//Register the channel
TcpChannel chan = new TcpChannel(8085);
ChannelServices.RegisterChannel(chan);
//Register the remote object
RemotingConfiguration.RegisterWellKnownServiceType(
Type.GetType("Effective.Csharp.Chapter4.RemoteObject, share"),
"Hello.rem", WellKnownObjectMode.SingleCall);
//Hold the server, wait for client
System.Console.WriteLine("Hit
1<enter> to exit...");
2
3System.Console.ReadLine();
4
5return 0;
6
7}
8
9}
10
11}
12
13//client.cs, Client Side code
14
15using System;
16
17using System.Runtime.Remoting;
18
19using System.Runtime.Remoting.Channels;
20
21using System.Runtime.Remoting.Channels.Tcp;
22
23namespace Effective.Csharp.Chapter4 {
24
25public class Client
26
27{
28
29public static int Main()
30
31{
32
33TcpChannel chan = new TcpChannel();
34
35ChannelServices.RegisterChannel(chan);
36
37//Create the remote object
38
39RemoteObject obj =
40
41( RemoteObject )Activator.GetObject(
42
43typeof( RemoteObject ), "tcp://localhost:8085/Hello.rem");
44
45Console.WriteLine(obj.SayHello("World"));
46
47return 0;
48
49}
50
51}
52
53}
54
55整个应用实际上被分解成三个独立编译的部分: share.cs 包含了远程对象 RemoteObject 的定义, server.cs 是创建和维护 RemoteObject 的服务器端代码,而 client.cs 则是实际应用 RemoteObject 的客户。一个特别需要注意的问题是: client.cs 代码多次引用了 RemoteObject 类型 ( 恰恰相反, server.cs 没有直接引用 RemoteObject) 。在条款 1 中我们曾经提到如果使用了在某个类集中定义的类型,就必须在编译期间使用 /r 选项引用相关的类集。换言之,在编译 client.cs 的时候必须显式引用 share 类集,而编译之后的 client 应用程序在缺少了 share 类集的情况下根本无法正确运行:
56
57csc server.cs
58
59csc /t:library share.cs
60
61csc /r:share.dll client.cs
62
63但是,在真实的分布式应用当中,绝不可能把远程对象的实现代码和客户端程序一起发布——尤其是当这些对象用于实现一些至关重要的算法 ( 例如加密、用户验证等 ) 的时候!
64
65事实上客户端的编译和运行并不真的需要远程对象的实现——客户端只是负责将对象方法的调用转换成消息的形式,然后发送给服务器做进一步处理。之所以需要引用包含远程对象实现的类集,只是因为编译器需要验证这些方法调用代码的参数和返回值类型正确无误,而运行时需要利用这些信息生成将方法调用转换成消息的代理。也就是说,编译客户端程序真正需要的只是远程对象的类型元数据 (MetaData) ,而不是实现!
66
67由此想到的一个显而易见的解决方法是使用接口和抽象基类——它们恰好是用于将具体的类实现与类方法定义分离开来。下面给出了利用接口继承实现上述远程对象应用实例的程序代码:
68
69//share.cs, Remote Object Interface definition
70
71namespace Effective.Csharp.Chapter4 {
72
73public interface IRemoteObject {
74
75String SayHello(String name);
76
77}
78
79}
80
81//server.cs, Server side code
82
83using System.Runtime.Remoting;
84
85using System.Runtime.Remoting.Channels;
86
87using System.Runtime.Remoting.Channels.Tcp;
88
89namespace Effective.Csharp.Chapter4 {
90
91//Must inherit from MarshalByRefObject
92
93public class RemoteObject : System.MarshalByRefObject,
94
95IRemoteObject {
96
97//a very simple method implementation
98
99public String SayHello(String name) {
100
101return "Hello, " + name;
102
103}
104
105}
106
107public class Server {
108
109public static int Main() {
110
111//Register the channel
112
113TcpChannel chan = new TcpChannel(8085);
114
115ChannelServices.RegisterChannel(chan);
116
117//Register the remote object
118
119RemotingConfiguration.RegisterWellKnownServiceType(
120
121Type.GetType("Effective.Csharp.Chapter4.RemoteObject, server"),
122
123"Hello.rem", WellKnownObjectMode.SingleCall);
124
125//Hold the server, wait for client
126
127System.Console.WriteLine("Hit <enter> to exit...");
128
129System.Console.ReadLine();
130
131return 0;
132
133}
134
135}
136
137}
138
139//client.cs, Client Side code
140
141using System;
142
143using System.Runtime.Remoting;
144
145using System.Runtime.Remoting.Channels;
146
147using System.Runtime.Remoting.Channels.Tcp;
148
149namespace Effective.Csharp.Chapter4 {
150
151public class Client
152
153{
154
155public static int Main()
156
157{
158
159TcpChannel chan = new TcpChannel();
160
161ChannelServices.RegisterChannel(chan);
162
163//Create the remote object
164
165IRemoteObject obj =
166
167( IRemoteObject )Activator.GetObject(
168
169typeof( IRemoteObject ), "tcp://localhost:8085/Hello.rem");
170
171Console.WriteLine(obj.SayHello("World"));
172
173return 0;
174
175}
176
177}
178
179}
180
181整个应用仍然被分成三个部分,但这一次 share.cs 不再包含远程对象的实现,而仅仅是用于描述远程对象方法的一个接口。远程对象实际的实现被转移到了 server.cs 当中。这样,在 client.cs 和 server.cs 的编译过程中只需要引用 share.cs 中定义的接口:
182
183csc /t:library share.cs
184
185csc /r:share.dll client.cs
186
187csc /r:share.dll server.cs
188
189实际发布的客户端程序只需要包含 client.exe 和 share.dll ,不再包括远程对象方法实现的代码了。
190
191除了使用接口以外,使用 .NET 框架提供的 SoapSuds.exe 工具同样可以完成上述工作。 SoapSuds 可以从一个已有的类集中分离出类型定义的元数据信息,分离出的部分可以用于客户端代码的编译和发布:
192
193//Extract remote object metadata from share.dll
194
195SOAPSUDS -types: Effective.Csharp.Chapter4.RemoteObject,share
196
197-oa:RemoteObject.dll
198
199//compile the client using extracted dll
200
201csc /r:RemoteObject.dll client.cs
202
203与使用接口的解决方法相比,使用 SoapSuds 工具要简单的多,但是使用接口继承可以使你的远程对象具备多态特性——同样的客户端不需要任何改动就可以用于任何一个实现了特定接口的远程对象。而且,使用接口的解决方案要更清晰一些——两个同名的 RemoteObject 类多少会令程序员感到有些混淆。(未完待续)
204
205* * *
206
207* 本文系原创作品,未经作者本人许可请勿转载。</enter></enter>