9.4 The Client

only for RuBoard

Now that the server is running, it's time to talk about building the client. The million dollar question is how the client will get the type information for ServerInfo . Directly referencing the ServerInfo assembly is one option, but not a realistic one, so it will not be considered . It doesn't take an active imagination to foresee the problems that can occur. Instead, this section examines a few techniques for consuming remote objects that allow the implementation to change without affecting the client.

9.4.1 SoapSuds

The .NET Framework SDK ships with a utility called SoapSuds.exe that can generate a proxy object the client can use. SoapSuds.exe will even generate the source code for the proxy (but only in C#). Just point it to the URL of the remote object, link to the client shown in Example 9-7, and run. The HTTPchannel must be specified along with the port that was given to the server.

The following command creates a proxy object (with C# source) to the Singleton version of ServerInfo :

 soapsuds -url:http://192.168.1.100:1970/RemotingTest/ ServerInfo.rem?wsdl -oa:ServerInfoProxy.dll -gc 
Example 9-7. Client for Remote Object Server
 'Remote Object Client '  'Compile: 'vbc /t:exe /r:system.dll /r:System.Runtime.Remoting.dll  '/r:ServerInfoProxy.dll remoteclient.vb  '     Imports System Imports ObjectServerSpace     Public Class Client       Public Shared Sub Main( )         Try           Dim si As New ServerInfo( )           If Not (si Is Nothing) Then         Console.WriteLine(si.MachineName)         Console.WriteLine(si.IPAddress)         Console.WriteLine(si.GetMachineTime)         Console.WriteLine("{0}MB Free", si.GetAvailableMemory)         Console.WriteLine("Processor Used: {0}", si.GetProcessorUsed)         Console.WriteLine("Calls made: {0}", si.CallCount)       End If         Catch e As Exception       Console.WriteLine("Type: {0}", e.GetType( ).FullName)       Console.WriteLine(e.Message)     End Try         Console.WriteLine("Hit ENTER to continue...")     Console.ReadLine( )       End Sub     End Class 

si looks like a local instance of ServerInfo . However, when the example is run (as shown in Figure 9-3), the statistics for the server machine are returned. The mystery is solved when the source code output from SoapSuds is examined. The namespace, class name , and methods are the same as ServerInfo , but the similarity ends there.

Figure 9-3. Client output
figs/oop_0903.gif
9.4.1.1 Lifetime leases

If the client is run successively, the call count is steadily incremented, proving that the object is in fact a Singleton. However, Singleton objects do not live forever. When created, server-side objects are associated with a lease , which is a countdown to destruction. By default, the lease time is 5 minutes. If a call is not made during this time, the object is garbage collected. Otherwise, when the lease expires , it is renewed, by default, for another 2 minutes.

After running the client, walk away for a while (get a peanut butter and jelly sandwich or something) and come back. When the client is run again, the call count should be back down to 5, indicating that a new instance of the Singleton was activated.

You can change the lease defaults by overriding the InitializeLifetimeService method in the ServerInfo class. To make the Singleton live forever, just return Nothing :

 '  Inherited from MarshalByRefObject  Public Overrides Function InitializeLifetimeService( ) As Object   Return Nothing End Function 

Optionally, the lease time can be changed. Here, the initial lease is set to 10 minutes, with a renewal time of 20 seconds:

 Public Overrides Function InitializeLifetimeService( ) As Object   Dim lease As ILease = CType(MyBase.GetLifetimeService, ILease)   If lease.CurrentState = LeaseState.Initial Then     lease.InitialLeaseTime = TimeSpan.FromMinutes(10)     lease.RenewOnCallTime = TimeSpan.FromSeconds(20)   End If   Return lease End Function 
9.4.1.2 Server and client activation

In Example 9-7, ServerInfo is a server-activated object. This means the server is responsible for the lifetime of the object; it determines when the object is no longer used. Each instance of the client will make calls on the same ServerInfo object.

In contrast, you can specify that a remote object be client-activated , meaning the client will have a say in determining an object's lifetime. To illustrate the concept, two changes must be made.

First, the server configuration file must be changed to allow ServerInfo to be client-activated. An <activated> element that supplies the client-activated type and the assembly in which it is located must be added within <service> , as the following code illustrates:

 'RemoteService.exe.config <service>   <wellknown.../>   <wellknown.../>   <wellknown.../>   <activated type="ObjectServerSpace.ServerInfo,ServerInfo"/> <service> 

The addition of the <activated> tag does not mean that the server will create client-activated types; it means that a client can now specify what kind of activation it wants (client or server).

The client must also be modified to register the type as client-activated, as follows (registration can occur anywhere , but the Main procedure is an obvious possibility):

 Imports System.Runtime.Remoting . . .  RemotingConfiguration.RegisterActivatedClientType( _   GetType(ServerInfo), "tcp://192.168.1.100:1969/RemotingTest")  Dim si As New ServerInfo( ) 

RegisterActivatedType takes the client-activated type and a URL to the type's location. Note the location does not contain the object URI, only the path to the object.

The client-activated object is still created on the server, but the reference held is unique to the client. Running multiple instances of the client shows that the call count returned by the object is 5 for each instance. When the client terminates, the object is garbage collected as if it were local. However, the CAO still has a lease. If the client did not terminate (and if the object is still in scope) and the lease expired , the object would be garbage collected just like an SAO.

9.4.2 Creating an Object Factory

Using SoapSuds to generate proxy assemblies has two shortcomings. First, it doesn't work if the object's constructor takes a parameter (it accepts only default constructors). Second, the server address is hardcoded into the proxy, which makes distribution more difficult. Changes on the server side would require the client to regenerate the proxy.

To get around this problem, you can use an object factory on the server. The idea behind this design pattern is that two interfaces are defined; the first interface (the factory) defines a method that returns the second (the object):

 Public Interface IObjectFactory     Function CreateObject( ) As IRemoteObject End Interface Public Interface IRemoteObject     'Interface methods correspond to remote object End Interface 

The interfaces are kept in a separate assembly that is then shared between the client and the server. The remote object assembly resides on the server and is implemented like this:

 Public Class ObjectFactory : Inherits MarshalByRefObject   Implements IObjectFactory       Public Function CreateObject( ) As IRemoteObject _     Implements IServerInfoFactory.CreateServerInfo     Return New RemoteObject( )   End Function     End Class     Public Class RemoteObject : Inherits MarshalByRefObject   Implements IRemoteObject   '  Methods corresponding to IRemoteObject  End Class 

The server configuration file exposes only the ObjectFactory class (which is implemented as a Singleton), preventing clients from establishing a dependency on RemoteObject . New functionality (or implementation of existing methods) can be added to the remote assembly without affecting existing clients.

9.4.2.1 Factory interfaces

Example 9-8 contains the object factory interfaces for a new version of ServerInfo .

Example 9-8. ServerInfo object factory interfaces
 'vbc /t:library /r:system.dll ServerInterfaces.vb Imports System Imports System.Net     Namespace ServerInterfaces       Public Interface IServerInfoFactory     Function CreateServerInfo( ) As IServerInfo   End Interface       Public Interface IServerInfo     Function GetMachineTime( ) As DateTime     Function GetProcessorUsed( ) As Single     Function GetAvailableMemory( ) As Long     ReadOnly Property IPAddress( ) As IPAddress     ReadOnly Property MachineName( ) As String   End Interface     End Namespace 
9.4.2.2 ServerInfo factory

The ServerInfo class must now be changed in order to implement IServerInfo ; the new version is shown in Example 9-9. In addition to referencing ServerInterfaces.dll , the object factory class must be implemented to return instances of the ServerInfo class.

Example 9-9. ServerInfo and object factory
 'vbc /t:library /r:System.dll  '/r:ServerInterface.dll ServerInfo.vb     Imports System Imports System.Diagnostics Imports System.Net Imports System.Threading         'Private Data Members  Imports ServerInterfaces   Namespace ObjectServerSpace   Public Class ServerInfoFactory : Inherits MarshalByRefObject   Implements IServerInfoFactory   Public Function CreateServerInfo( ) As IServerInfo _   Implements IServerInfoFactory.CreateServerInfo   Dim si As New ServerInfo( )   Return si   End Function   End Class   Public Class ServerInfo : Inherits MarshalByRefObject   Implements IServerInfo  Public Sub New( )       'Init data members     End Sub         'Shared method     Public Function GetMachineTime( ) As DateTime _  Implements IServerInfo.GetMachineTime  'Implementation     End Function         'Get % of process currently in use     Public Function GetProcessorUsed( ) As Single _  Implements IServerInfo.GetProcessorUsed  'Implementation     End Function         'Get MBytes of free memory     Public Function GetAvailableMemory( ) As Long _  Implements IServerInfo.GetAvailableMemory  'Implementation     End Function         Public ReadOnly Property MachineName( ) As String _  Implements IServerInfo.MachineName  'Implementation     End Property         Public ReadOnly Property IPAddress( ) As IPAddress _  Implements IServerInfo.IPAddress  'Implementation     End Property       End Class     End Namespace 

9.4.3 Using the Object Factory

The Windows Service does not need to be recompiled, because it does not directly reference ServerInfo.dll or ServerInterfaces.dll . However, the configuration file must be changed to expose a well-known Singleton object entry for the factory class (see Example 9-10). Generally, if there are entries for the object type returned by the factory in the config file, they can be removed.

Example 9-10. Server configuration using object factory
 <configuration>   <system.runtime.remoting>     <application name="RemotingTest">       <service>  <wellknown mode="Singleton  "  type="ObjectServerSpace.ServerInfoFactory,ServerInfo  "  objectUri="ServerInfoFactory.rem" />  <!--not needed         <wellknown mode="Singleton"                      type="ObjectServerSpace.ServerInfo,ServerInfo"                    objectUri="ServerInfo.rem" />         <wellknown mode="SingleCall"                    type="ObjectServerSpace.ServerInfo,ServerInfo"                    objectUri="ServerInfo2.rem" />-->       </service>     </application>   </system.runtime.remoting> </configuration> 

ServerInfo.dll and ServerInterfaces.dll should be moved to the same directory as the service.

There are also several small changes to the client. First, add a reference to ServerInterfaces.dll . Then, remove the reference to ServerInfoProxy.dll .

Getting an interface is the factory's purpose. However, since interfaces cannot be created, another mechanism has to be used to obtain the remote object, which, unfortunately , means no transparent activation (e.g., creating an instance using Dim like a normal object).

Activator.GetObject can be called to return an interface reference to an existing server-side object. This method can be used in place of the old ServerInfo declaration:

 'Replace this 'Dim si As New ServerInfo( )      'with this  Dim factoryObj As Object = _   Activator.GetObject(GetType(IServerInfoFactory), _  "  tcp://192.168.1.100:1969/RemotingTest/ServerInfoFactory.rem")   Dim factory As IServerInfoFactory = CType(factoryObj, IServerInfoFactory)   Dim si As IServerInfo = factory.CreateServerInfo( )  

The first parameter is the interface typein this case, IServerInfoFactory . The second is the URL of the object as defined in the server config file, including the object URI. The returned Object can be typecast to the factory interface. Note, though, that the proxy does not transmit anything over the network until a method is called. Here, that is during CreateServerInfo .

only for RuBoard


Object-Oriented Programming with Visual Basic. Net
Object-Oriented Programming with Visual Basic .NET
ISBN: 0596001460
EAN: 2147483647
Year: 2001
Pages: 112
Authors: J.P. Hamilton

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