JavaScript Editor JavaScript Editor     JavaScript Debugger 



Team LiB
Previous Section Next Section

The Coordination Server

Now that we've defined the basic building blocks for the Talk .NET system, it's time to move ahead and build the server. The TalkServer application has the task of tracking clients and routing messages from one user to another. The core of the application is implemented in the remotable ServerProcess class, which is provided to clients as a Singleton object. A separate module, called Startup, is used to start the TalkServer application. It initializes the Remoting configuration settings, creates and initializes an instance of the FormTraceListener, and displays the trace form modally. When the trace form is closed, the application ends, and the ServerProcess object is destroyed.

The startup code is shown here:

Imports System.Runtime.Remoting

Public Module Startup

    Public Sub Main()
        ' Create the server-side form (which displays diagnostic information).
        ' This form is implemented as a diagnostic logger.
        Dim frmLog As New TraceComponent.FormTraceListener()
        Trace.Listeners.Add(frmLog)

        ' Configure the connection and register the well-known object
        ' (ServerProcess), which will accept client requests.
        RemotingConfiguration.Configure("TalkServer.exe.config")
        ' From this point on, messages can be received by the ServerProcess
        ' object. The object will be created for the first request,
        ' although you could create it explicitly if desired.

        ' Show the trace listener form. By using ShowDialog(), we set up a
        ' message loop on this thread. The application will automatically end
        ' when the form is closed.
        Dim frm As Form = frmLog.TraceForm
        frm.Text = "Talk .NET Server (Trace Display)"
        frm.ShowDialog()
    End Sub

End Module

When you start the server, the ServerProcess Singleton object isn't created. Instead, it's created the first time a client invokes one of its methods. This will typically mean that the first application request will experience a slight delay, while the Singleton object is created.

The server configuration file is shown here. It includes three lines that are required if you want to run the Talk .NET applications under .NET 1.1 (the version of .NET included with Visual Studio .NET 2003). These lines enable full serialization, which allows the TalkServer to use the ITalkClient reference. If you are using .NET 1.0, these lines must remain commented out, because they will not be recognized. .NET 1.0 uses a slightly looser security model and allows full serialization support by default.

<configuration>
   <system.runtime.remoting>
      <application name="TalkNET">
         <service>
            <wellknown
               mode="Singleton"
               type="TalkServer.ServerProcess, TalkServer"
               objectUri="TalkServer" />
         </service>
         <channels>
            <channel port="8000" ref="tcp" >
               <!-- If you are using .NET 1.1, uncomment the lines below. -->
               <!--
               <serverProviders>
                   <formatter ref="binary" typeFilterLevel="Full" />
               </serverProviders>
               -->
            </channel>
         </channels>
      </application>
   </system.runtime.remoting>
</configuration>

Most of the code for the ServerProcess class is contained in the methods implemented from the ITalkServer interface. The basic outline is shown here:

Public Class ServerProcess
    Inherits MarshalByRefObject
    Implements ITalkServer

    ' Tracks all the user aliases, and the "network pointer" needed
    ' to communicate with them.
    Private ActiveUsers As New Hashtable()

    Public Sub AddUser(ByVal [alias] As String, ByVal client As ITalkClient) _
      Implements TalkComponent.ITalkServer.AddUser
        ' (Code omitted.)
    End Sub

    Public Sub RemoveUser(ByVal [alias] As String) _
      Implements TalkComponent.ITalkServer.RemoveUser
        ' (Code omitted.)
    End Sub

    Public Function GetUsers() As System.Collections.ICollection _
      Implements TalkComponent.ITalkServer.GetUsers
        ' (Code omitted.)
    End Function

    <System.Runtime.Remoting.Messaging.OneWay()> _
    Public Sub SendMessage(ByVal senderAlias As String, _
      ByVal recipientAlias As String, ByVal message As String) _
      Implements TalkComponent.ITalkServer.SendMessage
        ' (Code omitted.)
    End Sub

End Class

You'll see each method in more detail in the next few sections.

Tracking Clients

The Talk .NET server tracks clients using a Hashtable collection. The Hashtable provides several benefits compared to arrays or other types of collections:

  • The Hashtable is a key/value collection (unlike some collections, which do not require keys). This allows you to associate two pieces of information: the user name and a network reference to the client.

  • The Hashtable is optimized for quick key-based lookup. This is ideal, because users send messages based on the user's name. The server can speedily retrieve the client's location information.

  • The Hashtable allows easy synchronization for thread-safe programming. We'll look at these features in the next chapter.

