JavaScript Editor JavaScript Editor     JavaScript Debugger 



Team LiB
Previous Section Next Section

A Basic Remoting Example

To make all this clear, we'll consider a stripped-down Remoting example. To start, we'll create two console applications: a client and a server. The client will send a message to the server, which will display it in a console window.

Tip 

There's a reason we're beginning with a simple console example rather than a Windows Form application. The Console object is guaranteed to be thread-safe, meaning that there's no possibility for error if multiple clients call the same remote object at once.With a Windows Form, life isn't always as easy.

We'll also need to create a class library project that contains the remote object. That way, you can easily add a reference to the remote object from the client. Without this extra step, the client would lack the metadata that tells the CLR how it should verify method invocations, and communication wouldn't be possible.

If you coded the remote object directly in the server application, you would face the same problem in a different way. Because the server application is an executable assembly, you can't add a reference to it in your client. It's possible to circumvent this restriction using interfaces, as you'll see in the next chapter. Without them you must separate the remotable parts of an application into a separate assembly.

The Remote Object

You can begin by creating the remote object for the server. If you're using Visual Studio .NET, you'll begin by creating a new class library (DLL) project. This example reuses the RemoteObject class presented earlier, but replaces the custom Message object with a simple string for simplicity's sake.

Public Class RemoteObject
    Inherits MarshalByRefObject

    Public Sub ReceiveMessage(ByVal message As String)
        Console.WriteLine("Received message: " & message)
    End Sub
End Class

Because this object will be created in the server's application domain, it can use the Console object to display a message. A similar interaction would be possible if the server were a Windows Form application, but you would need a little extra threading code to prevent glitches when interacting with user-interface controls. The Console object, however, is always guaranteed to be thread-safe.

The Server

The server (or component host) is the main console application. If you're using Visual Studio .NET, you'll begin by creating a new console application. This application registers the Remoting settings defined in the Server.exe.config file, displays a message, and waits for the user to press Enter, at which point it will end.


Imports System.Runtime.Remoting

Public Module ServerApplication

    Public Sub Main()

        Console.WriteLine("Configuring remotable objects....")
        RemotingConfiguration.Configure("Server.exe.config")

        Console.WriteLine("Waiting for a request.")
        Console.WriteLine("Press any key to exit the application.")

        ' The CLR will monitor for requests as long as this application
        ' is running. When the user presses Enter, it will end.
        Console.ReadLine()

    End Sub

End Module
Tip 

