关于 .NET Remoting:三种概念的比较与简化






3.84/5 (13投票s)
2005年4月6日
6分钟阅读

99346

1176
这是一篇关于 .NET Remoting 的文章,讨论并比较了通过 .NET Remoting 从客户端访问服务器端对象的三个方法:Singleton、SingleCall 和 Client Activation。
引言
在 .NET Remoting 中,Singleton、SingleCall 和 Client Activation 是三种不同的方法,可以通过 MBR (MarshalByReference) 在客户端访问服务器端对象。本文使用一个示例应用程序来比较和简化这三种概念。
我试图限制本文的篇幅,并专注于主要主题。以下是这三种概念的介绍:
- Singleton:所有客户端的所有请求都使用服务器端对象的同一个实例。
- SingleCall:每当收到一个新调用时,都会创建一个服务器端对象的新实例,并将引用传递给客户端。因此,对于到服务器的每次调用,客户端都会获得对象的新实例。
- Client Activation:在此模式下,客户端向服务器请求创建一个新实例,该实例可用于多次调用。客户端激活的实例不会被其他客户端使用。客户端激活对象可以在其特定客户端的方法调用之间存储状态信息。
请注意,您必须将要向客户端公开以获取引用的类从此类派生 MarshalByRefObject
。
单例
如前所述,服务器上的一个 Singleton 对象供所有客户端使用。
在服务器端
在“App.config”文件中提及以下设置:
<configuration>
<system.runtime.remoting>
<application name="MyRemoting">
<service>
<wellknown mode="Singleton" type="MyRemoteService.MyService,
MyRemoteService" objectUri="MyService.rem"/>
</service>
<channels>
<channel ref="http" port="9999" />
</channels>
</application>
</system.runtime.remoting>
</configuration>
在上面的代码中,type
包含要在服务器上公开的对象类型。objectUri
是分配给此对象的任何特定名称,客户端在提及服务器 URL 时将使用它。为要使用的通道指定了任何特定的 port
。
包含 System.Runtime.Remoting
并编写以下代码块以在服务器上配置这些设置:
RemotingConfiguration.Configure("MyRemoteServer.exe.config");
请注意,您需要在上述语句中将您的配置文件名替换“MyRemoteServer.exe.config”,即您的“AssemblyName.config”。
在客户端
在您的类中包含 System.Runtime.Remoting
并编写以下代码以创建 Singleton 服务器端对象的代理:
MyService mySingletonRem = (MyService) Activator.GetObject(typeof(MyService),
"https://:9999/MyRemoting/MyService.rem");
上述 URL 格式为:所用协议://服务器地址:端口号/应用程序名称/对象URI
SingleCall
每当收到一个新调用时,都会创建一个服务器端对象的新实例,并将引用传递给客户端。因此,对于到服务器的每次调用,客户端都会获得对象的新实例。
要创建 SingleCall 对象,所有设置都将与上面提到的 Singleton 对象相同,但在服务器的 App.Config 文件中指定的 mode
将是“SingleCall”,而不是“Singleton”。
Client Activation
在此模式下,客户端向服务器请求创建一个新实例,该实例可用于多次调用。客户端激活的实例不会被其他客户端使用。客户端激活对象可以在其特定客户端的方法调用之间存储状态信息。
在服务器端
在“App.config”文件中提及以下设置:
<configuration>
<system.runtime.remoting>
<application name="MyRemoting">
<service>
<activated type="MyRemoteService.MyService, MyRemoteService" />
</service>
<channels>
<channel ref="http" port="9999" />
</channels>
</application>
</system.runtime.remoting>
</configuration>
在客户端
包含 System.Runtime.Remoting
和 System.Runtime.Remoting.Activation
并编写以下代码块:
object[] activateAttribute =
{new UrlAttribute("https://:9999/MyRemoting")};
MyService myClientActivatedRem =
(MyService) Activator.CreateInstance(typeof(MyService),
null, activateAttribute);
关于示例应用程序
该应用程序创建了同一类的所有三种类型的对象。尽管在实际项目中,我们很少会遇到创建同一类的所有类型的对象的情况。我创建它们是为了便于理解和比较。
每个示例对象都维护一个计数器,用于记录它收到的客户端调用次数。通过调用 ServeAll()
方法,它会返回一个包含该计数器的消息。
每个客户端在启动时都会为所有三种类型的服务器端对象创建代理。客户端中有三个按钮,分别是“Singleton”、“Client Activated”和“SingleCall”。单击按钮会向相应的服务器端对象发送调用。这意味着,单击“Singleton”按钮时,客户端将调用“Singleton”对象的 ServeAll
方法。
运行示例应用程序
- 要运行演示,请将所有演示文件解压缩到一个文件夹。现在,您可以看到有两个文件夹,一个是 MyRemoteServer,另一个是 MyRemoteClient。
- 现在,通过在 MyRemoteServer 文件夹中执行文件“MyRemoteServer.exe”来启动服务器。
- 通过在 MyRemoteClient 文件夹中执行文件“MyRemoteClient.exe”来启动一个客户端。
- 通过执行文件“MyRemoteClient.exe”来启动另一个客户端。
- 现在,在两个客户端上依次单击“SingleCall”、“Singleton”和“Client Activated”按钮。
- 您会注意到以下结果:
- 单击“SingleCall”时,服务器端对象将始终显示“到目前为止,我已收到 1 个客户端请求。”。尽管如此,从客户端发送的 SingleCall 请求数量在每次单击时都会增加(这反映在窗口的下部)。这是因为在 SingleCall 对象的情况下,客户端的每次方法调用都由一个新的服务器端对象处理。
- 单击“Client Activated”时,服务器端对象显示“到目前为止,我已收到 X 个客户端请求。”,其中 X 将与从特定客户端发送的 ClientActivated 请求数量相同。这意味着,如果第一个客户端向 Client Activated 对象发送了三个请求,则该对象将显示“到目前为止,我已收到 3 个客户端请求。”,无论第二个客户端发送了多少请求,因为客户端不会与其他客户端共享其 ClientActivated 对象。
- 单击“Singleton”时,服务器端对象显示“到目前为止,我已收到 X 个客户端请求。”,其中 X 将是所有客户端发送的 Singleton 请求的总和。这意味着,如果第一个客户端向 Singleton 对象发送了三个请求,第二个客户端发送了 6 个请求,则该服务器端对象将显示“到目前为止,我已收到 9 个(即 3 + 6)客户端请求。”。这是因为这两个客户端共享同一个服务器端 Singleton 对象。
示例应用程序内部
以下是暴露给客户端的对象的服务类。请注意,它派生自 MarshalByRefObject
。
using System;
namespace MyRemoteService
{
/**********************************************************
The objects of this class will be made available to
the clients thru .NET remoting.
**********************************************************/
public class MyService: MarshalByRefObject
{
private int countRequests;
//This method returns a message which includes the number of
//client requests received by the current instance of the class.
public string ServeAll()
{
return "I have received "
+ ++countRequests + " Client requests so far.";
}
}
}
服务器上的 App.Config 文件如下所示。请注意,它为所有三种类型的对象都有条目。它们都属于同一个类,即 MyRemoteService.MyService
。
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.runtime.remoting>
<application name="MyRemoteService">
<service>
<wellknown
mode="Singleton"
type="MyRemoteService.MyService, MyRemoteService"
objectUri="MySingleton.rem"
/>
<wellknown
mode="SingleCall"
type="MyRemoteService.MyService, MyRemoteService"
objectUri="MySingleCall.rem"
/>
<activated
type="MyRemoteService.MyService, MyRemoteService"
/>
</service>
<channels>
<channel ref="http" port="8989"/>
</channels>
</application>
</system.runtime.remoting>
</configuration>
服务器的编写方式如下:
using System;
using System.Runtime.Remoting;
namespace MyRemoteServer
{
/**********************************************************
This class configures the channel to publish
the service thru .NET remoting
***********************************************************/
class RemoteServer
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
RemotingConfiguration.Configure("MyRemoteServer.exe.config");
Console.WriteLine("Listening for requests. Press Enter to exit...");
Console.ReadLine();
}
}
}
客户端定义了三个成员变量:
MyService myClientActivatedRem;
MyService mySingleCallRem;
MyService mySingletonRem;
客户端上的 App.Config 文件如下所示:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="MyRemoteSingletonServiceUrl"
value="https://:8989/MyRemoteService/MySingleton.rem"/>
<add key="MyRemoteSingleCallServiceUrl"
value="https://:8989/MyRemoteService/MySingleCall.rem"/>
<add key="MyRemoteClientActivatedServiceUrl"
value="https://:8989/MyRemoteService"/>
</appSettings>
</configuration>
代理在客户端加载时创建。
private void Client_Load(object sender, System.EventArgs e)
{
//Initialize Singleton object proxy
string mySingletonUrl = ConfigurationSettings.AppSettings
["MyRemoteSingletonServiceUrl"];
mySingletonRem = (MyService)
Activator.GetObject(typeof(MyService),
mySingletonUrl);
//Initialize SingleCall object proxy
string mySingleCallUrl = ConfigurationSettings.AppSettings
["MyRemoteSingleCallServiceUrl"];
mySingleCallRem = (MyService)
Activator.GetObject(typeof(MyService),
mySingleCallUrl);
//Initialize ClientActivated object proxy
string myClientActivatedUrl = ConfigurationSettings.AppSettings
["MyRemoteClientActivatedServiceUrl"];
object[] activateAttribute =
{new UrlAttribute(myClientActivatedUrl)};
myClientActivatedRem =
(MyService) Activator.CreateInstance(typeof(MyService), null,
activateAttribute);
}
三个按钮的单击事件分别附加了以下方法:
private void btnSendRequest_Click(object sender, System.EventArgs e)
{
//Send a request to Singleton object
//Increment and Refresh the number of requests
//to Singleton object
lblSingleton.Text = ((int) ++requestSingletonCount).ToString();
//Display the Response received from the server
//from Singleton object
txtSingleton.Text = mySingletonRem.ServeAll();
}
private void btnSendSingleCallRequests_Click(object sender, System.EventArgs e)
{
//Send a request to SingleCall object
//Increment and Refresh the number of requests
//to SingleCall object
lblSingleCall.Text = ((int) ++requestSingleCallCount).ToString();
//Display the Response received from the server
//from SingleCall object
txtSingleCall.Text = mySingleCallRem.ServeAll();
}
private void btnClientActivated_Click(object sender, System.EventArgs e)
{
//Send a request to ClientActivated object
//Increment and Refresh the number of requests
//to ClientActivated object
lblClientActivated.Text = ((int) ++requestClientActivatedCount).ToString();
//Display the Response received from the server
//from ClientActivated object
txtClientActivated.Text = myClientActivatedRem.ServeAll();
}
几点说明
- 本文仅涵盖 MBR (MarshalByReference)。本文未考虑 MarshalByObject。
- 为使文章更简洁,本文未考虑由于 Lease-Expiry 导致的服务器端对象回收。
- 有很多方法可以完成示例中所示的任务。本文仅提及了示例应用程序中使用的方法。