Programming with TCP Remoting

I l @ ve RuBoard

The remoting infrastructure enables you to use different models of processing and activation. Let's look at the different options available and how you can implement distributed systems using them. We'll initially concentrate on remoting using the TCP channel and a custom server host process. In subsequent sections, we'll look at the HTTP channel and discuss how to host remote objects in IIS.

Server-Activated Object Remoting

Server-activated object types must be registered with the host server application. We did this earlier using the static RegisterWellKnownServiceType method of the RemotingConfiguration class. An alternative to hard-coding the details of individual types and channels into the host server is to use a configuration file. (We discussed configuration files in Chapter 2.) The schema used by application configuration files includes a system.runtime.remoting element, which supports various child elements and attributes used for specifying remoting configuration information.

The following listing, CakeSizeServer.exe.config, is a configuration file for the CakeServer application. The <application> element contains information about the remote objects that an application hosts or consumes. The <application> element holds a number of child elements. The two most important ones for host server applications are <channels> and <service> . (The order of the child elements is unimportant.)

CakeSizeServer.exe.config
 <?xmlversion="1.0"?> <configuration> <system.runtime.remoting> <application> <channels> <channelport="6000" displayName="CakeServerChannel" type="System.Runtime.Remoting.Channels.Tcp.TcpChannel, System.Runtime.Remoting,Version=1.0.3300.0,Culture=neutral, PublicKeyToken=b77a5c561934e089" /> </channels> <service> <wellknowntype="CakeUtils.CakeInfo,CakeUtils" objectUri="CakeInfo.rem" mode="Singleton" /> </service> </application> </system.runtime.remoting> </configuration> 

The <channels> element specifies the channels that the host application uses. It comprises one or more <channel> elements containing the details for each channel. A host server can listen on multiple channels, but each channel type can be used only once. (You cannot listen on two TCP channels, for example, but you can listen on a TCP channel and an HTTP channel.) The attributes of a channel are the port to listen on, the type of the channel (which is specified as a fully qualified class and assembly), and an optional display name (which is used by programs such as the .NET Framework Configuration Tool to identify the channel for display purposes only).

Configuration File Schema Templates

The entry specifying the assembly that provides the TcpChannel class shown in the preceding CakeSizeServer.exe.config file uses a fully qualified assembly name, including version, culture, and public key token. This is a bit of a mouthful and can be painful to maintain ( especially if you have a lot of configuration files) if a new version of the System.Runtime.Remoting assembly is released. For this reason, the configuration file schema also supports templates.

A template is a predefined set of attributes each of which is named with a unique string identifier (the id attribute) that can be referenced elsewhere simply by using this identifier. The machine.config file (located under \WINDOWS\Microsoft.NET\Framework\< version >\Config) contains a number of useful templates at the end. The final part of the machine.config file is reproduced here:

 <system.runtime.remoting> <channels> <channelid="http" type= "System.Runtime.Remoting.Channels.Http.HttpChannel, System.Runtime.Remoting,Version=1.0.3300.0,Culture=neutral, PublicKeyToken=b77a5c561934e089"/> <channelid="httpclient" type= "System.Runtime.Remoting.Channels.Http.HttpClientChannel, System.Runtime.Remoting,Version=1.0.3300.0,Culture=neutral, PublicKeyToken=b77a5c561934e089"/> <channelid="httpserver" type= "System.Runtime.Remoting.Channels.Http.HttpServerChannel, System.Runtime.Remoting,Version=1.0.3300.0,Culture=neutral, PublicKeyToken=b77a5c561934e089"/>  <channelid="tcp" type= "System.Runtime.Remoting.Channels.Tcp.TcpChannel, System.Runtime.Remoting,Version=1.0.3300.0,Culture=neutral, PublicKeyToken=b77a5c561934e089"/>  <channelid="tcpclient" type= "System.Runtime.Remoting.Channels.Tcp.TcpClientChannel, System.Runtime.Remoting,Version=1.0.3300.0,Culture=neutral, PublicKeyToken=b77a5c561934e089"/> <channelid="tcpserver" type= "System.Runtime.Remoting.Channels.Tcp.TcpServerChannel, System.Runtime.Remoting,Version=1.0.3300.0,Culture=neutral, PublicKeyToken=b77a5c561934e089"/> </channels> <channelSinkProviders> <clientProviders> <formatterid="soap" type= "System.Runtime.Remoting.Channels.SoapClientFormatterSinkProvider, System.Runtime.Remoting,Version=1.0.3300.0,Culture=neutral, PublicKeyToken=b77a5c561934e089"/> <formatterid="binary" type= "System.Runtime.Remoting.Channels.BinaryClientFormatterSinkProvider, System.Runtime.Remoting,Version=1.0.3300.0,Culture=neutral, PublicKeyToken=b77a5c561934e089"/> </clientProviders> <serverProviders> <formatterid="soap" type= "System.Runtime.Remoting.Channels.SoapServerFormatterSinkProvider, System.Runtime.Remoting,Version=1.0.3300.0,Culture=neutral, PublicKeyToken=b77a5c561934e089"/> <formatterid="binary" type= "System.Runtime.Remoting.Channels.BinaryServerFormatterSinkProvider, System.Runtime.Remoting,Version=1.0.3300.0,Culture=neutral, PublicKeyToken=b77a5c561934e089"/> <providerid="wsdl" type= "System.Runtime.Remoting.MetadataServices.SdlChannelSinkProvider, System.Runtime.Remoting,Version=1.0.3300.0,Culture=neutral, PublicKeyToken=b77a5c561934e089"/> </serverProviders> </channelSinkProviders> </system.runtime.remoting> 

