1. 程式人生 > >Linux網路程式設計--兩種高效的事件處理模式

Linux網路程式設計--兩種高效的事件處理模式

前言

  今天,我們主要介紹一下網路設計模式中的兩種事件處理模式,Reactor和Proactor模式。通常使用同步I/O模型(select,poll,epoll等)用來實現Reactor模式,而非同步(aio_read或aio_write等)用來實現Proactor模式,不過還可以使用同步來模擬Proactor模式。

Reactor模式

設計思路

  主執行緒(I/O處理單元)只負責對I/O事件的監聽,而不負責對I/O的讀寫,如果有I/O事件發生,則主執行緒會將該事件通知給工作執行緒(邏輯單元),由工作執行緒來完成具體的I/O讀寫和邏輯處理,比如:讀寫資料、接收新的連線和處理客戶請求。

工作流程

  使用同步I/O模型(epoll)實現的reactor模式的工作流程:
  (1) 主執行緒註冊socket上的可讀事件到epoll核心事件表;
  
  (2) 主執行緒呼叫epoll_wait等待socket上的有資料可讀 ;
  
  (3) 當socket上有資料可讀時,epoll_wait通知主執行緒,主執行緒將可讀事件放到請求佇列 ;
  
  (4) 睡眠在請求佇列上的某個工作執行緒被喚醒,它從socket上讀取資料,並處理客戶請求,然後往epoll核心事件表中註冊socket可寫事件 ;
  
  (5) 主執行緒呼叫epoll_wait等待事件可寫 ;
  
  (6) 當socket可寫時,epoll_wait通知主執行緒,主執行緒將可寫事件放到請求佇列 ;
  
  (7) 睡眠在佇列上的工作執行緒被喚醒,他往socket上的寫入伺服器處理客戶請求的結果。

工作流程圖

這裡寫圖片描述

Proactor

設計思路

  Proactor與Reactor的不同之處在於Reactor將I/O處理交由工作執行緒來完成,而Proactor則將I/O處理通過非同步I/O操作交給主執行緒和核心來處理,其工作執行緒只用來處理業務邏輯。

工作流程

使用非同步I/O模型(aio_read,aio_write為例)實現Proactor的詳細步驟 :
  (1) 主執行緒呼叫aio_read來向核心註冊socket上的讀完成事件,並告訴核心使用者讀緩衝區的位置,以及操作完成時如何通知應用程式;
  
  (2) 主執行緒繼續處理其他邏輯;
  
  (3) 當socket上有資料被讀到使用者緩衝區時,核心嚮應用程式傳送一個訊號,以通知應用程式資料已經可用 ;
  
  (4) 應用程式實現定義好的訊號處理函式選擇一個工作執行緒來處理客戶請求,工作執行緒完成客戶求情之後,呼叫aio_write函式來向核心註冊socket上的可寫事件,並告訴核心使用者寫緩衝區的位置,以及寫操作完成時該如何通知應用程式 ;
  
  (5) 主執行緒繼續處理其他邏輯 ;
  
  (6) 當用戶緩衝區的資料被寫到socket之後,核心將嚮應用程式傳送一個訊號,以通知應用程式資料已經發送完畢 ;
  
  (7) 應用程式預先定義好的訊號處理函式選擇一個工作執行緒來做善後處理,比如決定是否關閉socket。

工作流程圖

這裡寫圖片描述

注意:在上圖中,連線socket上的讀寫事件是通過aio_read/aio_write向核心註冊的,因此核心將通過訊號來嚮應用程序報告連線socket上的讀寫事件。所以,主執行緒中的epoll_wait呼叫僅能用來檢測監聽socket上的連線請求事件,而不能用來檢測連線socket上的讀寫事件。

模擬Proactor模式

思想

  我們還可以使用同步I/O來模擬非同步I/O,實際上也就是用主執行緒來執行Procactor中核心做的資料讀寫操作。主執行緒讀寫完成後,再向工作執行緒通知,這樣在工作執行緒看來,就是直接獲得了資料讀寫的結果,接下來就只是對讀寫的結果進行邏輯處理即可。

工作流程

(1)主執行緒往epoll核心事件表中註冊socket上讀就緒事件;

(2)主執行緒呼叫epoll_wait等待socket上有資料可讀;

(3)當socket上有資料可讀時,epoll_wait通知主執行緒。主執行緒從socket迴圈讀取資料,直到沒有更多資料可讀,然後將讀取到的資料封裝成一個請求物件並插入請求佇列;

(4)睡眠在請求佇列上的某個工作執行緒被喚醒,它獲取請求物件並處理客戶請求,然後往epoll核心事件表中註冊socket上的寫就緒事件;

(5)主執行緒呼叫epoll_wait等待socket可寫;

(6)當socket可寫時,epoll_wait通知主執行緒。主執行緒往socket上寫入伺服器處理客戶請求的結果;

工作流程圖

這裡寫圖片描述