2013-01-05 36 views
2

根據幾個問題,幾乎satysfy回答雷米Lebeau(再次感謝你)的一個特別的我試圖結合我的應用程序的代碼有用。 還有幾個方面給我不清楚。當你看到下面的代碼:TIdServer,關於再次同步

  • 當我使用Button3Click過程從GUI發送廣播到連接的客戶端 - 是正確的方法(我的意思是:這是安全的)?
  • 我可以放入類似於DoSomethingSafe方法的代碼,在其中創建與數據庫的連接,對其執行操作並關閉與數據庫的連接?它安全嗎?
  • 爲什麼我的應用程序凍結時,有超過20 clietns,我想通過使用其active:= false(button2.click方法)停止工作服務器?
  • 我可以在TCliContext.ProccessMsg中使用TCliContext.BroadcastMessage,只是在沒有任何同步的情況下調用它?我可以在OnConnect方法中讀取(Connection.IOHandler.ReadLn())(我想要用登錄數據讀取行並在數據庫中檢查它,然後當它不正確時立即斷開連接嗎?
  • 我在某處讀取,使用IdSync有時危險(如果有什麼內部使用它錯了),所以我有最後一個問題:什麼是更好的解決方案,以達到全局變量或VCL對象

我的示範代碼如下:?

type 
    TCliContext = class(TIdServerContext) 
    private 
    Who: String; 
    Queue: TIdThreadSafeStringList; 

    Activity_time: TDateTime; 
    Heartbeat_time: TDateTime; 

    InnerMessage: String; 

    procedure BroadcastMessage(const ABuffer: String); 
    procedure SendMessageTo(const ADestUser: String; const ABuffer: String); 

    public 
    constructor Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TThreadList = nil); override; 
    destructor Destroy; override; 

    procedure ProccessMsg; 
    procedure DoSomethingSafe; 
    procedure info_about_start_connection; 
    end; 

procedure TCliContext.BroadcastMessage(const ABuffer: String); 
var 
    cList: TList; 
    Count: Integer; 
    CliContext: TCliContext; 
begin 
    cList := Server.Contexts.LockList; 
    try 
    for Count := 0 to cList.Count - 1 do 
    begin 
     CliContext := TCliContext(cList[Count]); 
     if CliContext <> Self then 
     CliContext.Queue.Add(ABuffer); 
    end; 
    finally 
    Server.Contexts.UnlockList; 
    end; 
end; 

procedure TCliContext.SendMessageTo(const ADestUser: String; 
    const ABuffer: String); 
var 
    cList: TList; 
    Count: Integer; 
    CliContext: TCliContext; 
begin 
    cList := Server.Contexts.LockList; 
    try 
    for Count := 0 to cList.Count - 1 do 
    begin 
     CliContext := TCliContext(cList[Count]); 
     if CliContext.Who = ADestUser then 
     begin 
     CliContext.Queue.Add(ABuffer); 
     Break; 
     end; 
    end; 
    finally 
    Server.Contexts.UnlockList; 
    end; 
end; 

constructor TCliContext.Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TThreadList = nil); 
begin 
    // inherited Create(AConnection, AYarn, AList); 
    inherited; 
    Queue := TIdThreadSafeStringList.Create; 
end; 

destructor TCliContext.Destroy; 
begin 
    Queue.Free; 
    inherited; 
end; 

procedure TCliContext.ProccessMsg; 
begin 
    InnerMessage := Connection.IOHandler.ReadLn(); 
    TIdSync.SynchronizeMethod(DoSomethingSafe); 
    // is it ok? 
end; 

procedure TCliContext.info_about_start_connection; 
begin 
    MainForm.Memo1.Lines.Add('connected'); 
end; 

procedure TCliContext.DoSomethingSafe; 
begin 
    MainForm.Memo1.Lines.Add(InnerMessage); 
end; 

和代碼核心與GUI

procedure TMainForm.BroadcastMessage(Message: string); 
var 
    cList: TList; 
    Count: Integer; 
