TCP-сервер Indy 10
После долгих поисков я подумал, что Indy TCP-сервер будет лучшим для использования на сервере мгновенных сообщений, над которым я работаю. Единственная проблема, с которой я сейчас сталкиваюсь, - это широковещательная рассылка и пересылка сообщения другому подключенному клиенту, отправка ответного ответа тому же клиенту выглядит нормально и не прерывает активность других клиентов, но для пересылки сообщений другим клиентам известен механизм, о котором я знаю с помощью aContext.locklist
и перебирая список соединений, чтобы найти клиентское соединение, которое должно принимать данные.
Проблема здесь, я думаю, в том, что она замораживает список и не обрабатывает запросы других клиентов, пока не будет вызван список разблокировки. Так не повредит ли это производительности сервера? блокировка списка и итерация между соединениями для пересылки каждого сообщения (поскольку это часто происходит в мессенджере). Есть ли лучший способ сделать это?
Я использую Indy 10 и Delphi 7
Код для трансляции:
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;
Код для пересылки сообщения:
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;
1 ответ
Да, вы должны пройти через Contexts
список для того, чтобы передать сообщение нескольким клиентам. Однако вы не выполняете (и не должны) выполнять фактическую запись из цикла. Во-первых, как вы уже заметили, на производительность сервера может повлиять блокирование списка на некоторое время. Во-вторых, это не потокобезопасно. Если ваш цикл записывает данные в соединение, в то время как другой поток записывает в то же самое соединение в то же время, тогда две записи будут перекрывать друг друга и портить ваши связи с этим клиентом.
Обычно я вместо этого реализую исходящую очередь для каждого клиента, используя либо свойство TIdContext.Data, либо потомка TIdServerContext для хранения фактической очереди. Когда вам нужно отправить данные клиенту за пределами этого клиента OnExecute
событие, вместо этого поместите данные в очередь этого клиента. Этот клиент OnExecute
Затем событие может отправить содержимое очереди клиенту, когда это безопасно.
Например:
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;