SuperTCP Chat sample (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
SuperTCPChannelChatLibraryin theServiceBuilderin RO IDE menu to see its 2 services and 4 events under theEventSyncelement. - Notice that the
ChatServerServiceis secure, the login procedure ID being controlled by theLoginServiceas 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
RegisterEventClientfunction. 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
ExcludeSenderproperty 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_ClientMainFormclass. Notice that this class supports theIChatEventsinterface that allows it to be an event handler. - The
ROEventReceiverfield (of TROEventReceiver type) is responsible for receiving and dispatching events. It is necessary to call theRegisterEventHandlersmethod 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
SyncCallmethod:
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;