作者:CppExplore 網(wǎng)址:http://www./CppExplore/
本章主要列舉服務(wù)器程序的各種網(wǎng)絡(luò)模型,示例程序以及性能對比后面再寫。
一、分類依據(jù)。服務(wù)器的網(wǎng)絡(luò)模型分類主要依據(jù)以下幾點
(1)是否阻塞方式處理請求,是否多路復(fù)用,使用哪種多路復(fù)用函數(shù)
(2)是否多線程,多線程間如何組織
(3)是否多進(jìn)程,多進(jìn)程的切入點一般都是accept函數(shù)前
二、分類。首先根據(jù)是否多路復(fù)用分為三大類:
(1)阻塞式模型
(2)多路復(fù)用模型
(3)實時信號模型
三、詳細(xì)分類。
1、阻塞式模型根據(jù)是否多線程分四類:
(1)單線程處理。實現(xiàn)可以參見http://www./CppExplore/archive/2008/03/14/44509.html后面的示例代碼。
(2)一個請求一個線程。
主線程阻塞在accept處,新連接到來,實時生成線程處理新連接。受限于進(jìn)程的線程數(shù),以及實時創(chuàng)建線程的開銷,過多線程后上下文切換的開銷,該模型也就是有學(xué)習(xí)上價值。
(3)預(yù)派生一定數(shù)量線程,并且所有線程阻塞在accept處。
該模型與下面的(4)類似與線程的領(lǐng)導(dǎo)者/追隨者模型。
傳統(tǒng)的看法認(rèn)為多進(jìn)程(linux上線程仍然是進(jìn)程方式)同時阻塞在accept處,當(dāng)新連接到來時會有“驚群”現(xiàn)象發(fā)生,即所有都被激活,之后有一個獲取連接描述符返回,其它再次轉(zhuǎn)為睡眠。linux從2.2.9版本開始就不再存在這個問題,只會有一個被激活,其它平臺依舊可能有這個問題,甚至是不支持所有進(jìn)程直接在accept阻塞。
(4)預(yù)派生一定數(shù)量線程,并且所有線程阻塞在accept前的線程鎖處。
一次只有一個線程能阻塞在accept處。避免不支持所有線程直接阻塞在accept,并且避免驚群問題。特別是當(dāng)前l(fā)inux2.6的線程庫下,模型(3)沒有存在的價值了。另有文件鎖方式,不具有通用性,并且效率也不高,不再單獨列舉。
(5)主線程處理accept,預(yù)派生多個線程(線程池)處理連接。
類似與線程的半同步/半異步模型。
主線程的accept返回后,將clientfd放入預(yù)派生線程的線程消息隊列,線程池讀取線程消息隊列處理clientfd。主線程只處理accept,可以快速返回繼續(xù)調(diào)用accept,可以避免連接爆發(fā)情況的拒絕連接問題,另加大線程消息隊列的長度,可以有效減少線程消息隊列處的系統(tǒng)調(diào)用次數(shù)。
(6)預(yù)派生多線程阻塞在accept處,每個線程又有預(yù)派生線程專門處理連接。
(3)和(4)/(5)的復(fù)合體。
經(jīng)測試,(5)中的accept線程處理能力非常強,遠(yuǎn)遠(yuǎn)大于業(yè)務(wù)線程,并發(fā)10000的連接數(shù)也毫無影響,因此該模型沒有實際意義。
總結(jié):就前五模型而言,性能最好的是模型(5)。模型(3)/(4)可以一定程度上改善模型(1)的處理性能,處理爆發(fā)繁忙的連接,仍然不理想。。阻塞式模型因為讀的阻塞性,容易受到攻擊,一個死連接(建立連接但是不發(fā)送數(shù)據(jù)的連接)就可以導(dǎo)致業(yè)務(wù)線程死掉。因此內(nèi)部服務(wù)器的交互可以采用這類模型,對外的服務(wù)不適合。優(yōu)先(5),然后是(4),然后是(1),其它不考慮。
2、多路復(fù)用模型根據(jù)多路復(fù)用點、是否多線程分類:
以下各個模型依據(jù)選用select/poll/epoll又都細(xì)分為3類。下面?zhèn)€別術(shù)語采用select中的,僅為說明。
(1)accept函數(shù)在多路復(fù)用函數(shù)之前,主線程在accept處阻塞,多個從線程在多路復(fù)用函數(shù)處阻塞。主線程和從線程通過管道通訊,主線程通過管道依次將連接的clientfd寫入對應(yīng)從線程管道,從線程把管道的讀端pipefd作為fd_set的第一個描述符,如pipefd可讀,則讀數(shù)據(jù),根據(jù)預(yù)定義格式分解出clientfd放入fd_set,如果clientfd可讀,則read之后處理業(yè)務(wù)。
此方法可以避免select的fd_set上限限制,具體機器上select可以支持多少個描述符,可以通過打印sizeof(fd_set)查看,我機器上是512字節(jié),則支持512×8=4096個。為了支持多余4096的連接數(shù),此模型下就可以創(chuàng)建多個從線程分別多路復(fù)用,主線程accept后平均放入(順序循環(huán))各個線程的管道中。創(chuàng)建5個從線程以其對應(yīng)管道,就可以支持2w的連接,足夠了。另一方面相對與單線程的select,單一連接可讀的時候,還可以減少循環(huán)掃描fd_set的次數(shù)。單線程下要掃描所有fd_set(如果再最后),該模型下,只需要掃描所在線程的fd_set就可。
(2)accept函數(shù)在多路復(fù)用函數(shù)之前,與(1)的差別在于,主線程不直接與從線程通過管道通訊,而是將獲取的fd放入另一緩存線程的線程消息隊列,緩存線程讀消息隊列,然后通過管道與從線程通訊。
目的在主線程中減少系統(tǒng)調(diào)用,加快accept的處理,避免連接爆發(fā)情況下的拒絕連接。
(3)多路復(fù)用函數(shù)在accept之前。多路復(fù)用函數(shù)返回,如果可讀的是serverfd,則accept,其它則read,后處理業(yè)務(wù),這是多路復(fù)用通用的模型,也是經(jīng)典的reactor模型。
(4)連接在單獨線程中處理。
以上(1)(2)(3)都可以在檢測到cliendfd可讀的時候,把描述符寫入另一線程(也可以是線程池)的線程消息隊列,另一線程(或線程池)負(fù)責(zé)read,后處理業(yè)務(wù)。
(5)業(yè)務(wù)線程獨立,下面的網(wǎng)絡(luò)層讀取結(jié)束后通知業(yè)務(wù)線程。
以上(1)(2)(3)(4)中都可以將業(yè)務(wù)線程(可以是線程池)獨立,事先告之(1)、(2)、(3)、(4)中read所在線程(上面1、2、4都可以是線程池),需要讀取的字符串結(jié)束標(biāo)志或者需要讀取的字符串個數(shù),讀取結(jié)束,則將clientfd/buffer指針放入業(yè)務(wù)線程的線程消息隊列,業(yè)務(wù)線程讀取消息隊列處理業(yè)務(wù)。這也就是經(jīng)典的proactor模擬。
總結(jié):模型(1)是拓展select處理能力不錯選擇;模型(2)是模型(1)在爆發(fā)連接下的調(diào)整版本;模型(3)是經(jīng)典的reactor,epoll在該模型下性能就已經(jīng)很好,而select/poll仍然存在爆發(fā)連接的拒絕連接情況;模型(4)(5)則是方便業(yè)務(wù)處理,對模型(3)進(jìn)行多線程調(diào)整的版本。帶有復(fù)雜業(yè)務(wù)處理的情況下推薦模型(5)。根據(jù)測試顯示,使用epoll的時候,模型(1)(2)相對(3)沒有明顯的性能優(yōu)勢,(1)由于主線程兩次的系統(tǒng)調(diào)用,反而性能下降。
3、實時信號模型:
使用fcntl的F_SETSIG操作,把描述符可讀的信號由不可靠的SIGIO(SYSTEM V)或者SIGPOLL(BSD)換成可靠信號。即可成為替代多路復(fù)用的方式。優(yōu)于select/poll,特別是在大量死連接存在的情況下,但不及epoll。
四、多進(jìn)程的參與的方式
(1)fork模型。fork后所有進(jìn)程直接在accept阻塞。以上主線程在accept阻塞的都可以在accept前fork為多進(jìn)程。同樣面臨驚群問題。
(2)fork模型。fork后所有進(jìn)程阻塞在accept前的線程鎖處。同線程中一樣避免不支持所有進(jìn)程直接阻塞在accept或者驚群問題,所有進(jìn)程阻塞在共享內(nèi)存上實現(xiàn)的線程互斥鎖。
(3)業(yè)務(wù)和網(wǎng)絡(luò)層分離為不同進(jìn)程模型。這個模型可能是受unix簡單哲學(xué)的影響,一個進(jìn)程完成一件事情,復(fù)雜的事情通過多個進(jìn)程結(jié)合管道完成。我見過進(jìn)程方式的商業(yè)協(xié)議棧實現(xiàn)。自己暫時還沒有寫該模型的示例程序測試對比性能。
(4)均衡負(fù)載模型。起多個進(jìn)程綁定到不同的服務(wù)端口,前端部署lvs等均衡負(fù)載系統(tǒng),暴露一個網(wǎng)絡(luò)地址,后端映射到不同的進(jìn)程,實現(xiàn)可擴展的多進(jìn)程方案。
總結(jié):個人認(rèn)為(1)(2)沒什么意義。(3)暫不評價。(4)則是均衡負(fù)載方案,和以上所有方案不沖突。
以上模型的代碼示例以及性能對比后面給出。