Какие компоненты Indy использовать? - PullRequest
2 голосов
/ 06 марта 2012

Я новичок в Indy, реализовал очень простой idTCPServer и IdTCPClient между 2 компьютерами в локальной сети (без интернета), но теперь мне нужно 3 машины для общения!(Средой является Delphi 2010 / Win7 / DBExpress / MySQL).Приложение для управления парусным событием в реальном времени.«Хозяин» и «раб» (теперь нужно 2 раба!).Подчиненные компьютеры позволяют морякам регистрировать свои данные для события (хранятся в таблицах MySQL).Главный компьютер управляет: а) когда экран регистрации открывается / закрывается, б) отправляет сведения о событии ведомым, в) отправляет обратный отсчет времени старта / запуска и прошедшего забега на ведомые устройства, которые они должны отобразить и на которые реагировать (закрытие экрана регистрации и т. Д.),Мастер должен знать, когда новый пользователь вошел в систему или вышел из нее, чтобы обновить свой racelist.

В настоящее время я реализовал (свою первую программу Indy) с IDTCPServer на master.IdTCPClient на ведомом устройстве сообщает мастеру о новом входе / выходе и постоянно отправляет текстовые сообщения «запрос времени» на сервер, так как я не знаю, как отправить сообщение с сервера TCPServer!).

Я думаю, что это не лучший способ сделать это, и теперь клуб хочет ДВА Раба "Sign On", мне нужно снова посетить все это, поэтому я прошу вашего совета, пожалуйста ...

Какие компоненты Indy лучше всего использовать?Лучше ли иметь TCPServer на «мастер» ПК?Должен ли сервер вещать на 2 рабов?и (пожалуйста!) есть ли какой-нибудь пример, который имеет аналогичную функциональность, которую я могу использовать в качестве основы, чтобы помочь мне реализовать?Большое спасибо Крис

1 Ответ

8 голосов
/ 07 марта 2012

Использование TIdTCPServer на ведущем устройстве и TIdTCPClient на ведомых устройствах - правильный путь.

Один из способов отправки сообщений с сервера клиентам - использование свойства сервера Threads (Свойство Indy 9 и более ранних версий) или Contexts (Indy 10) для доступа к списку подключенных в данный момент клиентов.С каждым клиентом связан объект TIdTCPConnection для связи с этим клиентом.При необходимости вы можете заблокировать список клиентов сервера, пройтись по нему, записав свое сообщение каждому клиенту, а затем разблокировать список:

Indy 9:

procedure TMaster.SendMsg(const S: String);
var
  List: TList;
  I: Integer;
begin
  List := IdTCPServer1.Threads.LockList;
  try
    for I := 0 to List.Count-1 do
    begin
      try
        TIdPeerThread(List[I]).Connection.WriteLn(S);
      except
      end;
    end;
  finally
    IdTCPServer1.Threads.UnlockList;
  end;
end;

Indy 10:

procedure TMaster.SendMsg(const S: String);
var
  List: TList;
  I: Integer;
begin
  List := IdTCPServer1.Contexts.LockList;
  try
    for I := 0 to List.Coun-1 do
    begin
      try
        TIdContext(List[I]).Connection.IOHandler.WriteLn(S);
      except
      end;
    end;
  finally
    IdTCPServer1.Contexts.UnlockList;
  end;
end;

Однако у этого есть некоторые недостатки.

Одним из недостатков является то, что сообщения сериализуются, поэтому, если один клиент замерзнет, ​​последующие клиенты не получат свои сообщения своевременно.Другая проблема заключается в том, что клиенты работают в своих собственных потоках на сервере, поэтому при отправке данных клиенту из нескольких потоков в одно и то же время вам потребуется предоставить собственный механизм блокировки для каждого подключения (например, критический раздел или мьютекс).вокруг каждого доступа на запись к соединению, чтобы избежать наложения данных и повреждения ваших коммуникаций

Чтобы избежать этих ловушек, лучше дать каждому клиенту исходящую очередь сообщений, а затем позволить OnExecute сервера даже отправлятьсообщения в очереди по собственному расписанию.Таким образом, несколько клиентов могут получать сообщения параллельно, а не последовательно:

Indy 9:

uses
  ..., IdThreadSafe;

procedure TMaster.IdTCPServer1Connect(AThread: TIdPeerThead);
begin
  AThread.Data := TIdThreadSafeStringList.Create;
end;

procedure TMaster.IdTCPServer1Disconnect(AThread: TIdPeerThead);
begin
  AThread.Data.Free;
  AThread.Data := nil;
end;

procedure TMaster.IdTCPServer1Execute(AThread: TIdPeerThead);
var
  Queue: TIdThreadSafeStringList;
  List: TStringList;
  Tmp: TStringList;
  I: Integer;
