服務器編程框架
服務器程序種類有很多,但是基本框架都一樣,核心不同點在于邏輯處理單元。基本框架包含:I/O處理單元、邏輯單元、網絡存儲單元以及請求隊列。
- I/O處理單元(主線程):服務器用來管理客戶連接的模塊。通常需要等待并接受客戶連接,接受客戶數據,將服務器響應返回給客戶端。但是,數據的收發也可能在邏輯單元(工作線程)中完成。(數據收發在I/O單元上一個典型的事件處理模式就是模擬Proactor)
- 邏輯單元:分析處理客戶數據,將結果給I/O或者給客戶端,服務器通常擁有多個邏輯單元實現對多個客戶任務的并行處理
- 網絡存儲單元可以是數據庫、緩存或者文件,甚至是一臺獨立的服務器,也不是必須的比如ssh
- 請求隊列是各服務器之間預先建立的、靜態的、永久的TCP連接,通常被設計為池的一部分,負責以上個單元的通信
I/O模型
- 同步I/O模型:比如阻塞I/O、I/O復用,即I/O的讀寫操作都是由應用程序來完成的,也就是說同步IO模型要求用戶代碼自動執行IO操作(即將數據從內核緩沖區讀入或者寫入用戶緩沖區)
- 異步I/O模型:用戶可以之間對IO執行讀寫操作,看起來是這樣但是實際上用戶只是處理了客戶端的請求,沒有真正執行IO操作,它實際上只告訴內核用戶緩沖區的位置以及IO操作完成后如何通知應用程序進行剩下的邏輯處理,然后內核開始執行IO操作
- 綜上,對于應用程序來說,同步IO模型告訴他的是事件是否就緒的信息,異步IO則是告訴他事件被完成的消息
高效的事件處理模式
Reactor模式(同步IO模型):
?????????要求主線程(I/O處理單元)只負責監聽fd上是否有事件發生,有的話立即將事件通知給工作線程(邏輯單元),邏輯單元進行讀寫數據(用戶代碼執行IO操作),接受新的連接以及處理客戶請求,并將結果寫入到socket上返回給客戶。工作流程如下(以一次請求為例):
- 主線程往epoll注冊socket上的讀就緒事件
- 主線程調用epoll_wait監聽,等待socket上有數據可讀
- 當socket上有數據可讀,epoll_wait通知主線程,主線程將事件放入請求隊列喚醒工作線程處理
- 某個工作線程從socket讀取數據,然后處理客戶請求(協議解析 、處理業務邏輯比如數據庫查詢),并往epoll上注冊socket上的寫就緒事件
- 主線程調用epoll_wait監聽寫就緒事件
- epoll_wait通知主線程寫就緒,主線程將寫事件放入請求隊列
- 請求隊列上某個線程被喚醒,往socket寫入客戶請求的結果
Proactor(異步I/O模型):
? ? ? ? 所有的I/O操作交給內核處理,工作線程負責處理業務邏輯,工作流程如下(以aio_read/aio_read為例):
- 主線程調用aio_read向內核注冊讀完成事件,并且告訴內核用戶緩沖區的位置以及讀操作完成后如何通知應用程序
- 主線程繼續處理其他邏輯
- 當socket上的數據被讀入用戶緩沖區(這里說明一下,從socket的上讀數據,本質上是通過系統調用從內核緩沖區讀取到用戶緩沖區)后,內核通知應用程序告訴他可用
- 應用程序選擇一個工作線程進行業務處理比如協議解析、查詢數據庫、業務判斷等,處理完之后調用aio_read向內核注冊一個寫完成事件,并告訴內核用戶緩沖區的位置,以及寫操作完成后如何通知應用成
- 主線程繼續執行其他邏輯
- 當用戶緩沖區被寫入socket后,內核通知應用程序,結果已經發送完畢,應用程序選擇一個工作線程進行善后,決定是否關閉socket
????????綜上,連接socket上的事件是由系統調用aio_read/aio_rea注冊到內核的,主線程只調用epoll_wait只用來檢測和監聽socket上的連接請求事件
模擬Proator:
? ? ? ? 這就是使用同步I/O模擬出Proator的方法,主線程“充當”內核,工作線程依舊只處理業務邏輯。主線程往socket上注冊事件,主線程調用epoll_wait()檢測和監聽事件,主線程處理讀寫事件。
兩種高效的并發模式(針對I/O密集型,比如經常讀寫數據或訪問數據庫)
? ? ? ?首先I/O模型中的同步和異步用來區分:
? ? ? ? 第一,內核向應用程序通知的是何種IO事件,是就緒事件還是完成事件
? ? ? ? 第二,由誰來完成IO讀寫操作,內核還是用戶代碼(應用程序)。
? ? ? ? 在并發模式中,同步指的是程序完全按照代碼的順序去執行;“異步”只程序的執行由系統事件驅動。
半同步/半異步:?
????????同步線程用于處理客戶邏輯,異步線程用于處理I/O事件。異步線程監聽到客戶請求后,將其封裝插入請求隊列,請求隊列通知某個工作在同步模式的線程讀取并處理該請求對象。
? ? ? ? 同屬于該模式的還有:
- 半同步/半反應堆(結合了Reactor模式):工作過程見Reacor模式。 當然了也可以結合模擬proactor,主線程完成IO操作
- 主線程只負責管理監聽socket,連接socket由工作線程來管理,并且該socket上的任何IO操作都由該工作線程負責,直到客戶端關閉連接。簡單來說,主線程監聽到一個客戶端的連接,就把一個客戶端交給一個工作線程,就好像發任務一樣,每個人對接一個客戶,處理該客戶的所有請求。所以,工作線程也是會維持自己的事件循環。
領導者/追隨者
? ? ? ? 多個工作線程輪流獲得事件源,輪流監聽,分發并處理事件。在任意時間點,程序都有一個線程成為領導者,負責監聽I/O事件。當前的領導者如果監聽到I/O事件,首先從線程池中推選新的領導者,然后自己處理事件。此時,新的領導者在監聽,舊領導者在執行實現了并發。
? ? ? ? 當然了,領導者在監聽到事件時指定一個追隨者來處理事件,此時領導者不變,繼續監聽。