玄铁剑

成功的途径:抄,创造,研究,发明...
posts - 128, comments - 42, trackbacks - 0, articles - 174

.NET Remoting – Basic Maneuvers

Posted on 2007-01-03 11:07 玄铁剑 阅读(328) 评论(0)  编辑 收藏 引用 所属分类: Service

Contents

  1. Introduction
  2. Objectives.
  3. Prerequisites.
  4. Package.
  5. To Run The Sample.
  6. Configuration Files.
  7. Assembly References from client assembly.
  8. Code blocks in client source file CRemoteObjClient.cs
  9. Code blocks in client source file CRemoteObjServer.cs
  10. Walkthrough.
  11. References.

Introduction

.NET Remoting is a framework for developing distributed applications. It is the successor to DEC/RPC/DCOM. Simply put, Remoting allows an application (Remoting Client) to instantiate a type (Remotable class) on a remote server (Remoting server) across network. Communication between client and server object (Instance of Remotable class) hosted by a Remoting Server is channeled through a "proxy" – representation of server object on client side.

Remoting Server can be a simple console application, a Windows Service or hosted on IIS. It's responsible for hosting server objects and publishes them to the outside world. A Remoting client is any application that consumes the published server object.

There're many tutorials on this broad subject. Unfortunately, most are lacking, in one way or another, in coverage of basic maneuvers a developer needs to know. The purpose of this article is to supplement MSDN and to provide a complete coverage of basic remoting tasks in one short article.

Objectives of the tutorial

The tutorial will cover the following topics.

  • Basic Remoting , Programmatic Configuration (channel/type), Config files, LeaseTime, new, Activator.GetObject (server-activated object, or SAO) and Activator.CreateInstance (client activated object, or CAO)
  • How to implement interface for remote class, and interact with remote object through interface.
  • Passing custom/user-defined objects between remote object and client.
  • Asynchronous method calls on remote object.
  • Events and remoting: How client can subscribe to events generated by remote object.

This tutorial does not cover the following topics.

  • Hosting server from IIS [Ref - 2,7]
  • Delayed loading of channels
  • Tracking services
  • ClientSponsor and sponsoring mechanism [Ref 5]
  • Custom formatter, sinks, channels – that's where things actually get complicated. [Ref 1]
  • SoapSuds

Prerequisites

  • .NET Delegates and events.
  • Asynchronous programming. IAsyncResult, BeginInvoke, EndInvoke, WaitHandle... etc.

Package

  • dnetremoting.zip

