No need to store the IP, or whatever low-level implementation parameter (by the way, the IP would not be able to identify a connection in an unique manner: several clients may share the same IP).
In the mORMot framework, asynchronous callbacks are implemented via interface parameters. On the server side, each instance of this parameter would be in fact a "fake" class instance, linked to the input connection, able to call back the client via its interface methods.
This is a pretty straightforward way of implementing callbacks - in fact this is a good way of implementing SOLID callbacks on the server side, and the mORMot framework allows to publish this mechanism in a client/server way, by using WebSockets.
So you first define the callback interface, and the service interface:
IChatCallback = interface(IInvokable)
['{EA7EFE51-3EBA-4047-A356-253374518D1D}']
procedure BlaBla(const pseudo, msg: string);
end;
IChatService = interface(IInvokable)
['{C92DCBEA-C680-40BD-8D9C-3E6F2ED9C9CF}']
procedure Join(const pseudo: string; const callback: IChatCallback);
procedure BlaBla(const pseudo,msg: string);
procedure CallbackReleased(const callback: IInvokable);
end;
Then, on the server side, each call to IChatService.Join()
would subscribe to an internal list of connections:
TChatService = class(TInterfacedObject,IChatService)
protected
fConnected: array of IChatCallback;
public
procedure Join(const pseudo: string; const callback: IChatCallback);
procedure BlaBla(const pseudo,msg: string);
procedure CallbackReleased(const callback: IInvokable);
end;
procedure TChatService.Join(const pseudo: string;
const callback: IChatCallback);
begin
InterfaceArrayAdd(fConnected,callback);
end;
Then a remote call to the IChatService.BlaBla()
method should be broadcasted to all connected clients, just by calling the IChatCallback.BlaBla()
method:
procedure TChatService.BlaBla(const pseudo,msg: string);
var i: integer;
begin
for i := 0 to high(fConnected) do
fConnected[i].BlaBla(pseudo,msg);
end;
Note that all the loop calls to IChatCallback.BlaBla()
would be made via WebSockets, in an asynchronous and non blocking way, so that even in case of huge number of clients, the IChatService.BlaBla()
method won't block. In case of high numbers of messages, the framework is even able to gather push notification messages into a single message, to reduce the resource use.
The following method will be called by the server, when a client callback instance is released (either explicitly, or if the connection is broken), so could be used to unsubscribe to the notification:
procedure TChatService.CallbackReleased(const callback: IInvokable);
begin
InterfaceArrayDelete(fConnected,callback);
end;
On the server side, you define the service as such:
Server.ServiceDefine(TChatService,[IChatService],sicShared).
SetOptions([],[optExecLockedPerInterface]);
Here, the optExecLockedPerInterface
option has been set, so that all method calls would be made thread-safe, so that concurrent access to the internal fConnected[]
list would be safe.
On the client side, you implement the IChatCallback
callback interface:
type
TChatCallback = class(TInterfacedCallback,IChatCallback)
protected
procedure BlaBla(const pseudo, msg: string);
end;
procedure TChatCallback.BlaBla(const pseudo, msg: string);
begin
writeln(#13'@',pseudo,' ',msg);
end;
Then you subscribe to your remote service as such:
var Service: IChatService;
callback: IChatCallback;
...
Client.ServiceDefine([IChatService],sicShared);
if not Client.Services.Resolve(IChatService,Service) then
raise EServiceException.Create('Service IChatService unavailable');
...
callback := TChatCallback.Create(Client,IChatCallback);
Service.Join(pseudo,callback);
...
try
repeat
TextColor(ccLightGray);
readln(msg);
if msg='' then
break;
Service.BlaBla(pseudo,msg);
until false;
finally
callback := nil;
Service := nil; // release the service local instance BEFORE Client.Free
end;
If you compare with existing client/server SOA solutions (in Delphi, Java, C# or even in Go or other frameworks), this interface-based callback mechanism sounds pretty unique and easy to work with. Of course, this work from Delphi 6 up to XE7, and also under Linux thanks to FPC or CrossKylix.
I've uploaded the full sample source code in our repository, both the server side application and the client side.