SuperTCP Chat (Delphi)

Overview

This sample shows how the Super TCP Channel can be used to create a chat application. The Super TCP 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 (.NET), this means the Delphi client can talk to the .NET server and vice versa.

Getting started

  • Compile both projects.
  • Run the server application.
  • Run several client applications (3 client instances are recommended to demonstrate all implemented features).
  • Each client instance will provide a default login user name, but you can modify these as needed (there is no verification provided in this simple sample).
  • 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 chat users, otherwise it is visible to the selected user only.

Examine the code

The Server

  • Open the SuperTCPChannelChatLibrary in the ServiceBuilder in RO IDE menu to see its 2 services and 4 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:
procedure TLoginService.Login(const Nickname: UnicodeString);
var
  ev: IChatEvents_Writer;
  i: Integer;
  s: UnicodeString;
begin
  UserClientIDListCs.Acquire;
  try
    if UserClientIDList.IndexOfName(Nickname) <> -1 then raise Exception.Create('Nickname already in use');
    s := VarToWideStr(Session['nick']);
    if s <> '' then
      UserClientIDList.Delete(UserClientIDList.IndexOfName(s));

    CreateSession;
    RegisterEventClient((GuidToString(Session.SessionID)), EID_ChatEvents);
    UserClientIDList.Add(Nickname + '=' + GUIDToString(Session.SessionID));

    // inform other connected users about the current one
    ev := (EventRepository as IChatEvents_Writer);
    ev.ExcludeSender := True;
    for i := 0 to UserClientIDList.Count - 1 do
      ev.SessionList.Add(Copy(UserClientIDList[i], pos('=', UserClientIDList[i])+1, MaxInt));
    ev.UserLogin(Session.SessionID, Nickname);
  finally
    UserClientIDListCs.Release;
  end;
  Session['nick'] := Nickname;
end;
procedure TLoginService.Login(const Nickname: UnicodeString);
var
  ev: IROEventWriter<IChatEvents>;
  i: Integer;
  s: UnicodeString;
begin
  UserClientIDListCs.Acquire;
  try
    if UserClientIDList.IndexOfName(Nickname) <> -1 then raise Exception.Create('Nickname already in use');
    s := VarToWideStr(Session['nick']);
    if s <> '' then
      UserClientIDList.Delete(UserClientIDList.IndexOfName(s));

    CreateSession;
    RegisterEventClient(GuidToString(Session.SessionID), 'ChatEvents');

    UserClientIDList.Add(Nickname + '=' + GUIDToString(Session.SessionID));
    // inform other connected users about the current one
    ev := (EventRepository.GetWriter<IChatEvents>(Session.SessionID));
    ev.ExcludeSender := True;
    for i := 0 to UserClientIDList.Count - 1 do
      ev.SessionList.Add(Copy(UserClientIDList[i], pos('=', UserClientIDList[i])+1, MaxInt));
    ev.Event.UserLogin(Nickname);
  finally
    UserClientIDListCs.Release;
  end;
  Session['nick'] := Nickname;
end;
  • Note that we call the RegisterEventClient function. This function subscribes the current client to the event sink passed there as a parameter.
  • See how we obtain an event proxy in order to fire events. The ExcludeSender property allows to skip sending the event to the current session:
    ev := (EventRepository as IChatEvents_Writer);
    ev.ExcludeSender := False;
    ev.SessionList.Add(GuidToString(aClientID));

      // inform current user about other connected users
      for i := 0 to UserClientIDList.Count - 1 do
        ev.UserLogin(aClientID, UserClientIDList.Names[i]);
    ev := (EventRepository.GetWriter<IChatEvents>(aClientID));
    ev.ExcludeSender := False;
    ev.SessionList.Add(GuidToString(aClientID));

      // inform current user about other connected users
      for i := 0 to UserClientIDList.Count - 1 do
        ev.Event.UserLogin(UserClientIDList.Names[i]);

The Client

  • Examine TSuperTCPChannelChat_ClientMainForm class. Notice that this class supports the IChatEvents interface that allows it to be an event handler.
  • The ROEventReceiver field (of TROEventReceiver type) is responsible for receiving and dispatching events. It is necessary to call the RegisterEventHandlers method and activate the receiver in order to start receiving events and dispatch them to the appropriate handler.
procedure TSuperTCPChannelChat_ClientMainForm.FormCreate(Sender: TObject);
begin
  // ...
  ROEventReceiver.RegisterEventHandlers([EID_ChatEvents], [Self]);
  ROEventReceiver.Activate;
  // ...
end;
  • Note that all events are fired on a background thread. To allow GUI elements access, we have to use Windows messages processed by the SyncCall method:
type
  TSuperTCPChannelChat_ClientMainForm = class(TForm, IChatEvents)
  // ...
  public
    // ...
    procedure UserLogin(const Nickname: UnicodeString);
    // ...
    procedure SyncCall(var Msg: TMessage); message WM_USER;
  end;
  
procedure TSuperTCPChannelChat_ClientMainForm.UserLogin(const Nickname: UnicodeString);
var
  Data: TStringArray;
begin
  SetLength(Data, 1);
  Data[0] := Nickname;
  SendMessage(Handle, WM_USER, Longint(stUserLogin), Longint(Data));
end;

procedure TSuperTCPChannelChat_ClientMainForm.SyncCall(var Msg: TMessage);
var
  i: Integer;
  lSyncType: TSyncType;
  lData: TStringArray;
begin
  lSyncType := TSyncType(Msg.WParam);
  lData := TStringArray(Msg.LParam);
  // ...
end;

Concepts Covered