begin
  ...
  Queue := TIdThreadSafeStringList(AThread.Data);
  List := Queue.Lock;
  try
    if List.Count > 0 then
    begin
      Tmp := TStringList.Create;
      try
        Tmp.Assign(List);
        List.Clear;
      except
        Tmp.Free;
        raise;
      end;
    end;
  finally
    Queue.Unlock;
  end;
  if Tmp <> nil then
  try
    AThread.Connection.WriteStrings(Tmp, False);
  finally
    Tmp.Free;
  end;
  ...
end;

procedure TMaster.SendMsg(const S: String);
var
  List: TList;
  I: Integer;
begin
  List := IdTCPServer1.Threads.LockList;
  try
    for I := 0 to List.Count-1 do
    begin
      try
        TIdThreadSafeStringList(TIdPeerThread(List[I]).Data).Add(S);
      except
      end;
    end;
  finally
    IdTCPServer1.Threads.UnlockList;
  end;
end;

Indy 10:

uses
  ..., IdThreadSafe;

procedure TMaster.IdTCPServer1Connect(AContext: TIdContext);
begin
  AContext.Data := TIdThreadSafeStringList.Create;
end;

procedure TMaster.IdTCPServer1Disconnect(AContext: TIdContext);
begin
  AContext.Data.Free;
  AContext.Data := nil;
end;

procedure TMaster.IdTCPServer1Execute(AContext: TIdContext);
var
  Queue: TIdThreadSafeStringList;
  List: TStringList;
  Tmp: TStringList;
  I: Integer;
begin
  ...
  Queue := TIdThreadSafeStringList(AContext.Data);
  List := Queue.Lock;
  try
    if List.Count > 0 then
    begin
      Tmp := TStringList.Create;
      try
        Tmp.Assign(List);
        List.Clear;
      except
        Tmp.Free;
        raise;
      end;
    end;
  finally
    Queue.Unlock;
  end;
  if Tmp <> nil then
  try
    AContext.Connection.IOHandler.Write(Tmp, False);
  finally
    Tmp.Free;
  end;
  ...
end;

procedure TMaster.SendMsg(const S: String);
var
  List: TList;
  I: Integer;
begin
  List := IdTCPServer1.Contexts.LockList;
  try
    for I := 0 to List.Count-1 do
    begin
      try
        TIdThreadSafeStringList(TIdContext(List[I]).Data).Add(S);
      except
      end;
    end;
  finally
    IdTCPServer1.Contexts.UnlockList;
  end;
end;

Даже при наличии очередиЧтобы решить проблемы многопоточности, еще один недостаток заключается в том, что все еще не очень хорошо работает, если событие OnExecute вашего сервера или коллекция CommandHandlers должны отправить данные обратно клиенту в ответ на команду от клиента в то же время, что и сервер.Сервер передает сообщения тем же клиентам.После того, как клиент отправит команду и попытается прочитать ответ, он может вместо этого получить широковещательную рассылку, и реальный ответ будет получен после отправки другой команды позже.Клиенту придется обнаруживать широковещательные сообщения, чтобы он мог продолжать чтение, пока не получит ожидаемый ответ.

По сути, вы запрашиваете две отдельные модели связи.Одна модель позволяет клиенту отправлять команды на сервер (вход / выход и т. Д.), А другая модель - когда сервер отправляет клиентам широковещательные сообщения в режиме реального времени.Попытка управлять этими двумя моделями по одному соединению выполнима, но сложна.Простым решением было бы перенести трансляции на другой TIdTCPServer, который просто отправляет трансляции и больше ничего не делает.Ведущий может иметь два работающих компонента TIdTCPServer, которые прослушивают разные порты, а затем на каждом подчиненном устройстве могут работать два компонента TIdTCPClient, один для отправки команд и один для приема широковещательных сообщений.Недостатком является то, что каждому ведомому устройству придется поддерживать 2 соединения с ведущим, что может потреблять пропускную способность сети, если одновременно подключено много ведомых устройств.Но это делает ваше кодирование довольно простым с обеих сторон.

Indy 9:

procedure TMaster.IdTCPServer1Execute(AThread: TIdPeerThread);
var
  S: String;
begin
  S := AThread.Connection.ReadLn;
  if S = 'SignIn' then
    ...
  else if S = 'SignOut' then
    ...
  else
    ...
end;

procedure TMaster.SendBroadcast(const S: String);
var
  List: TList;
  I: Integer;
begin
  List := IdTCPServer2.Threads.LockList;
  try
    for I := 0 to List.Count-1 do
    begin
      try
        TIdPeerThread(List[I]).Connection.WriteLn(S);
      except
      end;
    end;
  finally
    IdTCPServer2.Threads.UnlockList;
  end;
end;

.

procedure TSlave.Connect;
begin
  IdTCPClient1.Connect;
  IdTCPClient2.Connect;
end;

procedure TSlave.SignIn;
begin
  IdTCPClient1.SendCmd('SignIn');
  ...
end;

procedure TSlave.SignOut;
begin
  IdTCPClient1.SendCmd('SignOut');
  ...
end;

