2012-03-06 78 views
2

我對Indy相當陌生,在本地局域網(沒有互聯網)上實現了非常基本的idTCPServer和IdTCPClient,但是現在需要3臺機器才能通話! (環境是Delphi 2010/Win7/DBExpress/MySQL)。 應用程序是控制實時航行事件。一個「主」PC和一個「奴隸」(現在需要2個奴隸!)。 從PC使水手能夠註冊事件的細節(存儲到MySQL表中)。主PC控制a)當註冊屏幕打開/關閉時,b)將事件詳細信息發送給從屬者,c)發送實時比賽倒數/開始和比賽經過時間給他們必須顯示並作出反應的奴隸(關閉註冊屏幕等) 。主人需要知道新人何時登錄或註銷以更新其樂譜。要使用哪種Indy組件?

目前我實施(我的第一個Indy程序)與主IDTCPServer。從站上的IdTCPClient在新登錄/註銷時告訴主站,並不斷向服務器發送「時間請求」文本消息,因爲我不知道如何從TCPServer發送消息!)。

這個我想是不是這樣做最好的方法,現在俱樂部希望兩個「登錄」的奴隸,我需要重新訪問整個事情,所以我向你請教,請...

其中印組件最適合使用? TCPServer在「主」PC上最好嗎?服務器應該向2個從機廣播嗎?和(請!)是否有任何示例具有類似的功能,我可以用它作爲基礎來幫助我實現? 非常感謝 克里斯

+0

克里斯,不要忘了['接受answers'(http://meta.stackexchange.com/a/5235/179541),它幫你解決你的問題; - ) – TLama 2012-03-07 12:00:58

回答

8

使用的奴隸TIdTCPServer在主服務器和TIdTCPClient是正確的道路要走。

將消息從服​​務器發送到客戶端的一種方法是使用服務器的Threads屬性(Indy 9及更早版本)或Contexts屬性(Indy 10)來訪問當前連接的客戶端列表。每個客戶端都有一個與其關聯的TIdTCPConnection對象用於與該客戶端進行通信。當需要時,您可以通過它鎖定服務器的客戶名單,循環寫郵件到每一個客戶端,然後解鎖名單:

印第安納波利斯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; 

印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甚至派在排隊的消息了自己的日程。這樣一來,多個客戶端可以同時接收消息,而不是串行:

印第安納波利斯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; 

印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等),另一種模式是服務器向客戶端發送實時廣播。試圖通過單一連接管理這兩種模式是可行的,但棘手。一個簡單的解決方案是將廣播移動到另一個TIdTCPServer,該廣播只發送廣播而不做其他任何事情。主設備可以有兩個TIdTCPServer組件運行,監聽不同的端口,然後每個從站可以有兩個TIdTCPClient組件運行,一個用於發送命令,另一個用於接收廣播。缺點是每個從設備必須與主設備保持2個連接,如果一次連接多個從設備,會佔用網絡帶寬。但它確實讓你的編碼在雙方都很簡單。

印第安納波利斯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; 

印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; 

如果使用命令和廣播單獨的連接是不是出於某種原因的選項,那麼你基本上是需要設計自己的通信協議異步工作,這意味着客戶可以發送命令到服務器,而不是等待迴應馬上回來。客戶端必須做的所有其讀取的定時/線程內,然後確定每個接收到的消息是否是廣播或到先前命令的響應,並採取相應的行動:

印地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; 

印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; 
+0

天哪雷米,謝謝 - 這裏有很多可以吸收的東西,現在是英國的午夜,所以我會努力工作。 h明天!順便說一句,我使用Indy 10,對不起,我沒有讓步,但我相信其他人會發現Indy 9也有用。非常感謝!! – ChrisH 2012-03-07 00:12:48

+0

我同意雷米。我會讓每個客戶端有兩個連接到服務器。每個連接都用於一個方向的通信。要從服務器發送到客戶端,服務器發送數據,客戶端只是說「好」。當從客戶端發送到服務器時,客戶端發送數據,而服務器只是說「好」。數據在每一端排隊,然後進行處理,可能有一些優先級方面。 – 2012-03-07 02:00:26

+0

謝謝,我已經看了以上所有。有沒有一種方式,TCPServer可以廣播消息給客戶端沒有收到客戶端請求(我想我的意思是如何觸發服務器OnExecute()如果沒有客戶端發送消息?你絕對的意思是有兩個IdTCPServers在主pc和兩個idTCPClients on slave pcs?(而不是每臺機器上都有一臺?)PS我試圖避免定時器/輪詢在任何地方發生延遲消息 – ChrisH 2012-04-12 15:06:33