天天看點

[ACE系列] ACE_Reactor詳解

Reactor 模型的基礎是事件多路分離器 ,比如 selete(),poll(),WaitForMultipleObjects() 系統函數 。這些優秀的系統函數允許使用者隻用一個程序或線程,就能處理許多事件。

下面,針對 selete() 作詳解說明,其餘系統函數類同。

select() 的機制 中提供一 fd_set 的資料結構,實際上是一 long 類型的數組 ,每一個數組元素都能與一打開的檔案句柄 (不管是 Socket 句柄 , 還是其他 檔案或命名管道或裝置句柄)建立聯系 ,建立聯系的工作由程式員完成, 當調用 select() 時,由核心根據 IO 狀态 修改 fd_set 的内容,由此來通知執行了 select() 的程序哪一 Socket 或檔案可讀、可寫或異常等。具體用法見網絡相關文章。

Reactor 架構最大的好處就是封裝 selete 具體的底層操作,使更易用,可移植等。 ACE_Event_Handler 類是 Reactor 必須使用的基類,任何想要通過 Reactor 檢測的事件都需要從 ACE_Event_Handler 派生,同時将相應的處理代碼增加到相應的虛回調接口中。向 Reactor 類登記應用的事件處理對象,把每個時間處理對象與感興趣的事件關聯起來,通過 Reactor 實作事件的檢測和分派。

注意:由于 selete 依賴于句柄,是以,每一個派生的事件處理器至少對應一個實際句柄, 并通過 ACE_Event_Handler 的 get_handle() 方法向 Reactor 提供實際句柄值。反過來說,句柄(狀态變化) -> 事件處理器(相應回調函數),這就是 Reactor 實際的工作流程。 ACE 支援多個句柄登記(對應)同一個事件處理器,觸發回調的特定句柄會傳給相應的虛回調函數,通過 ACE_HANDLE 參數,這樣就可以差別處理。

因為 Reactor 實際管理的是系統句柄,句柄作為一種系統資源是有限的,當登記的事件處理器不再使用時,解除句柄和事件處理器的對應關系是必須的。

如果 Reactor 是單線程時,最好不要使用阻塞式的系統調用 ,這樣會使整個線程處于懸挂狀态,無法處理其他句柄狀态變化等事件。在同一時刻每個線程隻能執行一個 I/O 操作,這種序列本質使得無法并發執行多個節點的資料傳輸。

虛回調函數傳回值問題:一般傳回 -1 或者 0.

1 傳回 -1 ,那麼反應器停止特定事件的檢測 (相應虛回調函數對應的事件,注意不再檢測相應事件,掩碼已經改變)。

2 傳回 0 ,繼續檢測相應事件。

Remeve_handle 也會引發 handle_close 回調,是以,在使用上需要注意。就是說虛回調函數和 remove_handle 均可以引發 handle_close 。

對于 socket ,當連接配接建立之後, socket 将持續可寫(可 send ),除非 socket 出錯;但隻有對方調用 send 或者 socket 出錯時, socket 才是可讀; 同時在調用 recv() 之後, socket 将複位狀态,不再可讀。

Selete 采用水準檢測,對于讀狀态,因為可複位(調用 recv ),在 socket 有效期間,狀态會發生變化;在 handle_input 中傳回 0 ,不會導緻持續不斷的回調。對于寫狀态,隻要 socket 正常,則持續可讀,不改變。是以,如果使用 Reactor ,那麼隻要檢測寫事件,那麼将持續有效, handle_output 如果傳回 0 ,那麼 handle_output 将持續被回調!如果傳回 -1 ,則停止檢測寫事件,除非重新設定狀态檢測。(見 TcpAcceptor 例子)

注意: WaitForMultipleObjects() 采用觸發檢測, 隻要在讀寫狀态發生變化才能檢測,隻要從不可讀到可讀才能檢測出寫狀态,是以隻有在 socket 建立時才會出現寫事件。 因為 recv 會複位讀狀态,是以和 Linux 情況一緻。(如果進行 window 程式設計,應該進行測試)。

在 socket 的使用中,很可能需要打開或者關閉事件監測。 Reactor 架構通過 register_handler(this,ACE_Event_Handler::WRITE_MASK); 提供句柄注冊,經注冊的句柄, reactor 即有能力監聽該句柄所對應的事件是否發生。因為 Reactor 實際管理的是系統句柄,句柄作為一種系統資源是有限的,當登記的事件處理器不再使用時,解除句柄和事件處理器的對應關系是必須的。是以在每個 ACE_Event_Handler 在 handle_close 中都會通過 remove_handle 來解除句柄和時間的關系。

但是因 remove_handle 調用本身也是一種事件,如果沒有 ACE_Event_Handler::DONT_CALL; 那麼将觸發 handle_close 調用。故如果僅僅是想停止某句柄上的事件檢測,使用 cancel_wakeup(this, ACE_Event_Handler::WRITE_MASK); 更加合适。是以隻要句柄經注冊後,最好就是通過 cancel_wakeup() and schedule_wakeup() 實作事件監聽排程。

To manage the callbacks to this method, we use two alternative reactor registration methods: cancel_wakeup() and schedule_wakeup() . Each assumes that the handler being specified is already registered with the reactor for at least one other I/O event type. ( Client is registered for READ events from the open() method.) cancel_wakeup() removes the specified mask bit(s) from this handler's reactor registration , and schedule_wakeup() adds the specified mask. The handler is not added or removed in either case, so there's no handle_close() call as a result of removing WRITE_MASK .

繼續閱讀