procedure TSlave.IdTCPClient2Connect(Sener: TObject);
begin
  Timer1.Enabled := True;
end;

procedure TSlave.IdTCPClient2Connect(Sener: TObject);
begin
  Timer1.Enabled := True;
end;

procedure TSlave.Timer1Elapsed(Sender: TObject);
var
  S: String;
begin
  try
    if IdTCPClient2.InputBuffer.Size = 0 then
      IdTCPClient2.ReadFromStack(True, 0, False);
    while IdTCPClient2.InputBuffer.Size > 0 do
    begin
      S := IdTCPClient2.ReadLn;
      ... handle broadcast ...
    end;
  except
    on E: EIdException do
      IdTCPClient2.Disconnect;
  end;
end;

Indy 10:

procedure TMaster.IdTCPServer1Execute(AContext: TIdContext);
var
  S: String;
begin
  S := AContext.Connection.IOHandler.ReadLn;
  if S = 'SignIn' then
    ...
  else if S = 'SignOut' then
    ...
  else
    ...
end;

procedure TMaster.SendBroadcast(const S: String);
var
  List: TList;
  I: Integer;
begin
  List := IdTCPServer2.Contexts.LockList;
  try
    for I := 0 to List.Count-1 do
    begin
      try
        TIdContext(List[I]).Connection.IOHandler.WriteLn(S);
      except
      end;
    end;
  finally
    IdTCPServer2.Contexts.UnlockList;
  end;
end;

.

procedure TSlave.Connect;
begin
  IdTCPClient1.Connect;
  IdTCPClient2.Connect;
end;

procedure TSlave.SignIn;
begin
  IdTCPClient1.SendCmd('SignIn');
  ...
end;

procedure TSlave.SignOut;
begin
  IdTCPClient1.SendCmd('SignOut');
  ...
end;

procedure TSlave.IdTCPClient2Connect(Sener: TObject);
begin
  Timer1.Enabled := True;
end;

procedure TSlave.IdTCPClient2Connect(Sener: TObject);
begin
  Timer1.Enabled := True;
end;

procedure TSlave.Timer1Elapsed(Sender: TObject);
var
  S: String;
begin
  try
    if IdTCPClient2.IOHandler.InputBufferIsEmpty then
      IdTCPClient2.IOHandler.CheckForDataOnSource(0);
    while not IdTCPClient2.IOHandler.InputBufferIsEmpty do
    begin
      S := IdTCPClient2.IOHandler.ReadLn;
      ... handle broadcast ...
    end;
  except
    on E: EIdException do
      IdTCPClient2.Disconnect;
  end;
end;

Если по каким-либо причинам использование отдельных соединений для команд и широковещательных рассылок не представляется возможным, то вам, по сути, необходимо настроить асинхронный протокол связи, то есть клиент может отправить командусервер и не ждать ответа, чтобы вернуться сразу же.Клиент должен будет выполнить все свои чтения из таймера / потока, а затем определить, является ли каждое полученное сообщение широковещательной рассылкой или ответом на предыдущую команду, и действовать соответственно:

Indy 9:

procedure TSlave.IdTCPClient1Connect(Sender: TObject);
begin
  Timer1.Enabled := True;
end;

procedure TSlave.IdTCPClient1Disconnect(Sender: TObject);
begin
  Timer1.Enabled := False;
end;

procedure TSlave.PostCmd(const S: String);
begin
  IdTCPClient1.WriteLn(S);
end;

procedure TSlave.Timer1Elapsed(Sender: TObject);
var
  S: String;
begin
  try
    if IdTCPClient1.InputBuffer.Size = 0 then
      IdTCPClient1.ReadFromStack(True, 0, False);
    while IdTCPClient1.InputBuffer.Size > 0 do
    begin
      S := IdTCPClient1.ReadLn;
      if (S is a broadcast) then
        ... handle broadcast ...
      else
        ... handle a command response ...
    end;
  except
    on E: EIdException do
      IdTCPClient1.Disconnect;
  end;
end;

Indy 10:

procedure TSlave.IdTCPClient1Connect(Sender: TObject);
begin
  Timer1.Enabled := True;
end;

procedure TSlave.IdTCPClient1Disconnect(Sender: TObject);
begin
  Timer1.Enabled := False;
end;

procedure TSlave.PostCmd(const S: String);
begin
  IdTCPClient1.IOHandler.WriteLn(S);
end;

procedure TSlave.Timer1Elapsed(Sender: TObject);
var
  S: String;
begin
  try
    if IdTCPClient1.IOHandler.InputBufferIsEmpty then
      IdTCPClient1.IOHandler.CheckForDataOnSource(0);
    while not IdTCPClient1.IOHandler.InputBufferIsEmpty do
    begin
      S := IdTCPClient1.IOHandler.ReadLn;
      if (S is a broadcast) then
        ... handle broadcast ...
      else
        ... handle a command response ...
    end;
  except
    on E: EIdException do
      IdTCPClient1.Disconnect;
  end;
end;
...