The name Server.exe.config is used because the application executable file is Server.exe. According to .NET conventions, settings for an executable application should always be stored in a configuration file that has the same name as the executable, and adds the .config extension. In some cases, .NET will read and apply these settings automatically, although this is not the case for Remoting settings (and so it's technically possible to use any file name you like).

The Server Configuration File

The server configuration file defines the object it will expose and the channel it will open for client requests. Remember, if you're using Visual Studio .NET, you should always give the application configuration file the name app.config. When Visual Studio .NET compiles your project, it will copy the app.config file to the appropriate directory, and give it the correct name.

Here's a sample configuration file for a component host:


1  <configuration>
2     <system.runtime.remoting>
3        <application name="Server">
4           <service>
5              <wellknown mode="Singleton"
6                          type="RemoteLibrary.RemoteObject, RemoteLibrary"
7                          objectUri="RemoteObject" />
8           </service>
9           <channels>
10            <channel ref="tcp server" port="8000" />
11          </channels>
12       </application>
13    </system.runtime.remoting>
14 </configuration>

It contains several important pieces of information:

  • The application is assigned the name "Server" (line 3).

  • The Singleton mode is used (line 5), ensuring that only a single instance of the object will be created on the server.

  • The remotable object has the fully qualified class name of RemoteLibrary.RemoteObject (first part of line 6). The remotable object can be found in the DLL assembly RemoteLibrary.dll (second part of line 6). Both of these pieces of information must match exactly. Note that the assembly name does not include the extension .dll. This is simply a matter of convention.

  • The remoteable object is given the URI "RemoteObject" (line 7). Together with the computer name and port number, this specifies the URL the client needs to use to access the object.

  • A TCP/IP server channel is defined on port 8000 (line 10). This channel can receive messages and respond to them. By default, this channel will use binary encoding for all messages, although you'll see how to tweak this later on.

In this case, the port number isn't terribly important. The next chapter discusses port numbers in more detail.

Note 

Ports are generally divided into three groups: well-known ports (0–1023), registered ports (1024–49151), and dynamic ports (49152–65535). Historically, well-known ports have been used for server-based applications such as web servers (80), FTP (20), and POP3 mail transfer (110). In your application, you would probably do best to use a registered or dynamic port that's not frequently used. These are less likely to cause a conflict (although more likely to be blocked by a firewall). For example, 6346 is most commonly used by Gnutella. For a list of frequently registered ports, refer to the C:\{WinDir]\System32\Drivers\Etc\Services file, or the http://www.iana.org/assignments/port-numbers site.

The Client

The client is also created as a console application. It performs a continuous loop asking the user for an input string, and only exits if the user enters the keyword "exit." Every time a message is entered, the client sends this object to the remote application domain simply by calling the remote object's ReceiveMessage() method.

Imports System.Runtime.Remoting

Public Module ClientApplication

    Public Sub Main()

        Console.WriteLine("Configuring remote objects....")
        RemotingConfiguration.Configure("Client.exe.config")

        Do
            Console.WriteLine()
            Console.WriteLine("Enter the message you would like to send.")
            Console.WriteLine("Or type 'exit' to exit the application.")
            Console.Write(">")
            Dim Message As String = Console.ReadLine()
            If Message.ToUpper() = "EXIT" Then Exit Do

            ' Create the remote object.
            Dim TestObject As New RemoteLibrary.RemoteObject()

            ' Send the message to the remote object.
            TestObject.ReceiveMessage(Message)
            Console.WriteLine()
            Console.WriteLine("Message sent.")
        Loop

    End Sub

End Module

In order for the client to be able to use the RemoteObject class, you must add a reference to the class library assembly that contains this type.

When the client creates the TestObject, it's actually creating a proxy class that mimics the remote object. When the client calls TestObject.ReceiveMessage(), the TestObject proxy class makes a call over the network and transmits the information needed to the real remote object instance.

This proxy layer has a couple of other side effects. For example, when the client creates the proxy class, it doesn't create the server-side object. If the remote object doesn't yet exist, it will be created the first time the ReceiveMessage() method is called. Similarly, the .NET Framework will not destroy the object if it goes out of scope or if the proxy reference is set to Nothing. Instead, the remote object behaves according to a specific lifetime lease. In this case, because the lifetime lease has not been explicitly configured, the default settings will prevail. That means that the object will have an initial lifetime of about five minutes, after which it will be destroyed if it experiences two minutes of inactivity. Thus, if the client sends multiple messages within a two-minute time period, it will reuse the same remote object instance, even though the proxy class is re-created with each iteration of the loop. We'll look at more advanced leasing options later in this chapter.

The Client Configuration File

The client configuration file loosely resembles the server configuration. It defines what channel to use to send communication, what URL to contact, and what object to communicate with.

1  <configuration>
2     <system.runtime.remoting>
3        <application name="Client">
4           <client>
5              <wellknown url="tcp://localhost:8000/RemoteObject"
6                       type="RemoteLibrary.RemoteObject, RemoteLibrary"/>
7           </client>
8           <channels>
9              <channel ref="tcp client"/>
10          </channels>
11       </application>
12    </system.runtime.remoting>
13 </configuration>

The file contains several pieces of information:

  • The application is assigned the name "Client" (line 3). This designation has no particular significance because the client will not be contacted by URL.

  • The URL for the remote object is specified (line 5). This URL consists of the computer name, port, and object URI. (In this example, the machine name is identified only as "localhost," which is a loopback alias that always points to the current computer.) The full object URL takes the following form:

        <client url="[Protocol]://[MachineName]:[Port]/[ObjectURI]">
    
  • The remotable object has the fully qualified class name of RemoteLibrary.RemoteObject (first part of line 6). The remotable object can be found in the DLL assembly RemoteLibrary.dll (second part of line 6). Both of these pieces of information must match exactly. Note that the assembly name does not include the extension .dll. This is simply a matter of convention.

  • A TCP/IP client channel is defined without a port number (line 9). This means that .NET will dynamically choose the most suitable port to open the connection on the client. This port does not need to be hard-coded, because no other application is trying to contact this client by URL.

The Application in Action

All the parts of this application are provided with the online samples for this chapter, in the OneWayRemoting directory. To test this solution, you can configure Visual Studio .NET to launch both the client and the server at the same time (just make sure the server is initialized before you enter any messages in the client). To do so, simply set Visual Studio .NET to launch multiple projects, as shown in Figure 3-7.

Click To expand
Figure 3-7: Launching multiple projects for debugging

Next, type a message into the client (Figure 3-8). After a brief delay, while the server-side object is created, the message will appear in the server's console window (Figure 3-9).

Click To expand
Figure 3-8: Entering a message in the client
Click To expand
Figure 3-9: Receiving the message with the remote object

To confirm that the application is working as expected, you can perform a simple test. Modify the client by omitting the RemotingConfiguration.Configure() method call. Now, when you create the RemoteObject, a local object will be instantiated in the client's application domain. When you call the ReceiveMessage() method, the message will be processed in the client's application domain and will be output in the client's console window.

Clearly, this is a trivial example of Remoting at work. But it gives you an important overview of the following key fundamentals:

  • Remoting is based on objects. While an experienced network programmer will refer to "sending a message" across the network (the terminology often used in this book), in Remoting you send this message by invoking a method on a remote object.

  • Remote objects are not necessarily tied to the server that hosts them. In fact, if you do want to allow communication between the remote object and the hosting "container" (as you probably will in a peer-to-peer application), you'll need to use synchronization code because these parts of the application execute on different threads.

  • Remoting uses configuration files to register available objects and define the channel that will be used. That makes it easy to change settings without recompiling. These settings are passed to the Remoting infrastructure provided by the CLR, which automatically creates channels, opens ports, and provides requested objects as needed without requiring any additional code.

Remote Object Lifetime

One of the problems with the previous generation of distributed object technology (for example, COM/DCOM) is the fact that it lacked a reliable way to handle object lifetime. In DCOM, the solution was to use keep-alive pinging messages, which increased network traffic unnecessarily and allowed greedy clients to keep objects alive indefinitely, wasting server memory. Remoting introduces a new lease-based system for handling object lifetimes that allows them to be automatically destroyed after a fixed amount of time (or a fixed period of idleness). You can set lifetime properties in several ways:

  • The application domain can configure default settings for all the objects it creates by using the <lifetime> configuration section in its configuration file.

  • The client using the object can manually retrieve the remote object's lease from the GetLifetimeService() method (which all MarshalByRefObject instances inherit). The client can then modify the lease settings.

  • The object itself can override its InitializeLifetimeService() method (which all MarshalByRefObject instances inherit) and add code to ignore lease settings and configure its own lease properties.

  • You can implement a custom lease sponsor that monitors an object and determines if its lifetime should be extended when it expires.

The lifetime leasing system plays a minor role in peer-to-peer programming, in which you typically want an object's network interface to remain as long as the application domain exists. For that reason, you'll usually want to configure an infinite lease time. The easiest way is to simply override the InitializeLifetimeService() to return a null reference:

' Ensures that this object will not be prematurely released.
Public Overrides Function InitializeLifetimeService() As Object
    Return Nothing
End Function

This works because it specifies a null reference in the place of the lease object. Alternatively, you could retrieve the ILease object and modify it to apply new settings, as shown here:

Public Overrides Function InitializeLifetimeService() As Object
    Dim Lease As ILease = MyBase.InitializeLifetimeService()
    ' Lease can only be configured if it's in an initial state.
    If Lease.CurrentState = LeaseState.Initial Then
        Lease.InitialLeaseTime = TimeSpan.FromMinutes(10)
        Lease.RenewOnCallTime = TimeSpan.FromMinutes(5)
    End If

    Return Lease

End Function

This will set a lease-lifetime policy in which the object lives at least ten minutes, and is removed after not being used for a five-minute period. As discussed previously, this technique would rarely be used in a peer-to-peer application.


Team LiB
Previous Section Next Section


JavaScript Editor Free JavaScript Editor     JavaScript Editor