簡體   English   中英

哪個Indy組件要使用?

[英]Which Indy components to use?

我是Indy的新手,已在本地局域網(無互聯網)上實現了2個pc之間的非常基本的idTCPServer和IdTCPClient,但現在需要3台機器進行通話! (環境是Delphi 2010 / Win7 / DBExpress / MySQL)。 應用是控制實時航行事件。 一個“主”pc和一個“奴隸”(現在需要2個奴隸!)。 slave pc使得水手能夠注冊他們的事件細節(存儲在MySQL表中)。 主控制器控制a)當注冊屏幕打開/關閉時,b)向從屬設備發送事件詳細信息,c)將實時比賽倒計時/開始和比賽經過時間發送給他們必須顯示和做出反應的對象(關閉注冊屏幕等) 。 Master需要知道新人何時登錄或退出以更新其racelist。

目前我在master上使用IDTCPServer實現了(我的第一個Indy程序)。 奴隸上的IdTCPClient在新的SignIn / Out時告訴master,並不斷向服務器發送“time request”文本消息,因為我不知道如何從TCPServer發送消息!)。

我認為這不是最好的方法,現在俱樂部想要兩個“登錄”的奴隸,我需要重新訪問整個事情,所以我請你的意見...

哪種Indy組件最好用? 最好在“主”電腦上安裝TCPServer嗎? 服務器應該向2個從站廣播嗎? 並且(請!)是否有任何具有類似功能的示例我可以作為幫助我實現的基礎? 非常感謝Chris

在主TIdTCPClient上使用TIdTCPServer和在從TIdTCPClient上使用TIdTCPServer是正確的方法。

從服務器向客戶端發送消息的一種方法是使用服務器的Threads屬性(Indy 9及更早版本)或Contexts屬性(Indy 10)來訪問當前連接的客戶端列表。 每個客戶端都有一個TIdTCPConnection關聯的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集合必須將數據發送回客戶端以響應來自客戶端的命令,那么事情仍然不能很好地發揮作用服務器正在向這些客戶端廣播消息。 在客戶端發送命令並嘗試回讀響應之后,它可能會接收到廣播,並且稍后在發送另一個命令之后將收到實際響應。 客戶端必須檢測廣播,以便它可以繼續閱讀,直到它獲得預期的響應。

您實際上是在尋求兩種獨立的通信模型。 一種模型允許客戶端向服務器發送命令(SignIn / Out等),以及服務器向客戶端發送實時廣播的另一種模型。 嘗試通過單個連接管理這兩個模型是可行的,但很棘手。 一個簡單的解決方案是將廣播移動到另一個僅發送廣播的TIdTCPServerTIdTCPServer執行任何其他操作。 主設備可以運行兩個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;

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM