http://illxx.com/?p=232
http://blog.csdn.net/AAA20090987/article/month/2013/01
8.1 服務器模型:
8.1.1 C/S模型:
C/S模型的邏輯很簡單。服務器啟動后,首先創建一個(或多個)監聽socket,並調用bind函數將其綁定到服務器感興趣的端口上,然后調用listen函數等待客戶連接。服務器運行穩定后,客戶端就可以調用connect函數向服務器發起連接了。由於客戶連接請求是隨機到達的異步事件,服務器需要某種I/O模型來年監聽這一事件。客戶端接受到服務器返回的結果之后,可以繼續向服務器發送請求,也可以立即主動關閉連接。如果客戶端主動關閉連接,則服務器執行被動關閉連接。
C/S模型非常適用於資源相對集中的場合,並且他的實現也很簡單,其缺點也很明顯:服務器是通信的中心,當訪問量過大時,可能所有客戶都將得到很慢的響應。
8.1.2 P2P模型:
P2P模型比C/S模型更符合網絡通信的實際情況。它摒棄了以服務器為中心的格局,讓網絡所有的主機回歸對等的地位。P2P模型的確定明顯,當用戶之間的傳輸的請求過多時,網絡的負載將加重。一般來說,實際中所用的P2P模型通常帶有一個專門的發現服務器,這個發現服務器還提供查找服務(甚至還提供內容服務),使每個客戶都能盡快的找到自己需要的資源
從編程角度來講,P2P模型可以看做C/S模型的擴展,每台主機既是客戶端,又是服務器。
8.2 服務器編程框架
雖然服務器程序種類繁多,但其基本框架都一樣,不同之處在邏輯處理。
I/O處理單元負責處理客戶連接,讀寫網絡數據,邏輯單元是業務進程或線程,分析處理客戶數據,然后將結果傳遞給I/O處理單元,網絡存儲單元可是是本地數據庫,緩存或文件,請求隊列是各個單元之間的通信方式。
8.3 兩種高效的事件處理模式
服務器程序通常處理三類事件:I/O事件,信號及定時事件。同步I/O模型通常用於實現Reactor模式,異步I/O模型則用於實現Proactor模式,也可以用同步I/O方式模擬出Proactor模式。
8.3.1 Reactor模式:(read, write)
Reactor是這樣一種模式,它要求主線程(I/O處理單元,下同)只負責監聽文件描述符上是否有事件發生,有的話立即將該事件通知工作線程(邏輯單元),除此之外,主線程不做任何實質性的工作。讀寫數據,接收新的連接,以及處理客戶請求均在工作線程中完成。
使用同步I/O模型(以epoll_wait為例)實現的Reactor模式的工作流程是:
1)主線程往epoll內核事件中注冊socket上的讀就緒事件;
2)主線程調用epoll_wait等待socket上有數據可讀;
3)當socket上有數據可讀時,epoll_wait通知主線程。主線程則將socket可讀事件放入請求隊列;
4)睡眠在請求隊列上某個工作線程被喚醒,從socket讀取數據,處理客戶請求,然后往epoll內核事件中注冊該socket上的寫就緒事件;
5)主線程調用epoll_wait等待socket可寫;
6)當socket可寫時,epoll_wait通知主線程。主線程將socket可寫事件放入請求隊列;
7)睡眠在請求隊列上的某個工作線程被喚醒,它往socket上寫入服務器處理客戶請求的結果;
8.3.2 Proactor模式(aio_read, aio_write)
與Reactor模型不同,Proactor模式將所有的I/O操作都交給主線程和內核來處理。工作現場僅僅負責業務邏輯,因此,Proactor模式更符合8-4中描述的服務器編程框架;
使用異步I/O模型(以aio_read和aio_write為例)實現的Proactor模式的工作流程是:
1) 主線程調用aio_read函數向內核注冊socket上的讀完成事件,並告訴內核用戶讀緩沖區的位置,以及讀操作完成時如何通知應用程序;
2)主線程繼續處理其他邏輯;
3)當socket上的數據被讀入用戶緩沖區后,內核向應用數據發送一個信號,以通知應用數據已經可用;
4)應用程序預先定義好的信號處理函數選擇一個工作線程來處理客戶請求(只負責業務邏輯處理,不負責實際的IO讀寫,實際的IO讀寫由主線程進行)。工作線程處理客戶請求之后,調用aio_write函數向內核注冊socket上寫完成事件,並告訴內核用戶寫緩沖區的位置,以及寫操作完成時如何通知應用程序;
5)主線程繼續處理其他邏輯;
6)當用戶緩沖區的數據被寫入socket之后,內核將向應用數據發送一個信號,以通知應用程序已經發送完畢;
7)應用程序預先定義好的信號處理函數選擇一個工作線程來做善后處理,比如決定是否關閉socket;
8.3.3 模擬Proactor模式
可以使用同步I/O方式模擬出Proactor模式,其原理是:主線程執行數據讀寫操作,讀寫完成之后,主線程向工作線程通知這一“完成事件”。那么從工作線程的角度來看,它們就直接獲得了數據讀寫的結果,接下來要做的是對讀寫的結果進行邏輯處理。
使用同步I/O模型(仍然以epoll_wait為例)模擬出的Proactor模式的工作流程如下:
1)主線程往epoll內核事件表中注冊socket上讀就緒事件;
2)主線程調用epoll_wait等待socket上有數據可讀;
3)當socket上有數據可讀時,epoll_wait通知主線程。主線程從socket循環讀取數據,直到沒有更多數據可讀,然后將讀取到的數據封裝成一個請求對象並插入請求隊列;
4)睡眠在請求隊列上的某個工作線程被喚醒,它獲取請求對象並處理客戶請求,然后往epoll內核事件表中注冊socket上的寫就緒事件;
5)主線程調用epoll_wait等待socket可寫;
6)當socket可寫時,epoll_wait通知主線程。主線程往socket上寫入服務器處理客戶請求的結果;
8.4 兩種高效的並發模式
並發編程的目的是讓“程序”執行多個任務。如果程序是“計算密集型”的,並發編程並沒有優勢,但如果程序時I/O密集型的,則並發編程則可以挺高CPU的利用率;
從實現來看,並發編程主要有多進程和多線程兩種方式。這一章主要討論並發模式,對應於圖8-4,並發模式是指I/O處理單元和多個邏輯單元之間協調完成任務的方法。服務器主要有兩種並發編程模式:半同步/半異步(half-sync/half-async)模式和領導者/追隨者(Leader/Followers)。
8.4.1 半同步/半異步(half-sync/half-async)模式:
這里所謂的“同步”和“異步”和I/O模型中的“同步”和“異步”是不同的概念。在I/O模型中,“同步”和“異步”區分的是內核向應用程序通知的何種I/O事件(是就緒事件還是完成事件),以及誰來完成I/O事件。在並發模式中,“同步”指的是程序完全按照代碼的序列執行:“異步”指的是程序的執行需要由系統事件來驅動。常見的系統事件包括中斷,信號等。
異步線程的執行率高,實時性強,但編寫以異步方式執行的程序相對復雜,難以調試和擴展,而且不適於大量的並發。而同步線程,執行效率相對較低,但實時性較差。
半同步/半異步模式中,同步線程用於處理客戶邏輯,相當於圖8-4的邏輯單元;異步線程用於處理I/O事件,相當於8-4中的I/O處理單元。異步線程監聽到客戶請求后,將其封裝成請求對象並插入請求隊列中。請求隊列將通知某個工作在同步模式下的工作線程來讀取並處理請求對象。具體選擇哪個線程來作為新的客戶請求服務。
在服務器程序中,如果結合考慮兩種事件處理模式和幾種I/O模型,則半同步/半異步模式就存在多種變體。其中有一種變體成為半同步/半反應堆模式,如圖8-10所示;
圖8-10中,異步線程只有一個,由主線程來充當。它負責聽所有socket上的事件。如果監聽socket上有可讀事件發生,既有新的連接請求到來,主線程就接受之,以得到新的連接socket,然后往epoll內核事件表中注冊該socket上的讀寫事件。如果連接socket上有讀寫事件,即新的客戶請求到來或有數據要發送至客戶端,主線程就將該連接socket插入請求隊列中。所有工作線程都睡眠在請求隊列中,當有任務來時,它們將通過競爭(比如申請互斥鎖)獲取任務的接管權。這種競爭機制使得只有空閑的工作線程才有機會來處理新任務這是合理的。
主線程插入請求隊列中的任務是就緒的連接socket。這說明圖8-10所示的半同步/半異步反應堆模式采用的事件處理模式是Reactor模式:它要求工作線程自己從socket上讀取客戶請求和往socket寫入服務器應答。
圖8-11描述了一種相對高效的半同步/半異步模式,它的每個工作線程都能同時處理多個客戶連接
主線程只管理監聽socket,連接socket由工作線程來管理。當有新的連接到來時,主線程就接受之並將新返回的連接socket派發給某個工作線程,此后該新socket上的任何I/O操作都由被選中的的工作線程處理,直到客戶關閉連接。主線程向工作線程派發socket的最簡單的方式,是往它和工作線程之間的管道里寫數據。工作線程檢測到管道上有數據可讀時,就分析是否是一個新的客戶連接請求到來。如果是,則將該新socket上的讀寫事件注冊到自己的epoll內核時間表中。
8.4.2 領導者/追隨者模式
領導者/追隨者模式是多個工作線程輪流獲得事件源集合,輪流監聽,分發並處理事件的一種模式。在任何時間點,程序都僅有一個領導者線程,它負責監聽I/O事件,而其他線程則都是追隨者,它們休眠在線程池中等待成為新的領導者。當前的領導者如果檢測到I/O事件,首先要從線程池推選出新的領導者線程,然后處理I/O事件。此時,新的領導者等待新的I/O事件,而原來的領導者則處理I/O事件,兩者實現了並發。
領導者/追隨者模式包含如下幾個組件:句柄集(HandleSet),線程集(ThreadSet),事件處理器(EventHandle )和具體的時間處理器(ConcreteEventHandler),它們的關系如下:
句柄集:句柄用於表示I/O資源,在Linux下通常就是一個文件描述符。句柄集管理眾多句柄,它使用wait_for_event方法來監聽這些句柄上的I/O事件。領導者將Handle和事件處理器綁定是通過調用句柄集中的register_handle方法實現的。
線程集:這個組件是所有工作線程的管理者。它負責各線程之間的同步,以及新領導者線程的推選
事件處理器和具體的事件處理器:根據上面的討論,我們將領導者/追隨者模式的工作流程總結於8-14