Overall Architecture

  • ReadMe: dnetremoting.doc (That's this document.)
  • Project folders, classes and overall structure:

[Editor Note - hyphens/spaces have been used in the table contents to prevent scrolling]

Module

Folder/Directory

Primary class

namespace

Remarks

Interface

Dll

/CRemote-ObjInterface

source: IRemoteObj.cs

CRemote-ObjInterface

nsCRemote-ObjInterface

Interface (IRemoteObj) for CRemoteObj. Three interface methods:

  • Authenticate-Loser
  • SetupUser
  • UpdateProfile
CProfile

To illustrates how to pass custom object between remote object (CRemoteObj) and client. It's marked Serializable and is input/return parameter of CremoteObj. UpdateProfile.

CStatusEventSink

This is the event sink class to be instantiated in client's process. It's derived from MarshalByRefObject. The class has a public method StatusHandler. This method handles the events raised by CRemoteObj. AuthenticateLoser

StatusEventArgs

Serializable event argument. Refer to CremoteObj. evStatus. Delegate to evStatus event can be found in CRemoteObj class. Signature as follows:

public delegate void StatusEvent( object sender, StatusEventArgs e);

Remote Object dll

/CRemoteObj

source: CRemoteObj.cs

CRemoteObj

nsCRemoteObj

That's your remote object. This class implement IRemoteObj interface and exposes:

Methods

  • Authenticate-Loser – Demonstrates how to client can response to events generated by remote object.
  • SetupUser – Demonstrates how to execute asynchronous calls and method parameters IO.
  • UpdateProfile – Demonstrates how to pass instances of user-defined class in and out of remote object.

Properties

  • objID - Remote object's lifetime depends on the object's activation mode:
  1. Server activated - SingleCall
  2. Server activated - Singleton
  3. Client activated

With Server- activated-SingleCall objects, a new instance of remote object is created every time client invoke a method through the proxy. For "Server-activated-Singleton" and "Client-activated" objects, lifetime of the remote object depends on server configurations (config file <lifetime> element or programmatically via LifeTimeServices), as well as when client invoke a method on the remote object. Default LeaseTimeis 300 sec.

The object ID is to demonstrate remote object lifetime by marking each object with a randomly generated ID. Pay attention to server console when invoking methods on remote object.

Server

exe

/CRemote-ObjServer

source: CRemote-ObjServer.cs

Executable:

\web_vdir\bin\ CRemoteObj-Server.exe

(C# console app)

CRemote-ObjServer

nsCRemote-ObjServer

CRemoteObjServer hosts/publishes the remote object CRemoteObj.

Config files

  • cremoteobjserv (client). config – for client activation.
  • cremoteobjserv (wellknown) .config – for server activation; WellKnown ObjectMode = SingleCall. You need to modify the config file if you wish to publish the remote object using Singleton mode.

OPTION 2 loads config file as follows:

Remoting Configuration. Configure( "cremoteobjserv. config");

Please be reminded that security setting in config files will be ignored unless file name comply to the following convention:

AssemblyName. exe.config

In this case:

CRemoteObjServer. exe.config

For this tutorial, rename config file name to: cremoteobjserv.config and place it in the same folder (\web_vdir\bin) as the exe.

Client

exe

/CRemote-ObjClient

source:

CRemoteObj-Client.cs

Executable:

\ bin\Debug \CRemoteObj-Client.exe

(C# console app)

CRemote-ObjClient

nsCRemote-ObjClient

NOTE:

If it wasn't due to the fact that we used "new" keyword to instantiate remote object in <BLOCK 2-A>:

CRemoteObj obj = new CRemoteObj();

We could have eliminated reference to CRemoteObj assembly – reference to interface CRemoteObjInterface would suffice.

Config files (folder: /config file):

  • cremoteobjclient (client).config – for client activation.
  • cremoteobjclient (wellknown). config – for server activation;

In <BLOCK 2-A>, we called Configure to load configuration file:

RemotingConfiguration. Configure( "cremoteobjclient. config");

Please be reminded that security setting in config files will be ignored unless file name comply to the following convention:

AssemblyName .exe.config

In this case:

CRemoteObjClient .exe.config

For this tutorial, rename config file name to: cremoteobjclient.config and place it in the same folder (\web_vdir\bin) as the exe.


To run the sample

  1. Run server executable.
  2. Run client executable.

Server and client are both C# console apps. Pay attention to console output.

Configuration files

Before Remoting client and Remoting Server can begin to communicate, two pieces of information must first be properly configured, whether you're on server side or client side:

Communication channel

  • Communication channel: port number, protocol, uri/url. <channel> tags in configuration files. (Both server and client side)

NOTE: You don't need to specify port number for client side.

Type (Remotable class) to be published or consumed:

  • Resource to be published and activation mode (config file for remoting server) <service> tag in configuration files (Remoting server side).
  • Resource to be consumed (config file for remoting client) <client> tag in configurations files (Remoting client side).

Two configuration options:

  • Using XML configuration files:
    • RemotingConfiguration.Configure("cremoteobjserv.config");
  • Configure setting programmatically:
    //Configure channel:
    ChannelServices.RegisterChannel(httpchannel);
    //Register TYPE for server-activated objects (SAO):
    RemotingConfiguration.RegisterWellKnownServiceType(
      typeof(nsCRemoteObj.CRemoteObj), //Type"CRemoteObjURI", //"object URI" (NOT URL to remoting server!)
      WellKnownObjectMode.SingleCall //Activation mode: SingleCall or Singleton
    );
    //Register TYPE for client-activated objects (CAO):
    RemotingConfiguration.RegisterActivatedType(
      Typeof(nsCRemoteObj.CRemoteObj) );

Note: There're two types of server object: SAO (server activated object) and CAO (client activated object). SAO can be further divided into two types: singlecall and singleton. Here's a brief description.

  • Server-Activated Object (SAO) = Well-known Object.
  • Client Activated Object (CAO).

SAO

SingleCall - A new instance of the remotable class is created every time a client invokes a method through proxy.

Lifetime: Instance remains in memory for duration of method call.

This is stateless.

Singleton - The Singleton Instance is created on first method call.

Lifetime: This instance stays in memory until "Lease" expires.

This is Stateful. Only one instance of Singleton-SAO is instantiated on remoting server. Multiple Remoting Clients are serviced by same Singleton-SAO. Multiple remoting clients can communicate through one instance of Singleton SAO.

CAO

  • Instantiated on remote server as soon as the remoting client requests that an instance be created through Activator.CreateInstance or new keyword. This is in contrast to SAO where server objects are created upon first method call.
  • Lifetime: Just as Singleton-SAO, object stays in memory until "Lease" expires.
  • Stateful. Each CAO instance services only the remoting client responsible for its creation. Therefore, if you have multiple Remoting Client, each gets their own instance of CAO.

Single-call SAO is the most scalable option among the three and is most suitable in a load-balanced environment. Singleton-SAO and CAO offers more flexibility that:

  • Remoting clients have complete control over a server object's lifetime.
  • Server objects are stateful. CAO maintains states across multiple requests/method call. Singleton-SAO maintains states across multiple Remoting Client.

Sample configuration files

Server side

Server activated. File name: cremoteobjserv(wellknown).config

Client side

Server-activated. File name: cremoteobjclient(wellknown).config

In addition to type and channel information, configuration file may contain setting such as object lifetime, security... Example: lifetime of Singleton SAO and CAO remote object can be configured in <lifetime> tag in a config file:

Remote object lives as long as CurrentLeaseTime>0. When the object first got instantiated, CurrentLeaseTime = leaseTime. From this point on, CurrentLeaseTime starts decrementing until the next method call, or that a "sponsor" extend the "lease". On next method call (if lease has not yet expired), CurrentLeaseTime is reset to renewOnCallTime. On the other hand, if CurrentLeaseTime decrement to zero before next method call arrives, the object is marked for garbage collection. If this happens, the next method call will be serviced by a new instance of the remote class. In short, "server-activated Singleton" and "client activated" remote object lives for as long as CurrentLeaseTime>0. Lifetime setting can also be configured programmatically:

				using System.Runtime.Remoting.LifetimeServices;
LifetimeServices.LeaseTime = TimeSpan.FromMinutes(3);
LifetimeServices.RenewOnCallTime = TimeSpan.FromMinutes(3);

For more information about leasing mechanism and sponsoring mechanism, refer to MSDN under the topic "Lifetime Leases" [Ref 5] and "Remoting Example: Lifetimes" [Ref 8].

Assembly References from "client" assembly

  • Reference to interface assembly (i.e.. "Project" menu>"Add Reference">select interface CRemoteObjInterface.dll)
Now, since we use "new" keyword in BLOCK 2-A, we added reference to remote object assembly (CRemoteObj.dll) in addition to reference to interface assembly (CRemoteObjInterface.dll). In general however, we can retrieve proxy via Activator.GetObject or Activator.CreateInstance, thereby eliminating need to reference remote object assembly (i.e.. Client references ONLY interface assembly).

Code blocks in client source file CRemoteObjClient.cs

Basically, client source is separated into two mutually exclusive blocks – marked by the following tags in client's source code CRemoteObjClient.cs:

<BLOCK X-Y>

... code block ...

<BLOCK X-Y END>

You should comment out BLOCK 1 when you want to run code in BLOCK 2, and vice versa.

  • BLOCK 1: remote object is published as server-activated object.
  • BLOCK 2: remote object is published as client-activated object.

So, depending on setting in CRemoteObjServer, you may wish to comment out one of the blocks before you compile and execute. In addition, <BLOCK 2-A> and <BLOCK 2-B> are mutually exclusive.

  • BLOCK 2-A configures channel/type information by loading the config file, then instantiate remote object with "new" keyword.
  • BLOCK 2-B configures channel/type information programmatically. Proxy is retrieved with call to Activator.CreateInstance(..)

So, again, comment out one of the two before compile and execute.

namespace nsCRemoteObjClient

{ //nsCRemoteObjClientclass CRemoteObjClient

{ //CRemoteObjClient
  1. Delegate to CRemoteObj.SetupUser()
  2. Signature: public string SetupUser(string sname, string spasswd, out string sTime)
  3. Refer to <BLOCK 1-C>
publicdelegatestring SetupUserDelegate(...);
[STAThread]
staticvoid Main(string[] args)
{ //Main()//Scenario 1: "server-activated" remote object. 

<BLOCK 1>

try

{ //try-A

<BLOCK 1-A>

Retrieve interface/proxy:
  • Illustrates how to retrieve proxy via: Activator.GetObject(...)
  • Cast proxy into IRemoteObj: further separation between provider and supplier.

<BLOCK 1-A END>

<BLOCK 1-B>

  • LeaseTime
  • Invoke method on remote object through interface.

<BLOCK 1-B END>

<BLOCK 1-C> Asynchronous programming/remoting

  • Illustrates asynchronous calls on remote objects and how to pass parameters into/out of methods exposed by remote object using BeginInvoke, EndInvoke.
  • Illustrates how to pass user defined object to/from remote object.

<BLOCK 1-C END>

} //try-Acatch(Exception err)
{
}

<BLOCK 1 END>

  • Scenario 2: "client-activated" remote object.

<BLOCK 2>

try

{ //try-B

<BLOCK 2-A>

  • Illustrates how to configure channel information using config file.
  • Illustrates how to instantiate remote object with "new" keyword.

<BLOCK 2-A END>

<BLOCK 2-B>

Illustrates how to retrieve proxy to remote object via

Activator.CreateInstance(...)

<BLOCK 2-B END>

<BLOCK 2-C>

  • Illustrate how to invoke method through proxy.
  • Illustrates how to subscribe to events generated by remote object.

<BLOCK 2-C END>

} //try-Bcatch(Exception err)
{
}

<BLOCK 2 END>

    } //Main()
  } //CRemoteObjClient
} //nsCRemoteObjClient

Code blocks in server source file

  • Block 1: Option 1: programmatic registration of channel and type information thru:
ChannelServices.RegisterChannel
RemotingConfiguration.RegisterWellKnownServiceType
  • Block 2: Option 2: Configure by loading xml config file:
RemotingConfiguration.Configure("cremoteobjserv.config");

Again, you comment out OPTION 1 if you're using OPTION 2, and vice versa.

Walkthrough

  • Remoting Basic, configuration (channel/type): programmatic, config files. LeaseTime, "new", Activator.GetObject (for server-activated) and Activator.CreateInstance (for client activated)
  • Config files: See previous section.

LeaseTime

  • Change config file, and run application in different activation mode: server-activated SingleCall, server-activated Singleton, client-activated.
  • Run the client. Comment out <BLOCK 2> and <BLOCK 1-C>. Pay attention that obj.AuthenticateLoser is invoked in <BLOCK 1-B>
  • Monitor server console and objID. Default LeaseTime is 300-sec for Singleton, if time elapsed between method calls is greater than 300sec, a new instance of remote object gets created each call when remote object runs under the following mode:
    • server-activated Singleton.
    • Client-activated

Lease configuration can be found in server config file:

<lifetime leaseTime="100MS" sponsorshipTimeout="50MS" 
    renewOnCallTime="100MS" leaseManagerPollTime="10MS" />

In <BLOCK 1-B>, time between consecutive method calls is 101 ms. That's a little over 100 sec. So, every method call should be serviced by a new instance – therefore a different objID. Adjust lease configuration and observe. For server-activated SingleCall, a new instance service every method call.

NOTE: objID is a randomly generated object ID assigned to an instance of remote object CRemoteObj – take a look at constructor.

"new" keyword: Instead of Activator.GetObject or Activator.CreateInstance, we can use "new" to instantiate the remote object. However, if you choose to configure channel and type information via config file, you may instantiate remote object using "new" keyword. However, "new" implies that you'll be instantiating CRemoteObj – as opposed to interface IRemoteObj. This further implies that your client assembly must reference CRemoteObj assembly.

Activator.GetObject: Take a look at CRemoteObjClient.cs <BLOCK 1-A>

IRemoteObj obj = (IRemoteObj) Activator.GetObject(
typeof(IRemoteObj),
"http://localhost:8085/CRemoteObjURI"
);

Activator.CreateInstance: Take a look at CRemoteObjClient.cs <BLOCK 2-B>

//Note that it references CRemoteObjServer – not the remote object.object[] attrs = { new UrlAttribute(
  "tcp://localhost:8086/CRemoteObjServer") };
ObjectHandle handle = (ObjectHandle) Activator.CreateInstance(
  "CRemoteObj", 
  "nsCRemoteObj.CRemoteObj", 
  attrs);
CRemoteObj obj = (CRemoteObj) handle.Unwrap();

How to implement interface for remote class, and interact with remote object through interface.

Interface: IRemoteObj (CRemoteObjInterface.cs)

If we instantiate remote object via Activator's static methods, it'd be unnecessary for client assembly to reference remote object's assembly. This provides further separation between the remote object and the client. If client side settings are loaded from config file instead, remote object has to be instantiated using "new" keyword. This implies client has to reference remote object's assembly.

Take a look at the source code. Pay attention to the architecture and assembly references.

Passing custom/user-defined objects between remote object and client.

User defined class: CProfile (defined in CRemoteObjInterface.cs)

//ATTENTION: Must be marked Serializable to pass //user-defined object in/out of remote object.
[Serializable] 
publicclass CProfile
{
  publicstring fname;
  publicstring lname;
  publicstring user;
  publicstring passwd;
  //... other stuff...
};

Method: CRemoteObj.UpdateProfile (defined in CRemoteObj.cs)

The method takes a CProfile object as argument, and return a CProfile object.

public CProfile UpdateProfile(CProfile p)
{
//...other stuff...return pout;
}

The method is also exposed by interface IRemoteObj, although it's not necessary. Call to method is trivial. Now, let's take a look at <BLOCK 1-C> in client's source file CRemoteObjClient.cs.

CProfile p = new CProfile("John", "Kennedy", "JFK", "ace");
CProfile p2 = obj.UpdateProfile(p);
Console.WriteLine("UpdateProfile completed. return p2 passwd: {0}", 
    p2.passwd);

Asynchronous method calls on remote object.

Take a look at <BLOCK 1-C> in client's source file CRemoteObjClient.cs.

//ATTENTION://Delegate for CRemoteObj.SetupUser:publicdelegatestring SetupUserDelegate(
    string string1, string string2, outstring string3);