The template with the id of tcp contains the information required by the server configuration file, which can be updated to use a ref to this template:

 <channels> <channelport="6000" displayName="CakeServerChannel" ref="tcp" /> </channels> 

If the version number of the System.Runtime.Remoting assembly changes, only the machine.config file needs to be updated.

The <service> element identifies the types that the host server application exposes and specifies the activation mode for each type. The <service> element can contain <wellknown> elements describing server-activated types and <activated> elements that provide information about client-activated types. For server-activated types, the <wellknown> element comprises attributes specifying the type (class and assembly, including locale, version, and public key token information if the assembly is in the GAC), the activation mode, and the published URI for objects of this type. As with the <channel> element, an optional display name is provided.

After creating a configuration file, you can make the host server application read the remoting information and configure itself, using the static RemotingConfiguration.Configure method. You specify the name of the configuration file as the parameter:

 RemotingConfiguration.Configure("CakeSizeServer.exe.config"); 

This should replace any calls made to ChannelServices.RegisterChannel and RemotingConfiguration.RegisterWellKnownServiceType . You can modify the configuration file, changing channel information and adding or removing types without having to rebuild the host application. The result of these changes is a reasonably generic server application that can host almost any type of remote object, although the server still needs to be linked with the assembly that provides the remote object class.

Although there is no direct reference to the assembly or class within the code, the runtime still needs to know about the remote object assembly to activate the appropriate version of the object, so the metadata for the server application must include this information. An alternative is to use reflection in the host server application to dynamically load the required assembly when the server executes. The RemotingConfiguration class provides methods that allow you to extract the details of the configuration file that would be useful in this situation. We'll cover some of these methods later. To see the completed code for the nonreflected server, consult the CakeServer.jsl sample file in the ConfigTCPCakeSizeServer project.

Tip

A generic host server is a prime example of an application that you should consider making into a Windows service. See Chapter 15 for details.


You can also use a configuration file with the client. In this case, the important elements are <channels> (again) and <client> . A client channel does not need to specify a port number. The <client> element can contain <wellknown> child elements describing server-activated remote objects and <activated> elements that provide information about client-activated remote objects. The <wellknown> element has url and type attributes, which should be set to match those of the remote object, plus an optional displayName attribute. The file CakeClient.exe.config, which follows , shows the client configuration file for the CakeClient application:

CakeClient.exe.config
 <?xmlversion="1.0"?> <configuration> <system.runtime.remoting> <application> <channels> <channeldisplayName="CakeClientChannel" ref="tcp" /> </channels> <client> <wellknowntype="CakeUtils.CakeInfo,CakeUtils" url="tcp://localhost:6000/CakeInfo.rem" />; </client> </application> </system.runtime.remoting> </configuration> 

As with the server, when a client has a configuration file such as this, it can dispense with the call to ChannelServices.RegisterChannel , replacing it with a call to RemotingConfiguration.Configure that specifies the configuration file as its parameter. Furthermore, you can replace the call to Activator.GetObject with the new operator, making the remote object reference appear just like a local object:

 RemotingConfiguration.Configure("CakeClient.exe.config"); CakeInfoci=newCakeInfo(); 

The complete code sample, CakeClient.jsl in the ConfigTCPCakeClient project, shows the client application updated to use the configuration file.

