使用的奴隸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;
克里斯,不要忘了['接受answers'(http://meta.stackexchange.com/a/5235/179541),它幫你解決你的問題; - ) – TLama 2012-03-07 12:00:58