網絡編程教程(六)Linux服務器編程框架

 

一、服務器編程框架

模塊 單個服務器程序 服務器機羣
I/O處理單元 處理客戶連接,讀寫網絡數據 作爲接入服務器,實現負載均衡
邏輯單元 業務進程或線程 邏輯服務器
網絡存儲單元 本地數據庫 、文件或緩存 數據庫服務器
請求隊列 各單元之間的通信方式 各服務器之間的永久TCP連接

        I/O處理單元是服務器管理客戶連接的模塊。主要完成以下工作:等待並接受新的客戶連接,接受客戶數據,將服務器響應數據返回給客戶端。但是,數據的收發不一定在I/O處理單元中執行,也可能在邏輯單元中執行,具體在何處執行取決於事件處理模式。對於一個服務器機羣來說,它實現負載均衡,從所有邏輯服務器中選取負荷最小的一臺來爲新客戶服務。

        一個邏輯單元通常是一個線程或者進程。它分析並處理客戶數據,然後將結果傳遞給I/O處理單元或者直接發送給客戶端。對服務器機羣而言,一個邏輯單元本身是一臺邏輯服務器。服務器通常擁有多個邏輯單元,以實現對多個客戶任務的並行處理。

網絡存儲單元可以使數據庫、緩存和文件,甚至是一臺獨立的服務器。但它不是必須的,比如ssh、telnet等登錄服務就不需要這個單元。

       請求隊列是各單元之間的通信方式的抽象。I/O處理單元收到客戶請求時,需要以某種方式通知一個邏輯單元來處理該請求。同樣,多個邏輯單元同時訪問同一個存儲單元時,也需要採用某種機制來協調處理競態條件。請求隊列通常實現爲池的一部分。

二、I/O模型

1.阻塞I/O和非阻塞I/O

        阻塞I/O執行的系統調用可能因爲無法立即完成而被操作系統掛起,直到等待的事件發生爲止。非阻塞I/O執行的系統調用總是立即返回,而不管事件是否已經發生,如果事件沒有立即發生則返回-1,和出錯情況一樣,此時可根據errno來區分這兩種情況。對accept/send和recv而言,事件未發生時errno通常被設置爲EAGAIN或者EWOULDBLOCK;對於connect而言,errno則被設置成EINPROGRESS。非阻塞I/O一般和I/O通知機制一起使用,如I/O複用和SIGIO信號。

2.I/O複用

       I/O複用是最常使用的I/O通知機制,指定是應用程序通過I/O複用函數向內核註冊一組事件,內核通過I/O複用函數把其中就緒的事件通知給應用程序。Linux上常用的I/O複用函數是select、poll和epoll_wait。I/O複用函數本身是阻塞的,它們能提高程序效率的原因在於它們具有同時監聽多個I/O事件的能力。

3.四種I/O模型

       阻塞I/O、I/O複用、信號驅動I/O這三者都是同步I/O模型。

I/O模型 讀寫操作和阻塞階段
阻塞I/O 程序阻塞與讀寫函數
I/O複用 程序阻塞與I/O複用系統調用,但可同時監聽多個I/O事件。對I/O本身的讀寫操作是非阻塞的
SIGIO信號 ·信號觸發讀寫就緒事件,用戶程序執行讀寫操作,程序沒有阻塞階段
異步I/O 內核執行讀寫操作並觸發讀寫完成事件。程序沒有阻塞階段

三、兩種高效的事件處理模式

      服務器程序通常要處理三類事件:I/O事件、信號和定時事件。兩種高效的事件處理模式:Reactor和Proactor。一般地,同步I/O模型常用於實現Reactor模式,異步I/O模型則用於實現Proactor模式。

1.Reactor模式

        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上寫入服務器處理客戶請求的結果。

2.Proactor模式

         Proactor模式將所有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.

3.兩者的區別

        Reactor模式適用於耗時短的處理場景,同時接受多個服務請求,並且一次同步的處理它們的事件驅動程序。

        Proactor則適用於耗時長的處理場景,異步接受和同時處理多個服務器請求的事件驅動程序。

四、兩種高效的併發模式

         併發編程的目的是讓程序“同時”執行多個任務。而併發模式是指I/O處理單元和多個邏輯單元之間協調完成任務的方法。

1.半同步/半異步模式

        同步是指程序完全按照代碼序列的順序執行;異步是指程序的執行需要有系統事件來驅動,常見的系統事件包括中斷、信號等。

        一種高效的半同步/半異步模式是指,主線程只管理監聽socket,連接socket由工作線程來管理。主線程是異步的,工作線程也是異步的,這不是嚴格意義上的半同步/半異步.

 

2.領導者/追隨者模式

        領導者/追隨者模式是多個工作線程輪流獲得事件源集合,輪流監聽、分發並處理時間的一種模式。在任意時間點,程序都僅有一個領導者,它負責監聽I/O事件,而其他線程則都是追隨者,他們休眠在線程池中等待稱爲新的領導者。當前的領導者如果檢測到I/O事件,首先要衝線程池中推選出新的領導者線程,然後處理I/O事件。此時,新的領導者等待新的I/O事件,而原來的領導者則處理I/O事件,二者實現了併發。

五、提高服務器性能的其他建議

        高性能服務器要注意的幾個方面:池、數據複製、上下文切換和鎖。

1.池

    池是一組資源的集合,這組資源在服務器啓動之初就完全被創建好並初始化,這稱爲靜態資源分配。當拂去其正式運行階段,即開始處理客戶請求的時候,如果需要相關資源,則可以直接從池中獲取,無需動態分配。因爲直接從池中獲取資源比動態分配資源的速度要快的多,因爲分配系統資源的系統調用都是很耗時的,涉及到用戶態和內核態的來回切換。當服務器處理完一個客戶連接以後,可以把相關資源放回池中,無需執行系統調用來釋放資源。

        常見的池有內存池、進程池、線程池和連接池。

(1)內存池

        內存池通常用於socket的接收緩衝和發送緩衝。內存池的大小可以根據情況進行分配,一種是預先分配好固定的內存池大小,另一種是根據情況動態擴大接收緩衝區。

(2)進程池/線程池

        進程池和線程池都是用於併發的手段,當我們需要一個工作進程或工作線程來處理新到來的客戶請求時,我們可以直接從進程的池或線程池中取得一個執行實體,而無須動態調用fork或pthread_create等函數來創建進程和線程。

(3)連接池

        連接池通常用於服務器或服務器機羣的內部永久連接。如每個邏輯單元都可能需要頻繁訪問本地的某個數據庫。簡單做法是:邏輯單元每次需要訪問數據庫的時候,就像數據庫程序發起連接,而訪問完畢後釋放連接。這種做法效率太低。一種解決方案是使用連接池。連接池是服務器預先和數據庫程序建立的一組連接的集合。當某個邏輯單元需要訪問數據庫時,它可以直接從連接池中取得一個連接的實體並使用之。待完成後再返回給連接池。

2.數據複製

        儘量使用“零拷貝”函數,如sendfile()、tee()等,從而避免數據在用戶空間和內核空間的來回拷貝,提高效率。

3.上下文切換和鎖

        不管是多進程還是多線程,數量都不應該太多,否則可能出現進程間或線程間切換佔用大量的CPU時間,從而降低效率。

       併發程序中考慮的另外一個問題是共享資源的加鎖保護,加的鎖應粒度儘可能的小。

 

補充:socket的基礎API中,可能被阻塞的系統調用包括accept、send、recv和connect.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章