//...other stuff...

Main(...)
{

<BLOCK 1-C>

Wrap SetupUser in a delegate before calling BeginInvoke:

  • SetupUserDelegate d = new SetupUserDelegate(obj.SetupUser);

Invoke CRemoteObj.SetupUser asynchronously:

string sTime;
IAsyncResult ar = d.BeginInvoke(
  "Dexter",
  "AceInOpen",
  out sTime,
  null,
  null
);
//Blocks the current thread until obj.AuthenticateLoser completes.
ar.AsyncWaitHandle.WaitOne(); 
if(ar.IsCompleted)
{
  //Retrieving output: //(a) "output variable" sTime//(b) "return variable" value.string ret = d.EndInvoke(out sTime, ar);
  Console.WriteLine("obj.SetupUser return. sTime:{0}{1}" +
  "return value: {2}", sTime, Environment.NewLine, ret);
}

//...other stuff...

<BLOCK 1-C END>

//...other stuff...
}

Events and remoting: How client can subscribe to events generated by remote object.

The event handler is declared in the interface module CRemoteObjInterface.

Routing mechanism: Here's the sequence of things that takes place when client invoke the AuthenticateLoser:

That's it. I hope this article provide a good coverage of most basic maneuvers that would be expected of a component developer. Go through the source code and play around with different configurations. You should be well on your way to building distributed applications and harness power offered by .NET Remoting.

References

raymond.fung


Click here to view raymond.fung's online profile.

只有注册用户登录后才能发表评论。