begin 
    cList := IdTCPServer.Contexts.LockList; 
    try 
    for Count := 0 to cList.Count - 1 do 
     TCliContext(cList[Count]).Queue.Add(Message); 
    finally 
    IdTCPServer.Contexts.UnlockList; 
    end; 
end; 

procedure TMainForm.FormCreate(Sender: TObject); 
begin 
    IdTCPServer.ContextClass := TCliContext; 
end; 

procedure TMainForm.IdTCPServerConnect(AContext: TIdContext); 
begin 
    TCliContext(AContext).Queue.Clear; 
    TCliContext(AContext).Heartbeat_time := now; 
    TCliContext(AContext).Activity_time := now; 
    TIdSync.SynchronizeMethod(TCliContext(AContext).info_about_start_connection); 
    // is it safe? 
end; 

procedure TMainForm.IdTCPServerExecute(AContext: TIdContext); 
var 
    tmplist, Queue: TStringlist; 
    dtNow: TDateTime; 
begin 
    dtNow := now; 
    tmplist := nil; 
    try 
    Queue := TCliContext(AContext).Queue.Lock; 
    try 
     if Queue.Count > 0 then 
     begin 
     tmplist := TStringlist.Create; 
     tmplist.Assign(Queue); 
     Queue.Clear; 
     end; 
    finally 
     TCliContext(AContext).Queue.Unlock; 
    end; 
    if tmplist <> nil then 
    begin 
     AContext.Connection.IOHandler.Write(tmplist); 
     TCliContext(AContext).Heartbeat_time := dtNow; 
    end; 
    finally 
    tmplist.Free; 
    end; 

    if SecondsBetween(dtNow, TCliContext(AContext).Heartbeat_time) > 30 then 
    begin 
    AContext.Connection.IOHandler.WriteLn('E:'); 
    TCliContext(AContext).Heartbeat_time := dtNow; 
    end; 

    if SecondsBetween(dtNow, TCliContext(AContext).Activity_time) > 6 then 
    begin 
    AContext.Connection.Disconnect; 
    Exit; 
    end; 
    TCliContext(AContext).ProccessMsg;; 
end; 

procedure TMainForm.Button1Click(Sender: TObject); 
begin 
    IdTCPServer.Active := true; 
end; 

procedure TMainForm.Button2Click(Sender: TObject); 
begin 
    IdTCPServer.Active := false; 
    // here application freezes when there are more then tens active clients 
end; 

procedure TMainForm.Button3Click(Sender: TObject); 
begin 
    BroadcastMessage('Hello'); 
    // is it safe and correct? 
end; 

更新(你的出色答卷後,最後一個問題)做簡單(代碼長度較短),我可以使用TIdNotify類象下面這樣:

TMyNotify.Create(1, 'ABC').Notify; 

type 
    TMyNotify = class(TidNotify) 
    public 
    faction: string; 
    fdata:string; 
    procedure DoNotify; override; 
    procedure action1(); 
    procedure action2(); 
    constructor Create(action:integer;fdata:string); reintroduce; 
    end; 

constructor TMyNotify.Create(action:integer;fdata:string); reintroduce; 
begin 
    inherited Create; 
    faction:=action; 
    fdata:=data; 
end; 

procedure TMyNotify.action2() 
begin 
    //use fdata and do something with vcl etc. 
end; 

procedure TMyNotify.action2() 
begin 
    //use fdata and do something with vcl etc. 
end; 

procedure TMyNotify.DoNotify; 
begin 
    case action of 
    1: action1() 
    2: action2() 
    end; 
end; 

再次感謝您對以前的幫助

回答

8

當我使用Button3Click過程從GUI發送廣播到連接的客戶端 - 是正確的方法(我的意思是:這是安全的)?

是的,您正在安全地發送數據。但是,TCliContext.ProcessMsg()正在對ReadLn()執行阻止呼叫。如果客戶端暫時沒有發送任何數據,該邏輯將阻止您的代碼及時執行其時間敏感邏輯(如果有的話)。由於涉及時間敏感的邏輯,因此您需要在連接處理中使用超時,以便您的時間檢查具有運行的機會。不要致電ProcessMsg(),直到有實際數據可供讀取(或ProcessMsg()內部處理超時),並且您應該爲TIdIOHandler.ReadTimeout屬性指定一個值,以便在客戶端停止在消息中間發送數據的情況下進行測量。例如:

procedure TMainForm.IdTCPServerConnect(AContext: TIdContext); 
begin 
    ... 
    AContext.Connection.IOHandler.ReadTimeout := 10000; 
end; 

procedure TMainForm.IdTCPServerExecute(AContext: TIdContext); 
var 
    ... 
begin 
    ... 

    if AContext.Connection.IOHandler.InputBufferIsEmpty then 
    begin 
    if not AContext.Connection.IOHandler.CheckForDataOnSource(100) then 
    begin 
     AContext.Connection.IOHandler.CheckForDisconnect; 
     Exit; 
    end; 
    end; 

    TCliContext(AContext).ProccessMsg; 
    TCliContext(AContext).Activity_time := Now(); 
end; 

我可以把類似於我創建連接到數據庫DoSomethingSafe方法的代碼,做了東西,併到DB密切的聯繫?它安全嗎?

是的。事實上,特別是對於數據庫查詢,您應該儘可能爲每個客戶端線程提供自己的與數據庫的連接。然後,您不必同步數據庫查詢(並根據所使用的數據庫,甚至可以在查詢本身中使用數據庫提供的同步鎖)。如果可能的話,您還應該彙集數據庫連接(由於架構限制,某些數據庫類型不可打包,例如,由於ADO使用特定於線程的ActiveX/COM對象)。如果不需要,不要跨多個線程同步數據庫連接。當您需要執行數據庫查詢時,從池中獲取數據庫連接(或者根據需要創建新連接),執行數據庫查詢,然後將數據庫連接放回池中(如果可能),以便另一個客戶端線程可以在需要時使用它。如果數據庫連接暫時在池中,請斷開連接,然後在需要再次使用它時重新連接。這有助於將DB連接的數量保持在最低限度,同時最大限度地提高其使用率。

爲什麼我的應用程序凍結時,有超過20 clietns,我想用它來停止工作服務器:= false(button2.click方法)?

要做到這一點,最常見的原因是,你很可能開始同步操作的主線程(或已經在同步操作的中間),而在主線程中停用服務器同時。這是一個有保證的死鎖情況。請記住,每個客戶端都在服務器內的自己的線程中運行。當主線程停用服務器時,它會在服務器等待服務器完成停用時被阻塞,因此無法處理同步請求。服務器停用等待所有客戶端線程完全終止。同步客戶端線程在等待主線程處理同步請求時被阻塞,因此無法終止。發生死鎖。客戶端的數量並不重要,即使只有1個客戶端連接,也可能發生這種情況。

