After a lot of searching I thought Indy TCP server would be the best to use on Instant messenger server I am working on. The only issue I am facing right now is broadcasting and forwarding message to other connected client, sending back response to the same client seems ok and doesn't hangs up other clients activity, but for forwarding message to other clients the mechanism that I know of is by using the aContext.locklist
, and iterating between the connection list to find the client connection that is to receive the data.
The problem here I think is that it freezes the list and doesn't process other clients requests until the unlocklist is called. So will it not hurt the performance of the server? locking the list and iterating between connections for forwarding each message (as this is what happens very often in a messenger). Is there any better way to do this?
I am using Indy 10 and Delphi 7
Code for broadcast:
Var tmpList: TList;
i: Integer;
Begin
tmpList := IdServer.Contexts.LockList;
For i := 0 to tmpList.Count Do Begin
TIdContext(tmpList[i]).Connection.Socket.WriteLn('Broadcast message');
End;
IdServer.Contexts.UnlockList;
Code for forwarding message:
Var tmpList: TList;
i: Integer;
Begin
tmpList := IdServer.Contexts.LockList;
For i := 0 to tmpList.Count Do Begin
If TIdContext(tmpList[i]).Connection.Socket.Tag = idReceiver Then
TIdContext(tmpList[i]).Connection.Socket.WriteLn('Message');
End;
IdServer.Contexts.UnlockList;
Yes, you have to loop through the Contexts
list in order to broadcast a message to multiple clients. However, you do not (and should not) perform the actual writing from inside the loop. One, as you already noticed, server performance can be affected by keeping the list locked for awhile. Two, it is not thread-safe. If your loop writes data to a connection while another thread is writing to the same connection at the same time, then the two writes will overlap each other and corrupt your communications with that client.
What I typically do is implement a per-client outbound queue instead, using either the TIdContext.Data property or a TIdServerContext descendant to hold the actual queue. When you need to send data to a client from outside of that client's OnExecute
event, put the data in that client's queue instead. That client's OnExecute
event can then send the contents of the queue to the client when it is safe to do so.
For example:
type
TMyContext = class(TIdServerContext)
public
Tag: Integer;
Queue: TIdThreadSafeStringList;
...
constructor Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TThreadList = nil); override;
destructor Destroy; override;
end;
constructor TMyContext.Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TThreadList = nil);
begin
inherited;
Queue := TIdThreadSafeStringList.Create;
end;
destructor TMyContext.Destroy;
begin
Queue.Free;
inherited;
end;
.
procedure TForm1.FormCreate(Sender: TObject);
begin
IdServer.ContextClass := TMyContext;
end;
procedure TForm1.IdServerConnect(AContext: TIdContext);
begin
TMyContext(AContext).Queue.Clear;
TMyContext(AContext).Tag := ...
end;
procedure TForm1.IdServerDisconnect(AContext: TIdContext);
begin
TMyContext(AContext).Queue.Clear;
end;
procedure TForm1.IdServerExecute(AContext: TIdContext);
var
Queue: TStringList;
tmpList: TStringList;
begin
...
tmpList := nil;
try
Queue := TMyContext(AContext).Queue.Lock;
try
if Queue.Count > 0 then
begin
tmpList := TStringList.Create;
tmpList.Assign(Queue);
Queue.Clear;
end;
finally
TMyContext(AContext).Queue.Unlock;
end;
if tmpList <> nil then
AContext.Connection.IOHandler.Write(tmpList);
finally
tmpList.Free;
end;
...
end;
.
var
tmpList: TList;
i: Integer;
begin
tmpList := IdServer.Contexts.LockList;
try
for i := 0 to tmpList.Count-1 do
TMyContext(tmpList[i]).Queue.Add('Broadcast message');
finally
IdServer.Contexts.UnlockList;
end;
end;
.
var
tmpList: TList;
i: Integer;
begin
tmpList := IdServer.Contexts.LockList;
try
for i := 0 to tmpList.Count-1 do
begin
if TMyContext(tmpList[i]).Tag = idReceiver then
TMyContext(tmpList[i]).Queue.Add('Message');
end;
finally
IdServer.Contexts.UnlockList;
end;
end;