Indy 10 TCP сервер

После долгих поисков я подумал, что лучше всего использовать Indy TCP server на сервере обмена мгновенными сообщениями, над которым я работаю. Единственная проблема, с которой я сейчас сталкиваюсь, - это широковещательная передача и пересылка сообщения другому подключенному клиенту, отправка ответа тому же клиенту выглядит нормально и не вешает активность других клиентов, но для пересылки сообщения другим клиентам - механизм, о котором я знаю заключается в использовании 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;

person Junaid Noor    schedule 31.12.2012    source источник
comment
Вы ожидаете, что мы психологически отладим ваш код?   -  person Barmar    schedule 31.12.2012
comment
Извините, я на самом деле думал об этом как об обычной проблеме, а не о проблеме с кодом.   -  person Junaid Noor    schedule 31.12.2012


Ответы (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;
person Remy Lebeau    schedule 31.12.2012
comment
Спасибо за предупреждение о безопасности потоков, я буду использовать его таким образом, но возникает еще один вопрос, и исходный вопрос все еще остается без ответа. Поэтому для написания вне процедуры serverexecute я должен реализовать очередь, а для операций с помощью процедуры serverexecute, например прямого ответа клиенту-отправителю, я могу просто выполнить задачу после проверки очереди, которую вы выполнили на сервере? или я должен написать и для этого в очередь? И вопрос в том, что, однако, кажется нормальным перебрать все соединения для трансляции, но ... - person Junaid Noor; 01.01.2013
comment
... но для простой пересылки сообщений от одного клиента к другому или передачи файлов повторение каждый раз было бы глупо? Могу ли я объявить класс для каждого клиента с его IP-адресом и переменной TIdContext и назначить ему контекст каждого клиента? это было бы безопасно? - person Junaid Noor; 01.01.2013
comment
Мой ответ касается вашего исходного вопроса - лучший способ сделать широковещательную / пересылку с учетом производительности и безопасности потоков - использовать очередь для каждого клиента. Будь то широковещательная передача или пересылка, вам все равно придется пройтись по списку Contexts, чтобы найти клиента (ов), которому вы хотите отправить (если вы не реализуете свои собственные потокобезопасные поисковые запросы, такие как группировка нескольких указателей TIdContext вместе). Очередь помогает свести к минимуму количество времени, в течение которого список Contexts необходимо заблокировать, переложив основную часть работы на событие OnExecute каждого клиента ... - person Remy Lebeau; 01.01.2013
comment
... OnExecute может делать со своим TIdContext клиентом все, что ему нужно, включая чтение ответов на сообщения в очереди после того, как они были отправлены в соединение. - person Remy Lebeau; 01.01.2013
comment
Еще один глупый вопрос, как мне добавить в очередь данные другого типа? вроде TIdBytes или что-то в этом роде ... Спасибо - person Junaid Noor; 16.01.2013
comment
Использование TIdThreadSafeStringList было просто примером. Вы можете использовать для очереди все, что захотите, при условии, что она будет защищена потокобезопасной блокировкой при каждом доступе к ней. Например, TThreadList, содержащий указатели на записи, где записи содержат ваши фактические данные. - person Remy Lebeau; 17.01.2013
comment
Да, я так и сделал, спасибо, что прояснили ситуацию :). Очень признателен. - person Junaid Noor; 17.01.2013
comment
Так что вам нужно зацикливаться, чтобы добраться до всех ... Это выглядит жутко. Не лучше ли использовать UDP, который проще и быстрее? - person Please_Dont_Bully_Me_SO_Lords; 06.08.2013
comment
TCP не поддерживает широковещательную рассылку, поэтому вам нужно перебрать все ваши активные соединения, если вы хотите отправить одни и те же данные всем. Нет, UDP не обязательно лучший выбор. Конечно, UDP может упростить ваш код (одна отправка может транслироваться нескольким получателям), но UDP не гарантирует, что данные вообще дойдут до места назначения, и даже если это так, UDP не гарантирует, что данные будут доставлены в такой же заказ он был отправлен. Это быстрее только потому, что UDP не подтверждает данные, как это делает TCP. Быстрее не всегда значит лучше. - person Remy Lebeau; 06.08.2013
comment
@RemyLebeau Я закончил свое дело, я воспользуюсь ими обоими! ;-) stackoverflow.com/questions/18069948/ Посмотрите, согласны ли вы со мной ... по крайней мере, для моего варианта использования. - person Please_Dont_Bully_Me_SO_Lords; 06.08.2013