爲了解決這個,你有兩個選擇:

  1. 創建工作線程停用的而不是主線程停用服務器。這釋放了主線程以正常處理同步請求,允許客戶端線程正常終止,服務器正常完全停用。例如:

    type 
        TShutdownThread = class(TThread) 
        protected 
        procedure Execute; override; 
        end; 
    
    procedure TShutdownThread.Execute; 
    begin 
        MainForm.IdTCPServer.Active := False; 
    end; 
    
    procedure TMainForm.Button2Click(Sender: TObject); 
    begin 
        if MainForm.IdTCPServer.Active then 
        begin 
        with TShutdownThread.Create(False) do 
        try 
         WaitFor; // internally processes sync requests... 
        finally 
         Free; 
        end; 
        end; 
    end; 
    
  2. 儘可能消除線程阻塞同步。在客戶端直接執行儘可能多的工作,而不是在主線程中執行。特別是對於您的客戶端代碼實際上不必等待主線程的響應的操作。如果實際上不需要跨線程邊界同步,則不要同步它。如果必須與主線程同步,請儘可能使用TIdNotify而不是TIdSyncTIdNotify是異步的,所以它不會像TIdSync那樣阻塞調用線程,從而避免了失效死鎖。你只需要更加小心TIdNotify,因爲它是異步的。它被放入後臺隊列並在稍後執行,因此您必須確保您訪問的任何對象和數據在最終運行時仍然有效。出於這個原因,最好使TIdNotify的實現儘可能獨立,所以它們不依賴於外部事物。例如:

    type 
        TMemoNotify = class(TIdNotify) 
        protected 
        FStr: String; 
        procedure DoNotify; override; 
        public 
        class procedure AddToMemo(const Str: string); 
        end; 
    
    procedure TMemoNotify.DoNotify; 
    begin 
        MainForm.Memo1.Lines.Add(FStr); 
    end; 
    
    class procedure TMemoNotify.AddToMemo(const Str: string); 
    begin 
        with Create do 
        begin 
        FStr := Str; 
        Notify; 
        // DO NOT free it! It is self-freeing after it is run later on... 
        end; 
    end; 
    
    procedure TCliContext.ProcessMsg; 
    var 
        Msg: string; 
    begin 
        Msg := Connection.IOHandler.ReadLn; 
        TMemoNotify.AddToMemo(Msg); 
        ... 
    end; 
    
    procedure TMainForm.IdTCPServerConnect(AContext: TIdContext); 
    begin 
        ... 
        TCliContext(AContext).Who := ...; 
        TMemoNotify.AddToMemo(TCliContext(AContext).Who + ' connected'); 
        ... 
    end; 
    
    procedure TMainForm.IdTCPServerDisconnect(AContext: TIdContext); 
    begin 
        ... 
        TMemoNotify.AddToMemo(TCliContext(AContext).Who + ' disconnected'); 
        ... 
    end; 
    

我可以使用TCliContext.BroadcastMessage的TCliContext.ProccessMsg裏面只是調用它wihtout任何同步?

是的,因爲TIdTCPServer.ContextTIdThreadSafeStringList鎖被提供adaquate同步(它們都使用TCriticalSection內部)。這同樣適用於TCliContext.SendMessageTo()

我可以讀(Connection.IOHandler.ReadLn())中的onConnect方法(我想登錄數據讀取線,並檢查它在DB那麼當它是不正確做斷開立刻?

是。OnConnect(和OnDisconnect)運行在該OnExecute運行英寸TIdTCPServer檢查如果套接字仍然後OnConnect退出連接,然後開始OnExecute環,以防OnConnect確實決定斷開客戶端之前相同的客戶端線程上下文。

我讀的地方,使用IDSYNC有時是危險(如果有什麼不順心的內部使用它)

在大多數情況下,TIdSyncTIdNotify是安全的,只要你正確地使用它們使用。

TIdSync,是同步的,如果主線程被阻塞,確實具有死鎖潛力,就這樣。

如果您使用的是TIdNotify,請確保您使用的是最新版本的Indy 10.一些較早版本的Indy 10在TIdNotify中有內存泄漏,但最近已修復此問題。

什麼是更好的解決方案來達到全局變量或VCL對象?

與任何給定線程沒有嚴格關聯的全局變量應儘可能提供自己的同步。無論是在自己的內部代碼中(例如您的BroadcastMessage()SendMessageTo()實現),還是通過單獨的鎖,如TCriticalSection對象。

VCL對象只能在主線程中訪問,所以如果你不使用TIdSync/TIdNotify,你必須使用其他形式的線程同步來委託代碼在main線。這就是UI邏輯和業務邏輯分離的真正原因。如果可能,應該將業務數據與用戶界面分開,然後在數據操作周圍提供安全的線程間鎖定,然後讓用戶界面在需要時安全地更新數據,並讓工作線程在需要時安全地更新數據,向UI發佈異步請求以顯示最新數據。

+0

優秀和完善的飲食。我很感激。但是,如果可以,請回答在我的主要問題末尾添加的TidNotify示例是否正確? – Artik

+0

是的,這將工作正常。 –

+0

這是我見過有關使用Indy實現tcp服務器的最佳職位之一。它總結了我在過去4年中所學到的。 @RemyLebeau謝謝。 – MikeT