SuperTCP Chat sample (.NET)

Overview

This sample shows how the Super TCP Channel can be used to create a chat application. The Super TCP Channel channel provides a flexible communication channel that uses persistent connections to enable true asynchronous calls and server callbacks.
The sample is compatible with the SuperTCP Chat Sample (Delphi), meaning the .NET client can talk to the Delphi server and vice versa.

Getting started

  • Compile the entire solution.
  • Run the server application.
  • Run several client applications (3 client instances are recommended to demonstrate all implemented features).
  • Test the chat by sending both broadcast and private messages. If the [All Users] item is selected in the users list, the message is visible to all the chat users, otherwise it is visible to the selected user only.

Examine the code

The server

  • Open the ChatServer.rodl to see its 2 services and 3 events under the EventSync element.
  • Notice that the ChatServerService is secure, the login procedure ID being controlled by the LoginService as described in this article
  • Examine the Login service implementation:
public virtual void Login(String nickname)
{
    lock (ChatServerService.UserClientIDList)
    {
        // Making sure the login name is unique
        if (ChatServerService.UserClientIDList.Contains(nickname)) throw new Exception("Nickname already in use");
        if (Session["nick"] != null) 
            ChatServerService.UserClientIDList.Remove(Session["nick"]);

        // Subscribing the current client for events
        SubscribeClientEventSink(typeof(IChatEvents));
        
        // We need to provide the new client with knowledge about all existing users by sending their names one by one
        IChatEvents ev = (IChatEvents)GetEventSink(typeof(IChatEvents), new Guid[] { Session.SessionID });
        foreach (DictionaryEntry et in ChatServerService.UserClientIDList)
            ev.UserLogin(et.Key as string);

        ChatServerService.UserClientIDList.Add(nickname, Session.SessionID);

        // Notifying all existing users about the new user 
        ArrayList lDestinations = new ArrayList();
        foreach (DictionaryEntry et in ChatServerService.UserClientIDList)
            lDestinations.Add(et.Value);

        ev = (IChatEvents)GetEventSink(typeof(IChatEvents), (Guid[])lDestinations.ToArray(typeof(Guid)));
        ev.UserLogin(nickname);
    }
    Session["nick"] = nickname;
}
  • We call the SubscribeClientEventSink function. This function subscribes the current client to the event sink, passed as a parameter. Without calling this function, the client will never receive events.
  • See how we can obtain an event proxy in order to fire events. Note that we pass the recipient ID into this function as new Guid[] { Session.SessionID }. This allows to restrict the recipients list, otherwise the event is sent to all subscribed clients.
IChatEvents ev = (IChatEvents)GetEventSink(typeof(IChatEvents), new Guid[] { Session.SessionID });
  • The implementation of the Talk and TalkPrivate functions in ChatService_impl.cs use a similar approach for sending events. These functions only differ in that the TalkPrivate implementation first defines the recipient and then fires the event only for that client.
public virtual void TalkPrivate(String nickname, String message)
{
    Guid cGuid;
    lock (userClientIDList)
    {
        if (userClientIDList.Contains(nickname))
            cGuid = (Guid)userClientIDList[nickname];
        else
            throw new Exception("Invalid user: " + nickname);
    }
    IChatEvents ev = (IChatEvents)GetEventSink(typeof(IChatEvents), new Guid[] { cGuid });
    ev.Message(Session["nick"] as String, nickname, message);
}

public virtual void Talk(String message)
{
    IChatEvents ev = (IChatEvents)GetEventSink(typeof(IChatEvents));
    ev.Message(Session["nick"] as String, "", message);
}

The client

  • Examine frmMain class. Notice that this class supports the IChatEvents interface that allows it to be an event handler.
  • The events field (of EventReceiver type) is responsible for the receiving and dispatching of events. It is necessary to call the RegisterEventHandler method in order to start receiving events and dispatch them to the appropriate handler.
public class frmMain : System.Windows.Forms.Form, IChatEvents
{
  // ...
  private void Form1_Load(object sender, System.EventArgs e)
  {
    // ...
    events.RegisterEventHandler(this, typeof(IChatEvents));
  }
}
  • Note that all events are fired on a background thread. To allow GUI elements access, the Form.Invoke must be used.

Concepts Covered