The collection stores ITalkClient references, indexed by user name. Technically, the ITalkClient reference really represents an instance of the System.Runtime.Remoting.ObjRef class. This class is a kind of network pointer—it contains all the information needed to generate a proxy object to communicate with the client, including the client channel, the object type, and the computer name. This ObjRef can be passed around the network, thus allowing any other user to locate and communicate with the client.

Following are the three collection-related methods that manage user registration. They're provided by the server.

Public Sub AddUser(ByVal [alias] As String, ByVal client As ITalkClient) _
  Implements TalkComponent.ITalkServer.AddUser
    Trace.Write("Added user '" & [alias] & "'")
    ActiveUsers([alias]) = client
End Sub

Public Sub RemoveUser(ByVal [alias] As String) _
  Implements TalkComponent.ITalkServer.RemoveUser
    Trace.Write("Removed user '" & [alias] & "'")
    ActiveUsers.Remove([alias])
End Sub

Public Function GetUsers() As System.Collections.ICollection _
  Implements TalkComponent.ITalkServer.GetUsers
    Return ActiveUsers.Keys
End Function

The AddUser() method doesn't check for duplicates. If the specified user name doesn't exist, a new entry is created. Otherwise, any entry with the same key is overwritten. The next chapter introduces some other ways to handle this behavior, but in a production application, you would probably want to authenticate users against a database with password information. This allows you to ensure that each user has a unique user name. If a user were to log in twice in a row, only the most recent connection information would be retained.

Note that only one part of the collection is returned to the client through the GetUsers() method: the user names. This prevents a malicious client from using the connection information to launch attacks against the peers on the system. Of course, this approach isn't possible in a decentralized peer-to-peer situation (wherein peers need to interact directly), but in this case, it's a realistic level of protection to add.

Sending Messages

The process of sending a message requires slightly more work. The server performs most of the heavy lifting in the SendMessage() method, which looks up the appropriate client and invokes its ReceiveMessage() method to deliver the message. If the recipient cannot be found (probably because the client has recently disconnected from the network), an error message is sent to the message sender by invoking its ReceiveMessage() method. If neither client can be found, the problem is harmlessly ignored.

Public Sub SendMessage(ByVal senderAlias As String, _
  ByVal recipientAlias As String, ByVal message As String) _
    Implements TalkComponent.ITalkServer.SendMessage

      ' Deliver the message.
      Dim Recipient As ITalkClient
      If ActiveUsers.ContainsKey(recipientAlias) Then
          Trace.Write("Recipient '" & recipientAlias & "' found")
          Recipient = CType(ActiveUsers(recipientAlias), ITalkClient)
      Else
          ' User wasn't found. Try to find the sender.
          If ActiveUsers.ContainsKey(senderAlias) Then
              Trace.Write("Recipient '" & recipientAlias & "' not found")
              Recipient = CType(ActiveUsers(senderAlias), ITalkClient)
              message = "'" & message & "' could not be delivered."
              senderAlias = "Talk .NET"
        Else
            Trace.Write("Recipient '" & recipientAlias & "' and sender '" & _
                         senderAlias & "' not found")
            ' Both sender and recipient weren't found.
            ' Ignore this message.
        End If
    End If

    Trace.Write("Delivering message to '" & recipientAlias & "' from '" & _
                 senderAlias & "'")
    If Not Recipient Is Nothing Then
        Dim callback As New ReceiveMessageCallback( _
          AddressOf Recipient.ReceiveMessage)
        callback.BeginInvoke(message, senderAlias, Nothing, Nothing)
    End If

End Sub

You'll see that the server doesn't directly call the ClientProcess.ReceiveMessage() method because this would stall the thread and prevent it from continuing other tasks. Instead, it makes the call on a new thread by using the BeginInvoke() method provided by all delegates. It's possible to use a server-side callback to determine when this call completes, but in this case, it's not necessary.

This completes the basic framework for the TalkServer application. The next step is to build a client that can work with the server to send instant messages around the network.


Team LiB
Previous Section Next Section


JavaScript Editor Free JavaScript Editor     JavaScript Editor


R7