目錄
前言
一.服務(wù)器編程基礎(chǔ)框架
C/S模型
主要框架
二.I/O模型
阻塞I/O
非阻塞I/O
異步I/O
三.兩種高效的事件處理模式
Reactor
Proactor
四.模擬Proactor模式
五.半同步/半異步的并發(fā)模式
六.有限狀態(tài)機(jī)
七.其他提高服務(wù)器性能的方法
池
數(shù)據(jù)復(fù)制
上下文切換和鎖
前言
????????這一章是整個(gè)專(zhuān)欄的核心,也是后續(xù)章節(jié)的總覽。這一章我們會(huì)從宏觀角度上概括,解釋?zhuān)治鯳ebserver的各個(gè)部分。
????????分析原理,搭建網(wǎng)絡(luò),將一個(gè)高性能Webserver的各個(gè)部分串聯(lián)起來(lái)??梢哉f(shuō),如果沒(méi)有徹底看懂這一章的內(nèi)容,那么后面的學(xué)習(xí)都如同霧里看花,甚至不知道在干嘛,唯有將框架了解清楚,才知道什么地方該填入正確的血肉。
????????同時(shí),這一章內(nèi)容也是面試的各大高頻考點(diǎn)。
一.服務(wù)器編程基礎(chǔ)框架
C/S模型
????????TCP/IP的最初設(shè)計(jì)理念并沒(méi)有客戶(hù)端和服務(wù)端的概念,主機(jī)與主機(jī)之間大多進(jìn)行的是信息對(duì)等交換。
????????類(lèi)似于人類(lèi)古代的以物易物的交易方式
????????但隨著網(wǎng)絡(luò)的發(fā)展,有一些主機(jī)其掌握了更多的資源,其他主機(jī)想要獲得資源都要去找它獲取,這些主機(jī)慢慢發(fā)展壯大,成了我們今天的服務(wù)端,而其他的主機(jī)便是客戶(hù)端。
????????類(lèi)似于出現(xiàn)了店家,人們不再以物易物,而是去店家那獲取資源
????????而在Linux中,客戶(hù)端與服務(wù)端之間“交易方式”,如下圖所示
主要框架
????????但很顯然,上面這種交易方式并不是單獨(dú)發(fā)生,當(dāng)有多個(gè)客戶(hù)端和服務(wù)端發(fā)生“交易”時(shí),服務(wù)端就得好好設(shè)計(jì)一番,才能不耽誤與各個(gè)客戶(hù)端的“交易”過(guò)程。
于是,我們?cè)诜?wù)端內(nèi)部作好一種最基本的新體系,如下圖:
- I/O處理單元:
- 接受客戶(hù)連接
- 接收客戶(hù)數(shù)據(jù)
- 將處理好的數(shù)據(jù)還給客戶(hù)端
- 請(qǐng)求隊(duì)列:
- 并非為真正的一個(gè)隊(duì)列,而是一種單元間通信的抽象。
- 當(dāng)I/O處理單元收到客戶(hù)請(qǐng)求后,將客戶(hù)請(qǐng)求交給邏輯單元處理
- 邏輯單元:
- 通常為一個(gè)進(jìn)程或線程
- 分析客戶(hù)數(shù)據(jù),作出處理
- 將處理結(jié)果交給I/O單元(或者直接給客戶(hù)端)
二.I/O模型
????????服務(wù)端與客戶(hù)端交互的第一棒是I/O,而I/O又可以分為阻塞I/O和非阻塞I/O
阻塞I/O
????????基礎(chǔ)API中,acept,send,recv,connect可能會(huì)被阻塞,而其一旦阻塞,操作系統(tǒng)就會(huì)將其掛起,直到其等待的事件發(fā)生為止(操作系統(tǒng)基礎(chǔ)知識(shí))。
非阻塞I/O
????????非阻塞I/O的系統(tǒng)調(diào)用總是被立刻返回,無(wú)論事件是否發(fā)生,若事件沒(méi)有發(fā)生,系統(tǒng)調(diào)用就返回-1,并返回錯(cuò)誤碼errno,我們就要根據(jù)errno進(jìn)行適當(dāng)處理.
????????如:再試一次.
????????所以為了不影響非阻塞I/O的效率,非阻塞I/O就要和其他I/O通知機(jī)制配合使用,比如 I/O復(fù)用 和 SIGIO信號(hào)
I/O復(fù)用:
- 原理:應(yīng)用程序通過(guò)I/O復(fù)用函數(shù)向內(nèi)核注冊(cè)一組事件,內(nèi)核通過(guò)I/O復(fù)用函數(shù)把其中就緒的事件通知給應(yīng)用程序.
- I/O復(fù)用函數(shù)
- select
- poll
- epoll_wait
C++ Webserver從零開(kāi)始:基礎(chǔ)知識(shí)(四)——I/O復(fù)用-CSDN博客
SIGIO信號(hào):
原理:我們?yōu)槟繕?biāo)文件描述符指定一個(gè)宿主進(jìn)程,宿主進(jìn)程會(huì)捕獲到SIGIO信號(hào),當(dāng)目標(biāo)文件描述符上有事件發(fā)生時(shí),SIGIO信號(hào)的信號(hào)處理函數(shù)將被觸發(fā),于是在該信號(hào)處理函數(shù)中,我們就可以進(jìn)行非阻塞I/O操作.
C++ Webserver從零開(kāi)始:基礎(chǔ)知識(shí)(五)——信號(hào)-CSDN博客
異步I/O
????????其實(shí)從理論上來(lái)說(shuō),上述的I/O模型都是同步I/O模型,因?yàn)镮/O的讀寫(xiě)操作都是在I/O事件發(fā)生后的.而真正的異步I/O模型是用戶(hù)直接進(jìn)行I/O讀寫(xiě)操作,這些操作告訴內(nèi)核:
????????????????1讀寫(xiě)緩沖區(qū)的位置
????????????????2讀寫(xiě)完成后怎么通知應(yīng)用程序
????????所以異步I/O會(huì)"自動(dòng)"進(jìn)行I/O讀寫(xiě),不用在乎I/O事件和I/O讀寫(xiě)的順序.
????????PS:可以理解為同步I/O模型將數(shù)據(jù)的讀和寫(xiě)操作按程序員寫(xiě)的代碼來(lái)處理,而異步I/O模型將數(shù)據(jù)的讀和寫(xiě)直接交給內(nèi)核來(lái)處理,程序員只需要告訴內(nèi)核一些注意事項(xiàng)即可.
三.兩種高效的事件處理模式
????????前面我們已經(jīng)了解了服務(wù)器編程的基礎(chǔ)框架和幾種不同的I/O模型,接下來(lái)我們將兩者結(jié)合一下,就可以得到兩種高效的事件處理模式
-
- Reactor
- Proactor
????????其中,同步I/O實(shí)現(xiàn)Reactor模式,異步I/O實(shí)現(xiàn)Proactor模式.
????????但同時(shí),我們可以用同步I/O去模擬出Proactor處理模式.
Reactor
????????我們知道,服務(wù)器的內(nèi)部有多個(gè)線程在工作,那么他們的結(jié)構(gòu)是怎么樣的呢?
????????一般的結(jié)構(gòu)是將服務(wù)器的線程分為主線程和工作線程,其中主線程一直處于激活狀態(tài),而工作線程則可能睡眠,也可能工作.
在Reactor模式中:
- 主線程:
- 一直在監(jiān)聽(tīng)文件描述符上是否有事件發(fā)生,有就通知工作線程
- 工作線程:
- 接受新的連接
- 讀數(shù)據(jù)
- 處理客戶(hù)請(qǐng)求
- 寫(xiě)數(shù)據(jù)
????????PS:圖中工作線程并不是區(qū)分“讀工作線程”和“寫(xiě)工作線程”,而是為了邏輯清晰而這樣畫(huà),Reactor真正運(yùn)行的過(guò)程中,工作線程將自己根據(jù)從請(qǐng)求隊(duì)列取出的事件類(lèi)型來(lái)決定如何處理
Proactor
????????Reactor是同步I/O模型的事件處理模式(只有在讀/寫(xiě)就緒之后才能進(jìn)行數(shù)據(jù)讀/寫(xiě)操作)
????????而Proactor則需要異步I/O模型,即Proactor模式下,所有的I/O操作將由內(nèi)核來(lái)處理,工作線程只負(fù)責(zé)業(yè)務(wù)邏輯。
Proactor模式中:
- 主線程:
- 一直在監(jiān)聽(tīng)socket上的事件
- 向內(nèi)核注冊(cè)socket讀完成事件,告訴內(nèi)核用戶(hù)讀緩沖區(qū)位置,讀操作完成時(shí)該怎么通知應(yīng)用程序
- 內(nèi)核
- 在讀緩沖區(qū)上收到socket數(shù)據(jù)后,向程序發(fā)送一個(gè)信號(hào)通知,程序會(huì)根據(jù)信號(hào)處理函數(shù)選擇工作線程進(jìn)行處理
- 接受工作線程的寫(xiě)完成事件和寫(xiě)緩沖區(qū)位置
- 工作線程將寫(xiě)緩沖區(qū)的數(shù)據(jù)寫(xiě)入socket后,向程序發(fā)送一個(gè)信號(hào)通知,程序根據(jù)信號(hào)處理函數(shù)選擇一個(gè)工作線程進(jìn)行善后處理
- 工作線程:
- 處理業(yè)務(wù)邏輯
????????PS:主線程中的epoll僅僅用來(lái)檢測(cè) 監(jiān)聽(tīng)socket 上的連接請(qǐng)求事件,不能用來(lái)檢測(cè) 連接socket 上的讀寫(xiě)事件
四.模擬Proactor模式
????????通過(guò)觀察我們發(fā)現(xiàn),在Proactor模型中I/O交由內(nèi)核處理,工作線程不需要進(jìn)行數(shù)據(jù)的讀寫(xiě)。因此,我們可以用主線程來(lái)完成一部分內(nèi)核的功能來(lái)做到模擬Proactor模式。
????????即:主線程執(zhí)行數(shù)據(jù)的讀寫(xiě)操作,讀寫(xiě)完成后,主線程向工作線程通知讀寫(xiě)完成。這樣從工作線程來(lái)看,就只需進(jìn)行數(shù)據(jù)的邏輯操作了。
五.半同步/半異步的并發(fā)模式
????????CPU的并發(fā)可以顯著提升程序的性能(操作系統(tǒng)基礎(chǔ)知識(shí)),在服務(wù)器編程中,一種比較高效常見(jiàn)的并發(fā)編程模式是:半同步/半異步模式。
????????注意,這里的同步和異步與前文I/O模型的同步和異步是完全不同的概念。
??在I/O模型中:
- 同步:內(nèi)核向應(yīng)用程序通知I/O就緒事件,由應(yīng)用程序(工作線程)完成讀寫(xiě)
- 異步:內(nèi)核向應(yīng)用程序通知I/O完成事件,內(nèi)核自己完成讀寫(xiě)
在并發(fā)模式中:
- 同步:代碼按順序執(zhí)行
- 異步:代碼可能受中斷,信號(hào)等影響,交錯(cuò)地執(zhí)行
????????在服務(wù)器的設(shè)計(jì)中,我們往往采用同步線程處理客戶(hù)邏輯,異步線程處理I/O事件
????????這種并發(fā)模式結(jié)合事件處理模式和I/O模型,即可得到一種稱(chēng)為
????????????????半同步/半反應(yīng)堆(half-sync/half-reactive)模式
????????從其名字可知,其采用的是Reactor的事件處理模式
????????該模式中,只有主線程是是異步線程,工作線程都是同步線程。
????????其工作流程細(xì)節(jié):
????????主線程監(jiān)聽(tīng)所有socket上的事件,如有有可讀事件發(fā)生(新的連接到來(lái)),就獲取其連接socket,并往epoll內(nèi)核事件表中注冊(cè)這個(gè)socket上的讀寫(xiě)事件,然后將其插入請(qǐng)求隊(duì)列。工作線程將其從中取出,進(jìn)行讀操作,邏輯處理,寫(xiě)操作等。
????????如何將其改成模擬Proactor事件模式:
????????主線程將socket的中的信息讀出封裝成一個(gè)任務(wù)對(duì)象,然后將這個(gè)任務(wù)對(duì)象(或指向這個(gè)任務(wù)對(duì)象的指針)插入請(qǐng)求隊(duì)列,工作線程就只用處理,不必進(jìn)行I/O操作了。
六.有限狀態(tài)機(jī)
????????我們前面幾節(jié)討論了服務(wù)器 I/O處理單元,請(qǐng)求隊(duì)列,邏輯單元之間協(xié)調(diào)完成任務(wù)的各種模式,現(xiàn)在我們來(lái)了解一下在邏輯單元內(nèi)部的一種處理問(wèn)題的編程方法:有限狀態(tài)機(jī)。
????????在連接socket的數(shù)據(jù)被工作線程得到后,可以根據(jù)其應(yīng)用層協(xié)議的頭部類(lèi)型字段的類(lèi)型來(lái)進(jìn)行不同的處理。其中每種不同的字段可以映射成有限狀態(tài)機(jī)內(nèi)的各種狀態(tài),每種狀態(tài)下編寫(xiě)不同的處理代碼。
????????同時(shí),狀態(tài)機(jī)還需要在不同的狀態(tài)之間進(jìn)行轉(zhuǎn)移。
{
State cur_State = typeA;//1.初始狀態(tài)
while(cur_State != typeC) {//2.當(dāng)狀態(tài)變成退出狀態(tài)typeC時(shí),退出循環(huán)
Package _pack = getNewPackage();
switch(cur_State) {
case type_A:
process_package_state_A(_pack);
cur_State = typeB;//3.切換狀態(tài)
break;
case type_B:
process_package_state_B(_pack);
cur_State = typeC;//4.切換到退出狀態(tài)
break;
}
}
七.其他提高服務(wù)器性能的方法
????????前面的內(nèi)容組成了一個(gè)高性能Web服務(wù)器的基礎(chǔ)框架,下面我們?cè)俸?jiǎn)要介紹其他幾個(gè)方面:池,數(shù)據(jù)復(fù)制,上下文切換和鎖
池
????????現(xiàn)代計(jì)算機(jī)的硬件資源大都十分充裕,所以我們可以用算法里的一個(gè)基本的思想來(lái)提高效率:用空間換時(shí)間。在Web服務(wù)器里,其具象化為池。
????????所謂池是一組資源的集合,這組資源在服務(wù)器啟動(dòng)的時(shí)候就已經(jīng)初始化好了,當(dāng)服務(wù)器進(jìn)入正式運(yùn)行階段,需要某些資源,就不需要花費(fèi)大量時(shí)間去動(dòng)態(tài)申請(qǐng),使用,釋放,而直接從池里取出用即可,這就極大提高了服務(wù)器的運(yùn)行效率。
池思想面臨的困難:
我們不知道預(yù)先應(yīng)該分配多少資源,應(yīng)該怎么辦?
解決方法:
- 分配非常多的資源,在大多數(shù)情況下不可能用完(不現(xiàn)實(shí))
- 根據(jù)使用場(chǎng)景預(yù)估分配足夠多的資源,如果不夠用了,可以自動(dòng)再擴(kuò)充池
常用池:線程池,進(jìn)程池,內(nèi)存池,連接池。
數(shù)據(jù)復(fù)制
????????高性能服務(wù)器應(yīng)該避免不必要的數(shù)據(jù)復(fù)制操作,且要善用零拷貝,共享內(nèi)存等方式。
????????例如:FTP服務(wù)器,當(dāng)客戶(hù)請(qǐng)求一個(gè)文件時(shí),服務(wù)器只需要檢測(cè)目標(biāo)文件是否存在,用戶(hù)是否有讀取它的權(quán)限即可,不必關(guān)心文件內(nèi)的內(nèi)容。所以FTP服務(wù)器無(wú)需將目標(biāo)文件的內(nèi)容完整地讀到應(yīng)用程序緩沖區(qū)中并調(diào)用send來(lái)進(jìn)行發(fā)送,而是可以直接使用“零拷貝”函數(shù)send將其直接發(fā)給客戶(hù)端。
上下文切換和鎖
????????在web服務(wù)器地模型中,有人也許回想:我為每一個(gè)客戶(hù)連接都創(chuàng)建一個(gè)工作線程可行嗎?
????????答案是:不行。
????????因?yàn)檫@樣會(huì)創(chuàng)建非常多的工作線程,而CPU的數(shù)量是有限的。而CPU在并發(fā)的工作模式下,會(huì)在同的線程之間進(jìn)行切換,這種切換會(huì)消耗CPU資源,而處理業(yè)務(wù)邏輯的時(shí)間就會(huì)減少,如果線程太多,CPU就會(huì)頻繁切換上下文而真正有意義的工作將大大減少。
????????我們提高服務(wù)器運(yùn)行效率的其中一個(gè)重要方法是共享內(nèi)存,但共享內(nèi)存同時(shí)也會(huì)帶來(lái)許多安全隱患問(wèn)題,為了防止這些隱患,我們需要使用鎖。而鎖也會(huì)占用一定的系統(tǒng)資源,這種時(shí)候我們需要考慮使用粒度更小的鎖,如讀寫(xiě)鎖等來(lái)提高性能。
八.一些廢話
? ? ? ? 這篇文章我寫(xiě)了兩天,算是自己對(duì)Web服務(wù)器框架的梳理。很顯然,它并不完美也不全面,但是對(duì)于初學(xué)者來(lái)說(shuō)應(yīng)該已經(jīng)夠用了,在服務(wù)器基礎(chǔ)框架這里有許多光看十分難理解的內(nèi)容。比如:Reactor和Proactro處理模式到底是怎么搞得。這部分基本上要結(jié)合后面的實(shí)踐才能有更深的理解。但是它也足夠重要。寫(xiě)這篇文章的時(shí)候我腱鞘炎又犯了,寫(xiě)的非常痛苦。但好在寫(xiě)完了,在自己梳理一遍以后對(duì)服務(wù)器框架會(huì)更加清晰,這是光靠看所不能達(dá)到的。我推薦每一個(gè)做Web服務(wù)器的同學(xué),都能自己去梳理一遍框架,這會(huì)對(duì)你對(duì)這個(gè)項(xiàng)目的理解更加深刻。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-835901.html
? ? ? ? 后面會(huì)盡快出I/O復(fù)用和信號(hào)的文章,講解epoll和信號(hào)的有關(guān)內(nèi)容。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-835901.html
到了這里,關(guān)于C++ Webserver從零開(kāi)始:基礎(chǔ)知識(shí)(三)——Linux服務(wù)器程序框架的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!