服務器有三個主要模塊:
(1)I/O處理單元
(2)邏輯單元
(3)存儲單元
1.服務器模型
C/S模型
邏輯:服務器啟動后,首先創建一個或多個監聽socket,並調用bind函數將其綁定到服務器感興趣的端口上,然后調用listen函數等待客戶連接。
服務器運行穩定后,客戶端就可以調用connect函數向服務器發起連接了。
P2P模型
P2P模型使得每台機器在消耗服務的同時也給別人提供服務,這樣資源能夠充分、自由的共享。
2.服務器編程框架
包含:
I/O處理單元 請求隊列 邏輯單元 請求隊列 網絡存儲單元
I/O處理單元:處理客戶連接,讀寫網絡數據
邏輯單元:業務進程或線程
網絡存儲單元:本地數據庫、文件或緩存
請求隊列:各單元之間的通信方式
3. I/O模型
阻塞I/O執行的系統調用可能因為無法立即完成而被操作系統掛起,知道等待的事件發生為止。可能阻塞的系統調用包括:accept,send,recv和connect。
非阻塞I/O執行的系統調用則總是立即返回,而不管時間是否發生。如果事件沒有立即發生,這些系統調用就返回-1,和出錯的情況一樣。必須根據errno來區分這兩種情況。
對accept,send和recv而言,事件未發生時errno通常被設置成EAGAIN或者EWOULDBLOCK:對connect而言,errno則被設置成EINPROGRESS。
顯然,只有在事件已經發生的情況下操作非阻塞I/O,才能提高程序的效率。I/O通知機制有:I/O復用和SIGIO信號。
I/O復用:應用程序通過I/O復用函數向內核注冊一組事件,內核通過I/O復用函數把其中就緒的事件通知給應用程序。Linux上常用I/O復用函數是selcet、poll和epoll_wait。I/O復用本身是阻塞的,能提高效率的原因是
它們具有同時監聽多個I/O事件的能力。
SIGIO信號:可以為一個目標文件描述符指定宿主進程,那么被指定的宿主進程將捕獲到SIGIO信號。這樣,當目標文件描述符上有事件發生時,SIGIO信號的信號處理函數將被觸發,可以在該信號處理函數中對目標文件描述符執行非阻塞I/O操作了。
理論上說,阻塞I/O,I/O復用和信號驅動I/O都是I/O同步模型。在這三種模型中,I/O的讀寫操作,都是I/O事件發生之后,由應用程序來完成的。
對異步I/O而言,用戶可以直接對I/O執行讀寫操作,這些操作告訴內核用戶讀寫緩沖區的位置,以及I/O操作完成之后內核通知應用程序的方式,異步I/O的讀寫操作總是立即返回,而不論I/O是否是阻塞的,因為真正的讀寫操作已經由內核接管。
也就是說,同步I/O模型要求用戶代碼自行執行I/O操作(將數據從內核緩沖區讀入用戶緩沖區,或將數據從用戶緩沖區寫入內核緩沖區),而異步I/O機制則由內核來執行I/O操作(數據在內核緩沖區和用戶緩沖區之間的移動是由內核在“后台”完成的)。
同步I/O向應用程序通知的是I/O就緒事件,而異步I/O向應用程序通知的是I/O完成事件。
4.兩種高效的事件處理模式
使用同步I/O模型實現Reactor模式:
(1)主線程往epoll內核事件表中 注冊socket上的讀就緒事件。
(2)主線程調用epoll_wait等待socket上有數據可讀。
(3)當socket上有數據可讀時,epoll_wait通知主線程,主線程將socket事件放入請求隊列中。
(4)睡眠在請求隊列上的某個工作線程被喚醒,從socket讀取數據,並處理客戶請求,然后往epoll內核事件表中注冊該事件的寫就緒事件。
(5)主線程調用epoll_wait等待socket可寫。
(6)當socket可寫時,epoll_wait通知主線程。主線程將socket可寫事件放入請求隊列。
(7)睡眠在請求隊列上的某個工作線程被喚醒,它往socket上寫入服務器處理客戶請求的結果。
Proactor模式
Proactor模式將所有I/O操作都交給主線程和內核來處理,工作線程僅僅負責業務邏輯。
半同步/半異步:
同步線程用於處理客戶邏輯,異步線程用於處理I/O事件。
異步線程由主線程充當。負責監聽所有socket上的事件。若監聽socket上有可讀事件發生,即有新的連接到來,主線程就接受之以得到新的連接socket,然后往epoll內核事件表中注冊該socket上的讀寫事件。
如果連接socket上有讀寫事件發生,即有新的客戶請求到來或有數據要發送到客戶端,主線程就將該連接socket插入請求隊列中。所有工作線程都睡眠在請求隊列上,當有任務帶來時,將通過競爭獲得任務的接管權。
缺點:
主線程和工作線程共享請求隊列。主線程往請求隊列中添加任務,或者工作線程從請求隊列中取出任務,都需要對請求隊列加鎖保護,從而拜拜浪費CPU時間。
每個工作線程在同一時間只能處理一個客戶請求。