65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.84/5 (13投票s)

2005年4月6日

6分钟阅读

viewsIcon

99346

downloadIcon

1176

这是一篇关于 .NET Remoting 的文章,讨论并比较了通过 .NET Remoting 从客户端访问服务器端对象的三个方法:Singleton、SingleCall 和 Client Activation。

Sample Image - AboutRemoting.gif

引言

在 .NET Remoting 中,Singleton、SingleCall 和 Client Activation 是三种不同的方法,可以通过 MBR (MarshalByReference) 在客户端访问服务器端对象。本文使用一个示例应用程序来比较和简化这三种概念。

我试图限制本文的篇幅,并专注于主要主题。以下是这三种概念的介绍:

  1. Singleton:所有客户端的所有请求都使用服务器端对象的同一个实例。
  2. SingleCall:每当收到一个新调用时,都会创建一个服务器端对象的新实例,并将引用传递给客户端。因此,对于到服务器的每次调用,客户端都会获得对象的新实例。
  3. 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.RemotingSystem.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 方法。

运行示例应用程序

  1. 要运行演示,请将所有演示文件解压缩到一个文件夹。现在,您可以看到有两个文件夹,一个是 MyRemoteServer,另一个是 MyRemoteClient
  2. 现在,通过在 MyRemoteServer 文件夹中执行文件“MyRemoteServer.exe”来启动服务器。
  3. 通过在 MyRemoteClient 文件夹中执行文件“MyRemoteClient.exe”来启动一个客户端。
  4. 通过执行文件“MyRemoteClient.exe”来启动另一个客户端。
  5. 现在,在两个客户端上依次单击“SingleCall”、“Singleton”和“Client Activated”按钮。
  6. 您会注意到以下结果:
    • 单击“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 导致的服务器端对象回收。
  • 有很多方法可以完成示例中所示的任务。本文仅提及了示例应用程序中使用的方法。
© . All rights reserved.