2012-12-31 147 views
3

經過大量的搜索後,我認爲Indy TCP服務器將是我正在使用的即時通訊服務器上最好的使用方式。我現在面臨的唯一問題是向其他連接的客戶端廣播和轉發消息,向同一個客戶端發回響應似乎沒問題,並且不會掛斷其他客戶端的活動,但是爲了將消息轉發給其他客戶端,我知道的機制是通過使用aContext.locklist,並在連接列表之間進行迭代來查找要接收數據的客戶端連接。Indy 10 TCP服務器

這裏的問題我認爲它凍結了列表並且不會處理其他客戶端請求,直到解鎖列表被調用。那麼它會不會損害服務器的性能?鎖定列表並在連接之間迭代以轉發每條消息(因爲這是在Messenger中經常發生的事情)。有沒有更好的方法來做到這一點?

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

您希望我們能夠在心理上調試您的代碼? – Barmar

+0

對不起,我其實認爲它是一個常見問題,而不是代碼問題。 –

回答

8

是的,您可以通過Contexts名單中有循環爲了向多個客戶端廣播消息。但是,您不(也不應該)從循環內執行實際的寫作。其中一個,正如你已經注意到的那樣,服務器的性能可能會受到一段時間鎖定列表的影響。二,它不是線程安全的。如果您的循環在另一個線程同時向同一連接寫入數據時將數據寫入連接,那麼這兩個寫入將相互重疊並破壞與該客戶端的通信。

我通常使用TIdContext.Data屬性或TIdServerContext後代實現每個客戶端出站隊列來保存實際隊列。當您需要從客戶端的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; 
+0

感謝您對線程安全的提醒,我會以這種方式使用它,但是這又出現了一個問題,原始問題仍然沒有答案。因此,爲了在serverexecute過程之外編寫代碼,我應該實現一個隊列,併爲在serverexecute過程中的操作,例如對發送者客戶端的直接響應,我可以在隊列檢查完成後在服務器執行過程中執行任務嗎?還是我應該寫信給隊列呢?而且似乎可以遍歷所有的廣播連接,但... –

+0

...但是對於從一個客戶端到另一個客戶端的簡單消息轉發或文件傳輸迭代每次都不會是愚蠢的?我可以用他們的ip和TIdContext變量爲每個客戶端聲明一個類,併爲每個客戶端分配它的上下文嗎?它會安全嗎? –

+2

我的回答確實解決了您的原始問題 - 考慮到性能和線程安全性,進行廣播/轉發的最佳方式是使用每客戶端隊列。無論是廣播還是轉發,您仍然需要遍歷'Contexts'列表來查找要發送到的客戶端(除非您實現自己的線程安全查找,例如將多個'TIdContext'指針組合在一起)。通過將大部分工作卸載到每個客戶端的'OnExecute'事件,隊列有助於最大限度地減少'Contexts'列表需要被鎖定的時間... –