Activation Modes

The .NET Framework provides two activation modes for server-activated objects: Singleton and SingleCall . The activation mode is specified when the object type is registered, either in the configuration file or using the RegisterWellKnownServiceType method. (If you're using the RegisterWellKnownServiceType method, you should specify one of the values in the System.Runtime.Remoting.WellKnownObjectMode enumeration.)

When a client invokes a method on a server-activated remote object, the sequence of operations that follows depends on the activation mode. If the remote object is activated in Singleton mode, the remoting infrastructure will consult the internal tables of the host server to determine whether an object of the appropriate type has already been created. If so, the request will be directed to that object; if not, the remoting infrastructure will create a new instance. In this arrangement, it is possible that many clients will be executing methods on the same instance of the remote object at the same time, in different threads.

Caution

Be sure that all methods defined by classes that can be remoted in Singleton mode are thread-safe.


If the remote object is activated in SingleCall mode, the remoting infrastructure will create a new instance of the remote object for each client method call. The object will be destroyed after the method call completes and will not be reused. No two clients will share the same object. In this case, thread safety becomes an issue only if the remote class defines static data.

From the preceding discussion, you might infer that you can use a singleton remote object to share global data between client applications. While this is feasible , you should be aware of some caveats. The major issue is that even though the singleton mode ensures that no more than one instance of the remote object will be active at any time in the host server application, the remoting infrastructure might destroy the object if it has not been used for a while and re-create a new instance when a client needs it. As a result, you cannot guarantee to the client that it will always have access to the same instance of the singleton object, and you cannot rely on the values of any instance variables being retained. The purpose of this approach is to conserve resources ”memory, especially ”on the server computer. We'll look in detail at how object lifetimes are managed later.

Even though objects might be continually created and destroyed, the remoting infrastructure does maintain the semantics of static data ”but again, you must be aware of how static data access is managed in .NET. Static data is preserved even as objects are torn down and re-created by the runtime. However, static data and methods are regarded as context-agile regardless of the contextual requirements of the defining class. This means that they are always accessed from within the same context as the caller and are never actually remoted ”the client must have access to the assembly defining the remote class, and this information is used to create local copies of static data and execute static methods.

In the case of a remote method, the remote object's copy of the static data will be used, but a client application running in a different context will have its own local static data, probably with different values! Similarly, if the client invokes a static method on a remote object, the TransparentProxy will instead direct the method call to a local copy running in the same context as the client.

Finally, when a host server application instantiates a server-activated object on behalf of a client, it will build the object using its default constructor. It is therefore vitally important to be sure that it has one and that it performs whatever initialization is required. Failure to provide such a constructor will result in the runtime throwing a System.MissingMethodException when a client makes the first method call. (This can be confusing because it is the constructor that is missing rather than the method being called.) The runtime does not currently provide a mechanism for invoking other constructors on server-activated objects (unlike client-activated objects).

Using Interfaces

For a client to use a remote object, it must have access to the metadata describing that object. One way to make the metadata available is to provide the assembly containing the remote object class to the client, but this is wasteful and can lead to versioning issues, especially if the host server application is updated to use a later release of the assembly. A more flexible approach is to define an interface for the remote object. The server should instantiate an object that implements this interface, and the client should activate the object through the interface. For example, in the CakeServer scenario, the CakeInfo class in the CakeUtils assembly can be represented by the ICakeInfo interface, packaged up in the CakeInterface assembly, as shown here:

 packageCakeInterface; publicinterfaceICakeInfo { publicshortFeedsHowMany(shortdiameter,shortshape,shortfilling); } 

The CakeInfo class can be amended to implement this interface. (This version of the CakeInfo class is available in the InterfaceCakeUtils project.)

 packageCakeUtils; importCakeInterface.*; publicclassCakeInfoextendsMarshalByRefObjectimplementsICakeInfo { } 

Note

If the CakeUtils assembly is strongly named and stored in the GAC, the CakeInterface assembly (holding the ICakeInfo interface) must also be strongly named and placed in the GAC.


The code in the host server application can be left untouched (although you might need to recompile it against the new version of the CakeInfo class ”a relinked version is available in the InterfaceTCPCakeSizeServer project), but you should modify the client to use this interface. If you're using a client configuration file, this involves changing the <wellknown> element:

 <wellknowntype="CakeInterface.ICakeInfo,CakeInterface" url="tcp://localhost:6000/CakeInfo.rem" />; 

Also, if you've replaced the Activator.GetObject method call in the client code with the new operator, you'll unfortunately have to put it back because you cannot use new to instantiate an interface using the Java language. However, you can make use of the information supplied in the client configuration file to avoid hard-coding any assembly and remote server URL information. The RemotingConfiguration class supplies the static GetRegisteredWellKnownClie-ntTypes method, which retrieves an array of WellKnownClientTypeEntry objects that contain the contents of the <wellknown> elements. You can find the type and URL of an object by querying the ObjectType and ObjectUrl properties. The following code fragment shows how to activate a remote CakeInfo object using the ICakeInfo interface and a configuration file:

 importCakeInterface.*; RemotingConfiguration.Configure("CakeClient.exe.config"); WellKnownClientTypeEntry[]entry= RemotingConfiguration.GetRegisteredWellKnownClientTypes(); ICakeInfoci=(ICakeInfo)Activator.GetObject(entry[0].get_ObjectType(),entry[0].get_ObjectUrl()); 

When you build the client application, you must reference the CakeInterface assembly containing the interface, but you no longer need access to the CakeUtils assembly. The code sample CakeClient.jsl in the InterfaceTCPCakeClient project shows the updated version of the CakeClient class. (The CakeShape and CakeFilling classes have been moved to the CakeInterface assembly to allow continued access by the client.)

Client-Activated Object Remoting

A client-activated object, as its name suggests, is a remote object that is created and managed by a client application. The host server application simply provides an environment in which such objects can execute. The client is passed a reference to the remote object in the usual way ”through a RealProxy / TransparentProxy pair (as described earlier).

Configuration File Settings

Client-activated object types have to be registered with the host server application, using either the RemotingConfiguration.RegisterActivatedServiceType method or a configuration file that uses the <activated> element rather than the <wellknown> element to define the service. We discussed the RegisterActivated ­ServiceType method earlier, but for completeness, the following fragment shows the <service> entry that you can place in the server configuration file if you prefer to use the RemotingConfiguration.Configure method:

 <service> <activatedtype="CakeUtils.CakeInfo,CakeUtils"/> </service> 

The client configuration file should be adjusted in a similar manner. The <wellknown> child element of the <client> element should be replaced with an <activated> element that specifies the remote object type. Also, the <client> element itself must have a url attribute added that indicates the protocol and port that the server is listening on to wait for activation requests . This information corresponds to the <channel> attributes specified in the server configuration file. The modified <client> element is shown here:

 <clienturl="tcp://localhost:6000"> <activatedtype="CakeUtils.CakeInfo,CakeUtils"/> </client> 
Activating Objects

A client can activate an object using the Activator.CreateInstance method. This was also described earlier, but bear in mind that one major advantage of client activation over server activation is that client-activated objects can be built using nondefault constructors. The CreateInstance method can take an array of constructor parameters as one of its arguments, and the constructor that best matches this parameter list will be used when the object is instantiated on the server (or a MissingMethodException will be thrown if no matching constructor can be found). Correspondingly, a disadvantage is that CreateInstance cannot be used to create an instance of an interface ”you can apply it only to object types. (A purist would argue that a client should not attempt to create a client-activated object using an interface anyway, but a workaround is shown below.)

To create an object using its default constructor, you need only specify the object type when you use CreateInstance . The remaining details ”URL and channel information ”are all discovered from the configuration file. You can also query the configuration file to obtain the registered type of the object. Again, this is useful if you want to avoid hard-coding type information into your programs. You saw earlier that the RemotingConfiguration class had static methods that allow you to query type information for well-known server-activated objects. It also has the equivalent functionality for obtaining information about client-activated objects: the GetRegisteredActivatedClientTypes method. This method returns an array of ActivatedClientTypeEntry objects (one for each <activated> element in the configuration file), and you can determine type and assembly information using properties exposed by the ActivatedClientType ­Entry class.

The following code fragment shows how to activate a CakeInfo object using a configuration file. This example uses the ICakeInfo interface, although the configuration file actually specifies the CakeInfo class. This is legal as long as the CakeInfo class implements the ICakeInfo interface.

 //Configuretheclientchannelandremoteobjectactivator RemotingConfiguration.Configure("CakeClient.exe.config"); //Extractthetypeoftheregisteredremoteinterface ActivatedClientTypeEntry[]entry= RemotingConfiguration.GetRegisteredActivatedClientTypes(); //CreateareferencetoaremoteCakeInfoobjectthrough //theremoteinterface ICakeInfoci= (ICakeInfo)Activator.CreateInstance(entry[0].get_ObjectType()); 

Once a client-activated object has been instantiated, it will remain alive on the server until the client destroys it by removing its last reference (possibly when the client application terminates) or the remoting infrastructure determines that the object has been inactive for too long and decides to remove it (a strategy discussed in the next section). All the time the remote server object is alive, any variables it contains will have their values preserved between method calls; this is another major difference between client-activated and server-activated objects. However, systems that rely heavily on client-activated objects might not scale well as a result.

Note

If you're familiar with the Enterprise JavaBeans (EJB) model of Java 2 Enterprise Edition (J2EE), you can think of the .NET client-activated object as being roughly analogous to the stateful session bean. A .NET server-activated single-call object is equivalent to a stateless session bean, and a .NET server-activated singleton object is similar to an entity bean. If you're a "COM head," you can think of a .NET client-activated object as similar to the model used by classical DCOM; a .NET server-activated single-call object also bears more than a passing resemblance to the stateless COM+/MTS model.


Managing Object Lifetimes and Leases

A key issue in distributed systems is balancing the need to keep objects available between client requests and conserving the resources required to keep a large number of objects in memory. The problem is how to determine whether an idle object will remain idle (in which case it can be destroyed) or might suddenly be activated by a client (in which case it will need to be created again shortly after it is destroyed). Although this issue affects singleton server-activated objects, in the case of client-activated objects, which are guaranteed to retain state between calls, the issue is even more acute because a host server application might not be aware that a client application has terminated and that its remote objects should be destroyed.

Architectures such as DCOM use reference counting to keep track of the number of clients holding references to a remote object, and they destroy objects when the number of references drops to zero, but this relies on the client applications themselves being written correctly. (If you're a DCOM hack, you'll undoubtedly have spent many happy hours trying to work out whether your AddRef and Release calls balance.) Even if the client code is correct, issues such as network failure can wreak havoc on such a system because clients can no longer inform host server applications that they've finished with a remote object. To try to counter the effects of this, DCOM implements periodic pinging of clients over the network, sending an "are you still alive" message ”the DCOM infrastructure on the client responds in the affirmative if the client application is still running and sends no response otherwise ). This uses network bandwidth ”a noticeable overhead in a system that comprises many thousands of objects and client applications executing concurrently.

With .NET, Microsoft has implemented a more scalable and fault-tolerant mechanism that places less reliance on the client code and that can cope with network failure. Remote object lifetimes are governed using a system of leases , a lease manager , and a series of sponsors . When a remote object is created, it is granted a lease that allows it to reside in memory for a designated period of time. When an object becomes idle, its lease starts ebbing away. If the object is not reactivated, its lease will eventually expire and the object can die.

Every application domain has a lease manager object. The purpose of the lease manager is to periodically examine the list of expired leases in its domain and determine whether to arrange for the corresponding objects to be garbage collected. Before this happens, the lease manager will ask any sponsors that have registered with the remote object whether they want to renew the lease for that remote object. Only if the lease is not renewed will the object be destroyed.

A sponsor is an object that implements the System.Runtime.Remoting.Lifetime.ISponsor interface. You can define your own custom sponsor class by implementing this interface; a default sponsor class is available in System.Run ­time.Remoting.Lifetime.ClientSponsor . The only method in the ISponsor interface is Renewal , which takes a lease as a parameter and returns a TimeSpan that indicates the length of any renewal period granted. A sponsor is associated with a particular object and can extend the lease for that object when the lease manager calls its Renewal method. (It might decide not to do so, depending on the policy the sponsor implements.)

A remote object does not have to have any sponsors, in which case it will be terminated when its lease first expires . (The lease will be renewed whenever a client calls a method on the object, however, so as long as an object is reasonably active it will not disappear.) Sponsors are typically created by the client application, which then registers them with a remote object's lease. This gives the client a degree of control over how the lifetime of the remote object is managed.

You can use the static RemotingServices.GetLifetimeService method to obtain the default lease for a remote object (which is returned as an ILease object):

 importSystem.Runtime.Remoting.*; importSystem.Runtime.Remoting.Lifetime*; //CreateareferencetoaremoteCakeInfoobject ICakeInfoci=(ICakeInfo)Activator.GetObject(...); //ObtainthelifetimeserviceleasefortheremoteCakeInfoobject ILeaselease=(ILease)RemotingServices.GetLifetimeService(ci); 

Internally, this method calls the GetLifetimeService of the remote object itself. The GetLifetimeService method is implemented by the MarshalByRefObject class, but a class can override it to change the terms of the default lease. The ILease interface supplies the Register method, which a client uses to specify a sponsor. Although the sponsor object is often created by the client, the sponsor object itself should also be remote, executing in the same application domain as the remote object, because the sponsor object must be directly available to the runtime on the remote server:

 //Createanewcustomsponsorobject ISponsorsponsor=(MyCustomSponsor)Activator.CreateInstance(...); //Associatethesponsorwiththeleasefortheremoteobject lease.Register(sponsor); 

A lease has a number of properties that you can query to determine its state, and you can modify some of these properties. The CurrentLeaseTime property ( get_CurrentLeaseTime in J#) returns a TimeSpan indicating the amount of time left on the lease, and the CurrentState property ( get_CurrentState ) returns the current state of the lease (initial, active, expired, and so on). You can use the InitialLeaseTime property to set the initial period for the lease. This is useful for client-activated objects that are instantiated before (possibly a long time before) the first method call. You can prevent the lease from expiring by setting this property to TimeSpan.Zero . Be careful, though ”remote objects with leases that do not expire can become memory hogs.

Each time a method is invoked on a remote object, its lease period is reset using the value specified by the RenewOnCallTime property. When a lease becomes due for renewal through inactivity and the lease manager asks a sponsor to renew the lease on an object, it will wait for the time specified by the SponsorshipTimeout property for the sponsor to reply. If the sponsor fails to respond in this period, it is assumed to have died and is removed from the lease's list of sponsors. Setting this property to TimeSpan.Zero will effectively disable all sponsors for this lease. These properties can be changed only on a lease that is in the initial state (before the corresponding object has been activated). The following code fragment shows how to set these properties:

 lease.set_InitialLeaseTime(newTimeSpan(0,2,0));//2minutes lease.set_RenewOnCallTime(newTimeSpan(0,3,0));//3minutes lease.set_SponsorshipTimeout(newTimeSpan(0,0,10));//10seconds 

You can set these properties globally for the lease manager in an application domain using the System.Runtime.Remoting.Lifetime.LifetimeServices class (the individual lease properties override those of the lease manager), which also exposes the LeaseTime , RenewalOnCallTime and SponsorshipTimeout static properties. You should set these properties from the host server application because that is where the lease manager that controls the remote object lifetime executes. In addition, the LeaseManagerPollTime property specifies how frequently the lease manager checks for expired remote object leases. These properties can also be set using a server application configuration file: You add a <lifetime> child element to the <application> element, as shown below. The suffixes determine the unit of time ”2M means 2 minutes, and 10S indicates 10 seconds, for example. You can also specify MS for milliseconds , H for hours, and even D for days:

 <?xmlversion="1.0"?> <configuration> <system.runtime.remoting> <application> <lifetimeleaseTime="2M" sponsorshipTimeout="10S" renewOnCallTime="3M" leaseManagerPollTime="2M"/> </application> </system.runtime.remoting> </configuration> 

TCP Remoting Security

Remote objects and assemblies are subject to the same security constraints as locally accessed objects. Security policies for code access dictate what a remote object can and cannot do, based on the location (or zone) of the client (remem-ber that code executes on a server computer when requested by a client) and the degree of trust the client assembly has been granted. The remoting infrastructure performs security checks when it invokes methods in the Remoting ­Configuration and ChannelServices classes, as well as when it activates objects. By default, a client can create a TCP channel to a host server, activate a remote object on that server, and execute its methods if the client assembly is located on the local intranet or on the same computer as the host server application. A client cannot activate a host object over the Internet unless the host server computer explicitly trusts the client assembly (or the zone permissions are adjusted on the server computer).

Securing the data sent from a client to a server requires encrypting the data before it is transmitted by the channel and decrypting it after the transmission. With the TCP channel, this involves adding a custom sink to the sink chain to perform the encryption and decryption on both the client and server ends of the channel. It makes sense to encrypt messages after they have been serialized and decrypt them again before they are deserialized. But authenticating clients based on identity and authorizing them to execute selected methods on a remote object would be a nontrivial task over the TCP channel because the TCP channel does not provide any built-in support for attaching credentials to messages. An additional custom sink to perform this task would be required on the client side of the channel before the data is serialized and encrypted. A corresponding sink on the server side would examine these credentials and accept or reject the method request. Such a strategy would also involve enrolling users and creating, storing, and managing credential information on the server, possibly in a secure database.

Security management is far easier using the HTTP channel and IIS as the host server application. The TCP channel was designed to be lean and mean, so you should use the TCP channel for applications executing using a local area network (LAN), where speed is the main issue, but employ the HTTP channel for wide area networks (WANs), intranets , and the Internet, where security is more important.

Remote Method Parameters

The examples you've seen so far in this chapter have shown how to invoke the FeedsHowMany method of the CakeInfo class. The parameters of this method are three short integers. Integers, like all the primitive types of J#, are marshaled by value when they're sent as parameters to a remote server. If the remote method changes any of these parameters, it simply changes a local copy in the server process. The new values will not be copied back when the method c-ompletes. In this respect, the semantics of a remote method call are similar to those of a local call in Java.

The fun starts when a remote method expects a nonprimitive object as a parameter. In this situation, you have a choice: You can marshal the parameter either by reference or by value, depending on how the object type is defined. Types that are marked with SerializableAttribute will always be marshaled by value, and the data will be copied to the server. For a parameter to be passed by reference, the type of the parameter must descend from MarshalByRef ­Object . Any changes that the server makes to the contents of this parameter will be treated by the server as a remote method call back to the client.

Also, when the client registers the channel, the client must create a port that the server can use for communicating back to the client application. This can be any unused port. It is often a good idea to let the remoting infrastructure itself pick a port; you can do this by specifying zero when you define the channel programmatically (or when you specify the <channel> element in a configuration file):

 //Registerachannelandcreateaportontheclient ChannelServices.RegisterChannel(newTcpChannel(0)); 

Remote Events

Recall that the event-handling mechanism of .NET uses delegates to refer to methods that subscribe to events. To provide a little variation and to prove that remoting clients and servers can be written in different languages, we wrote a version of the CakeInfo class in C# that publishes an event (available in the CSharpCakeUtils namespace sample shown below and also downloadable as the CSharpCakeUtils project).

Recall from Chapter 3 that a class that publishes an event should expose a method that allows subscribers to register delegates for this event. (The example uses the AddOnFeedsHowManyEvent method for this purpose.) When the event is raised, the methods referred to by any subscribing delegates are executed. It is also possible for remote objects to raise events that a client application can subscribe to, and the mechanism used is similar. However, one difference is that a delegate registered with a remote server object will point to a method executing in the client. When an event is raised, the server will submit a remote call back to the client to execute this method, temporarily reversing the roles of the client and the server.

Delegates themselves are marshal- by-value objects and will always be serialized when they are passed from a client to a remote object server. However, the method referred to by a delegate can be located in the client as long as some requirements are met, both in terms of the subscribing client and any arguments returned by an event when it is raised.

In the CSharpCakeUtils assembly shown below, the FeedsHowMany method is nearly identical to the J# implementation used in earlier examples in this chapter, with the exception that it raises an event called FeedsHowManyEvent before the method finishes. This method takes a parameter indicating the number of consumers the cake will serve and passes it to any registered handler that subscribes to this event.

The assembly also contains the FeedsHowManyEventArgs event arguments class, which defines the data returned to the client when the event is raised. This class must be marked as Serializable . (The data is marshaled from the server back to the client.) The CakeInfo class exposes the FeedsHowManyEventHandler delegate, which defines the callback that a client wanting to subscribe to the FeedsHowManyEvent should use. The public AddOnFeedsHowManyEvent method binds a delegate to the event. The OnFeedsHowManyEvent method raises the event when it is invoked from FeedsHowMany .

Although this version of the CakeInfo class is written in C#, it can still be hosted by a J# application that uses the same techniques shown earlier in this chapter. (The ClientActivatedTCPCakeSizeServerWithEvent project does just that.)

CSharpCakeUtils.cs
 usingSystem; usingSystem.Runtime.Remoting; usingSystem.Runtime.Serialization; usingCakeInterface; namespaceCSharpCakeUtils { ///<summary> ///C#implementationoftheCakeInfoclass,withevents ///</summary> //EventargsclassfortheFeedsHowManyEvent. //ThisdataisreturnedwhenFeedsHowManyEventisraisedbythe //FeedsHowManymethodoftheCakeInfoclass [Serializable()] publicclassFeedsHowManyEventArgs:EventArgs { privateintnumEaters; publicFeedsHowManyEventArgs(intnum) { numEaters=num; } publicintNumberOfEaters { get { returnnumEaters; } } } publicclassCakeInfo:MarshalByRefObject,ICakeInfo { //Definedelegateandevent publicdelegatevoidFeedsHowManyEventHandler(objectsender, FeedsHowManyEventArgsarg); privateeventFeedsHowManyEventHandlerfeedsHowManyEvent; //MethodthatraisestheFeedsHowManyEvent protectedvoidOnFeedsHowManyEvent(FeedsHowManyEventArgse) { if(feedsHowManyEvent!=null) feedsHowManyEvent(this,e); } //Publicmethodallowingclientstosubscribeto //theFeedsHowManyEvent publicvoidAddOnFeedsHowManyEvent(FeedsHowManyEventHandlerhandler) { feedsHowManyEvent+=handler; } publicCakeInfo() { Console.WriteLine("Hello!"); } //Workouthowmanypeopleacakeofagivensize,shape, //andfillingwillserve publicshortFeedsHowMany(shortdiameter,shortshape,shortfilling) { Console.WriteLine("Calculating..."); doublemunchSizeFactor=(filling==CakeFilling.Fruit?2.5:1); doubledeadSpaceFactor; switch(shape) { caseCakeShape.Square:deadSpaceFactor=0; break; caseCakeShape.Hexagonal:deadSpaceFactor=0.1; break; caseCakeShape.Round:deadSpaceFactor=0.15; break; default:deadSpaceFactor=0.2; break; } shortnumConsumers= (short)(diameter*munchSizeFactor*(1-deadSpaceFactor)); //RaisetheFeedsHowManyEventpassingbackthenumber //ofconsumers OnFeedsHowManyEvent(newFeedsHowManyEventArgs(numConsumers)); //Alsoreturnthenumberofconsumerstheregularway returnnumConsumers; } } } 

A subscribing object should instantiate a FeedsHowManyEventHandler delegate and then call the AddOnFeedsHowMany method of the remote CakeInfo object. The delegate can refer to a method in the client or even another remote method. The CakeClient.jsl sample in the ClientActivatedTCPCakeClientWithEvent project uses the local EventHandler method of the CakeClient class. Because the CakeClient class will be accessed from the remote server, it must also fulfill the requirements of a remote object: It must either be Serializable or inherit from MarshalByRefObject . If you mark the class as serializable, it will be marshaled by value and a copy will be created at the remote server. The delegate method will be invoked in this copy at the server, which is probably not what you want in this case. Therefore, this example uses MarshalByRefObject ; the client will be accessed by reference using a proxy from the server, and the delegated method will execute locally at the client.

Tip

If you want to see the difference between the two types of client marshaling, you can remove the MarshalByRefObject inheritance and instead mark the class with the Serializable attribute. When the event handler runs, the message output will be displayed on the server console screen and not the client.


A reference to the assembly containing the client code (CakeClient) must also be added to the host server application. The J# compiler does not allow you to reference executables (EXE files), only libraries (DLLs). Therefore, the CakeClient class has been wrapped up inside a DLL. The configuration file should be updated to use the CakeInfo class in the CSharpCakeUtils assembly. (We also changed the name of the configuration file to CakeClient.dll.config for consistency.) Furthermore, the client must specify a port that the server can use for communicating back to the client as part of the channel configuration. You can use the value 0 to allow the remote infrastructure to select an unused port. The configuration file must be deployed with each client application using this library:

 <?xmlversion="1.0"?> <configuration> <system.runtime.remoting> <application> <channels> <channelref="tcp" displayName="CakeClientChannel" port="0"/> </channels> <clienturl="tcp://localhost:6000"> <activatedtype="CSharpCakeUtils.CakeInfo,CSharpCakeUtils" /> </client> </application> </system.runtime.remoting> </configuration> 

Apart from adding the reference to the CakeClient assembly and changing the reference to the CakeUtils assembly to CSharpCakeUtils (and updating the <activated> element in the configuration file in the same way as the client), you don't have to change any code in the host server application. Figures 11-5 and 11-6 show the console for the server application and the client test harness (in the ClientActivatedTestHarness project), respectively. Notice that the event message is displayed at the client.

Figure 11-5. The console screen showing the output of the host server application

Figure 11-6. The console screen showing the output of the client test harness

I l @ ve RuBoard


Microsoft Visual J# .NET (Core Reference)
Microsoft Visual J# .NET (Core Reference) (Pro-Developer)
ISBN: 0735615500
EAN: 2147483647
Year: 2002
Pages: 128

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net