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
SuperTCPChannelChatLibrary
in theServiceBuilder
in RO IDE menu to see its 2 services and 4 events under theEventSync
element. - Notice that the
ChatServerService
is secure, the login procedure ID being controlled by theLoginService
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 theIChatEvents
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 theRegisterEventHandlers
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;