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;
Другие вопросы по тегам