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;