JavaScript Editor JavaScript Editor     JavaScript Debugger 



Team LiB
Previous Section Next Section

A Bidirectional Remoting Example

In the One-Way Remoting example, the client always contacts the server. The server can respond to the client through the method return value, but once the method call is finished, the client closes the connection and the server can no longer contact the client. This is not appropriate for a peer-to-peer system, which requires bidirectional communication.

In order to support bidirectional communication, the client must meet three criteria:

Once these criteria are met, there are several choices for the actual method of communication:

The first option—using events—requires the least amount of work. Multiple clients can attach event handlers to the same event, and the server doesn't need to worry about who is being contacted when it fires the event. The only consideration is making sure that the EventArgs object is serializable, so that it can leap across application domain boundaries. However, the event-based approach is less practical because it doesn't allow the flexibility for the server to call a specific client. It can also lead to problems if clients disconnect from the network without unregistering their event handlers properly.

The delegate or interface approaches are more flexible. In both cases, the server is in charge of tracking clients (typically by using some sort of collection object), and removing them from the collection when they can no longer be contacted. The instant-messaging example in the next chapter uses an interface-based approach.

The following example uses a similar, yet slightly different approach: a delegate that both the server and client recognize. This project can be found in the TwoWayRemoting directory with the samples for this chapter. This example uses a Windows client. The server (component host) is unchanged.

The Remote Objects

The first step is to modify the server-side remotable object so that it will attempt to contact the client after a short delay through a callback. It works like this:

  1. The client calls a method in the remote object.

  2. The method sets up a timer and returns.

  3. When the timer ticks, a new message is sent to the client. This requires opening a new connection because the original connection has been closed. This time, the server is acting as a client because it's opening the connection.

Here's the code for our simple example:


Public Delegate Sub ConfirmationCallback(ByVal message As String)

Public Class RemoteObject

    Inherits MarshalByRefObject

    Private WithEvents tmrCallback As New System.Timers.Timer()
    Private Callback As ConfirmationCallback
    Private Message As String

    Public Sub ReceiveMessage(ByVal message As String, _
      ByVal callback As ConfirmationCallback)
        Me.Callback = callback
        Me.Message = "Received message: " & message
        tmrCallback.Interval = 5000
        tmrCallback.Start()
    End Sub

    Private Sub tmrCallback_Elapsed(ByVal sender As System.Object, _
     ByVal e As System.Timers.ElapsedEventArgs) _
     Handles tmrCallback.Elapsed
        tmrCallback.Stop()
        Callback.Invoke(Message)
    End Sub

End Class
Note 

This simple design isn't suitable for a system that experiences multiple calls in close succession because the ConfirmationCallback and Message values will be overwritten with each new call. Don't worry too much about this limitation now—the next two chapters will explore these limitations in detail and resolve them.

The RemoteLibrary project also contains the remotable portion of the client, which is a dedicated listener object. This object is created in the client's application domain for the sole purpose of receiving the callback. It raises a local event so the client application can become notified of the callback. This is a common pattern in peer-to-peer systems with Remoting, and you'll see it again in the next chapter.


Public Class Listener

    Inherits MarshalByRefObject

    Public Event CallbackReceived(ByVal sender As Object, _
      ByVal e As MessageEventArgs)

    Public Sub ConfirmationCallback(ByVal message As String)
        RaiseEvent CallbackReceived(Me, New MessageEventArgs(message))
    End Sub

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

End Class

Public Class MessageEventArgs
    Inherits EventArgs

    Public Message As String

    Public Sub New(ByVal message As String)
        Me.Message = message
    End Sub

End Class

The Configuration Files

The configuration files require only a single change from the previous example. In the simple One-Way Remoting example, the client declared a client-only channel (TCP client), while the server declared a server-only channel (TCP server). To remedy this design, you must configure a bidirectional channel that can create new outgoing connections and receive incoming connections.

The changed line looks like this in the server:

<channel ref="tcp" port="8000" />

The client configuration file requires a similar change. It doesn't define a port number because the .NET Framework will dynamically choose the first available dynamic port.

<channel ref="tcp"/>

The Client

The client is modeled after the One-Way Remoting example. It allows any message to be dispatched to the client. The message is then returned through a callback and handled in a local event, which displays the message box shown in Figure 3-10.


Figure 3-10: Receiving a callback at the client

The client code is encapsulated in a single form, as follows:

Imports System.Runtime.Remoting

Public Class Client
    Inherits System.Windows.Forms.Form

    ' Create the local remotable object that can receive the callback.
    Private ListenerObject As New RemoteLibrary.Listener()

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

    Private Sub Form1_Load(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles MyBase.Load

        RemotingConfiguration.Configure("Client.exe.config")

    End Sub

    Private Sub cmdSend_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles cmdSend.Click

        ' Create the delegate that points to the client object.
        Dim Callback As New RemoteLibrary.ConfirmationCallback( _
          AddressOf ListenerObject.ConfirmationCallback)
        ' Connect the event handler to the local listener class.
        AddHandler ListenerObject.CallbackReceived, _
          AddressOf ListenerObject_CallbackReceived

        ' Send the message to the remote object.
        TestObject.ReceiveMessage(txtMessage.Text, Callback)

    End Sub

    Private Sub ListenerObject_CallbackReceived(ByVal sender As Object, _
      ByVal e As RemoteLibrary.MessageEventArgs)

        MessageBox.Show(e.Message)

    End Sub

End Class
Note 

You might assume that server callbacks and events work using the channel established by the client. However, due to the way that Remoting works, this isn't possible. Instead, the server opens a new channel to deliver its message, which has significant implications if the client is behind a firewall or network address translation (NAT) device. Ingo Rammer has created a proof-of-concept bidirectional TCP channel that solves this issue and allows the server to use the client-created channel (it's available at http://www.dotnetremoting.cc/projects/modules/BidirectionalTcpChannel.asp). Unfortunately, this sample isn't yet ready for a production environment. Your best bet may be to wait for future .NET platform releases, since Microsoft Remoting architects are actively considering this issue.


Team LiB
Previous Section Next Section


JavaScript Editor Free JavaScript Editor     JavaScript Editor