Linux基礎(chǔ)(五)——項目實戰(zhàn)與總結(jié)
一、阻塞和非阻塞、同步和異步

阻塞:當(dāng)代碼執(zhí)行一個需要等待某些操作完成的任務(wù)時,它會一直等待,直到操作完成后才繼續(xù)執(zhí)行下一步操作。
非阻塞:當(dāng)代碼執(zhí)行一個需要等待某些操作完成的任務(wù)時,它會立即返回并繼續(xù)執(zhí)行下一步操作,而不是等待操作完成。
同步和異步通常用來描述代碼執(zhí)行的方式:
同步:當(dāng)代碼執(zhí)行一個任務(wù)時,它會一直等待該任務(wù)完成后才繼續(xù)執(zhí)行下一步操作。
異步:當(dāng)代碼執(zhí)行一個任務(wù)時,它會繼續(xù)執(zhí)行下一步操作,而不是等待任務(wù)完成。在任務(wù)完成后,代碼會得到通知,然后再執(zhí)行相應(yīng)的操作。同步阻塞、同步非阻塞、異步阻塞、異步非阻塞是四種常見的代碼執(zhí)行方式,它們描述了代碼在等待任務(wù)完成時的行為以及執(zhí)行方式。
同步阻塞:代碼執(zhí)行一個任務(wù)時,會一直等待任務(wù)完成后才繼續(xù)執(zhí)行下一步操作。在等待任務(wù)完成期間,代碼會被阻塞,不能執(zhí)行其他操作。
同步非阻塞:代碼執(zhí)行一個任務(wù)時,會立即返回并繼續(xù)執(zhí)行下一步操作,而不是等待任務(wù)完成。但是,在等待任務(wù)完成期間,代碼會不斷輪詢?nèi)蝿?wù)狀態(tài),直到任務(wù)完成。
異步阻塞:代碼執(zhí)行一個任務(wù)時,不會等待任務(wù)完成,而是繼續(xù)執(zhí)行下一步操作。但是,在任務(wù)完成之前,代碼會被阻塞,不能執(zhí)行其他操作。
異步非阻塞:代碼執(zhí)行一個任務(wù)時,不會等待任務(wù)完成,而是繼續(xù)執(zhí)行下一步操作。在等待任務(wù)完成期間,代碼會進行其他操作。當(dāng)任務(wù)完成時,代碼會得到通知,并執(zhí)行相應(yīng)的操作。
這四種執(zhí)行方式都有其適用場景和優(yōu)缺點。例如,同步阻塞適用于對執(zhí)行順序和結(jié)果準確性要求較高的場景,但是可能會導(dǎo)致性能問題和資源浪費;異步非阻塞適用于對性能要求較高的場景,但是可能會導(dǎo)致代碼復(fù)雜度增加。開發(fā)人員應(yīng)根據(jù)實際情況選擇適合的執(zhí)行方式。

二、Unix、Linux上的五種IO模型

在阻塞IO模型中,當(dāng)應(yīng)用程序調(diào)用一個IO操作(如讀取數(shù)據(jù))時,如果沒有數(shù)據(jù)可讀,則應(yīng)用程序會被阻塞,直到有數(shù)據(jù)可讀或發(fā)生錯誤。在這個過程中,應(yīng)用程序無法執(zhí)行其他操作,直到IO操作完成。
阻塞IO模型的優(yōu)點是簡單易懂,容易實現(xiàn)和使用。但是,它的缺點也很明顯:由于應(yīng)用程序在IO操作期間被阻塞,因此它無法同時處理其他任務(wù),造成了資源浪費和性能瓶頸。
對于網(wǎng)絡(luò)編程來說,阻塞IO模型也有其局限性。當(dāng)一個應(yīng)用程序在等待數(shù)據(jù)時,它會一直等待,直到有數(shù)據(jù)可讀或發(fā)生錯誤。這可能會導(dǎo)致應(yīng)用程序的響應(yīng)時間變慢,尤其是在高并發(fā)的情況下。
因此,在高并發(fā)、高性能的網(wǎng)絡(luò)編程中,通常需要使用其他更高級的IO模型,如IO多路復(fù)用、非阻塞IO、異步IO等,以提高系統(tǒng)的性能和響應(yīng)能力。

在非阻塞IO模型中,當(dāng)應(yīng)用程序調(diào)用一個IO操作時,如果沒有數(shù)據(jù)可讀,則應(yīng)用程序不會被阻塞,而是立即返回一個錯誤碼(如EAGAIN或EWOULDBLOCK),表示當(dāng)前沒有數(shù)據(jù)可讀。應(yīng)用程序可以繼續(xù)執(zhí)行其他任務(wù),直到后續(xù)再次嘗試IO操作。
在這個過程中,應(yīng)用程序需要不斷地輪詢IO狀態(tài),以確定是否有數(shù)據(jù)可讀或?qū)懭搿km然非阻塞IO模型可以避免阻塞,提高應(yīng)用程序的響應(yīng)能力,但它需要應(yīng)用程序不斷地輪詢IO狀態(tài),造成CPU資源浪費。
對于網(wǎng)絡(luò)編程來說,非阻塞IO模型的應(yīng)用場景較為有限。當(dāng)應(yīng)用程序需要同時處理多個連接時,使用非阻塞IO模型時,應(yīng)用程序需要不斷輪詢每個連接的IO狀態(tài),這可能會導(dǎo)致系統(tǒng)的性能下降。因此,在高并發(fā)、高性能的網(wǎng)絡(luò)編程中,通常需要使用其他更高級的IO模型,如IO多路復(fù)用、異步IO等。

在IO復(fù)用模型中,應(yīng)用程序可以將多個IO操作注冊到一個IO多路復(fù)用器中,例如select、poll或epoll。IO多路復(fù)用器會不斷輪詢這些IO操作的狀態(tài),當(dāng)有數(shù)據(jù)可讀或可寫時,IO多路復(fù)用器會通知應(yīng)用程序進行操作。
使用IO復(fù)用模型時,應(yīng)用程序可以同時處理多個IO操作,而不必為每個IO操作創(chuàng)建一個獨立的線程或進程。這可以減少線程或進程的數(shù)量,從而減少系統(tǒng)開銷,提高系統(tǒng)的吞吐量和響應(yīng)能力。
IO復(fù)用模型的缺點是,應(yīng)用程序需要不斷輪詢IO狀態(tài),造成CPU資源浪費。此外,由于IO多路復(fù)用器需要維護多個IO操作,因此在高并發(fā)的情況下,IO多路復(fù)用器可能會成為瓶頸,導(dǎo)致系統(tǒng)的性能下降。
總體來說,IO復(fù)用模型適用于需要同時處理多個IO操作、但IO操作之間的關(guān)系比較簡單的場景,例如網(wǎng)絡(luò)服務(wù)器、文件服務(wù)器等。在高并發(fā)、高性能的網(wǎng)絡(luò)編程中,IO復(fù)用模型是一個比較常用的IO模型。

在信號驅(qū)動IO模型中,應(yīng)用程序首先要通過signal函數(shù)將一個信號(如SIGIO)與一個文件描述符關(guān)聯(lián)起來。當(dāng)IO操作完成時,操作系統(tǒng)會向應(yīng)用程序發(fā)送一個SIGIO信號,應(yīng)用程序可以通過信號處理函數(shù)來處理這個信號,從而完成IO操作。
使用信號驅(qū)動IO模型時,應(yīng)用程序可以在等待IO操作完成時進行其他操作,而不必被阻塞。信號驅(qū)動IO模型可以提高應(yīng)用程序的響應(yīng)能力,同時避免了輪詢IO狀態(tài)的CPU資源浪費。
但是,信號驅(qū)動IO模型也有其缺點。由于信號是異步的,因此應(yīng)用程序需要使用信號處理函數(shù)來處理信號,而信號處理函數(shù)可能會影響應(yīng)用程序的可維護性和可讀性。此外,在高并發(fā)、高性能的網(wǎng)絡(luò)編程中,信號驅(qū)動IO模型可能會出現(xiàn)一些問題,例如信號處理函數(shù)的競爭條件等。
總體來說,信號驅(qū)動IO模型適用于需要避免阻塞和輪詢IO狀態(tài)的場景,例如實時媒體流、網(wǎng)絡(luò)游戲等。在高并發(fā)、高性能的網(wǎng)絡(luò)編程中,信號驅(qū)動IO模型通常使用較少,而更常用的是IO復(fù)用模型和異步IO模型。

在異步IO模型中,應(yīng)用程序可以通過aio_read和aio_write等異步IO函數(shù)發(fā)起IO操作,然后立即返回。當(dāng)IO操作完成時,操作系統(tǒng)會通知應(yīng)用程序,應(yīng)用程序可以在回調(diào)函數(shù)中處理數(shù)據(jù)。
使用異步IO模型時,應(yīng)用程序可以在等待IO操作完成時繼續(xù)執(zhí)行其他操作,而不必被阻塞或輪詢IO狀態(tài)。異步IO模型可以提高應(yīng)用程序的響應(yīng)能力和吞吐量,同時減少CPU資源的浪費。
但是,異步IO模型也有其缺點。異步IO模型的實現(xiàn)比較復(fù)雜,需要使用操作系統(tǒng)提供的異步IO接口,例如POSIX的aio系列函數(shù)或Windows的IO Completion Ports。此外,在不同的操作系統(tǒng)和平臺上,異步IO接口的實現(xiàn)也可能不同,需要進行不同的適配。
總體來說,異步IO模型適用于需要處理大量IO操作和高并發(fā)的場景,例如網(wǎng)絡(luò)服務(wù)器、數(shù)據(jù)庫等。在高并發(fā)、高性能的網(wǎng)絡(luò)編程中,異步IO模型是一個比較常用的IO模型。

在Unix、Linux系統(tǒng)中,AIO控制塊的結(jié)構(gòu)體類型為struct aiocb,定義在<aio.h>頭文件中。該結(jié)構(gòu)體包含了以下字段:
void *aio_buf:指向IO數(shù)據(jù)緩沖區(qū)的指針。
size_t aio_nbytes:IO操作的數(shù)據(jù)長度。
off_t aio_offset:IO操作在文件中的偏移量。
int aio_fildes:文件描述符。
int aio_lio_opcode:IO操作類型,取值為LIO_READ或LIO_WRITE。
int aio_reqprio:IO操作的優(yōu)先級。
struct sigevent aio_sigevent:異步IO完成后的通知方式,可以是信號、線程或進程。
int aio_return:IO操作完成后的返回值。
int aio_errno:IO操作完成時的錯誤碼。
int aio_offset:IO操作完成時的偏移量。
int aio_nbytes:IO操作完成時的數(shù)據(jù)長度。
使用AIO控制塊時,應(yīng)用程序可以通過aio_read和aio_write等異步IO函數(shù)發(fā)起IO操作,并傳遞一個AIO控制塊作為參數(shù)。在IO操作完成后,操作系統(tǒng)會更新AIO控制塊的狀態(tài),應(yīng)用程序可以通過讀取AIO控制塊的字段來獲取IO操作的結(jié)果。
需要注意的是,AIO控制塊只適用于異步IO模型,不能用于其他IO模型。在使用AIO控制塊時,應(yīng)用程序需要使用操作系統(tǒng)提供的異步IO接口,例如POSIX的aio系列函數(shù)或Windows的IO Completion Ports。
三、Web服務(wù)器簡介及HTTP協(xié)議

Web服務(wù)器可以提供各種不同類型的Web服務(wù),例如靜態(tài)網(wǎng)頁、動態(tài)網(wǎng)頁、文件下載、視頻流媒體等。Web服務(wù)器通常會提供一些可配置的選項,例如虛擬主機、訪問控制、數(shù)據(jù)緩存等,以滿足不同應(yīng)用程序的需求。
Web服務(wù)器通常采用多線程或多進程的方式來處理客戶端請求,以提高并發(fā)性能。另外,隨著Web應(yīng)用程序的復(fù)雜性不斷增加,一些Web服務(wù)器也開始支持異步IO模型和多路復(fù)用技術(shù),以進一步提高性能和響應(yīng)能力。
常見的Web服務(wù)器軟件包括Apache、Nginx、Microsoft IIS、Lighttpd等。這些Web服務(wù)器都有不同的優(yōu)缺點和適用場景,應(yīng)用程序需要根據(jù)自己的需求選擇合適的Web服務(wù)器。





四、服務(wù)器編程基本框架和兩種高效的事件處理模式

服務(wù)器編程基本框架
服務(wù)器編程的基本框架通常由以下幾個組成部分:
網(wǎng)絡(luò)通信模塊:負責(zé)與客戶端建立連接、接收和發(fā)送數(shù)據(jù),常用的網(wǎng)絡(luò)通信協(xié)議包括TCP/IP、HTTP等。
業(yè)務(wù)邏輯模塊:負責(zé)處理客戶端發(fā)送過來的請求,根據(jù)請求內(nèi)容進行相應(yīng)的處理,并將處理結(jié)果返回給客戶端。
數(shù)據(jù)存儲模塊:負責(zé)將服務(wù)器收到的數(shù)據(jù)進行存儲,常用的數(shù)據(jù)存儲方式包括關(guān)系型數(shù)據(jù)庫和非關(guān)系型數(shù)據(jù)庫等。
安全認證模塊:負責(zé)對客戶端請求進行身份驗證、權(quán)限控制等操作,以確保服務(wù)器數(shù)據(jù)的安全性。
日志記錄模塊:負責(zé)記錄服務(wù)器的運行日志,包括請求處理情況、異常信息等。
以上模塊可以根據(jù)實際需求進行擴展和精簡,具體實現(xiàn)方式也可以根據(jù)編程語言和框架的不同而有所不同。

在Reactor模式中,程序通過一個事件循環(huán)來監(jiān)聽多個I/O事件,當(dāng)有事件發(fā)生時,會根據(jù)不同的事件類型來調(diào)用相應(yīng)的回調(diào)函數(shù)進行處理。這種模式的核心是事件循環(huán),它不斷地輪詢所有注冊的I/O事件,一旦有事件發(fā)生,就會觸發(fā)相應(yīng)的處理函數(shù)。在事件循環(huán)中,Reactor會使用一個專門的線程來處理所有的I/O事件,其他業(yè)務(wù)邏輯則可以運行在其他線程中,從而實現(xiàn)了高并發(fā)的處理能力。
Reactor模式的核心組件包括:事件處理器、事件源、事件循環(huán)以及回調(diào)函數(shù)。事件處理器負責(zé)處理I/O事件,事件源則是產(chǎn)生I/O事件的對象,事件循環(huán)則負責(zé)監(jiān)聽所有的事件源,回調(diào)函數(shù)則是具體的業(yè)務(wù)邏輯實現(xiàn)。
總之,Reactor模式是一種應(yīng)用廣泛的事件驅(qū)動編程模式,它可以提高程序的性能和可維護性,尤其適用于高并發(fā)的網(wǎng)絡(luò)編程。

Proactor模式的核心思想是將I/O操作的處理分為兩個階段:請求提交和請求完成。在請求提交階段,應(yīng)用程序提交I/O請求,并指定I/O操作完成后將要執(zhí)行的回調(diào)函數(shù)。在請求完成階段,當(dāng)I/O操作完成后,操作系統(tǒng)將調(diào)用指定的回調(diào)函數(shù),通知應(yīng)用程序I/O操作已完成,并將結(jié)果傳遞給應(yīng)用程序。
與Reactor模式不同,Proactor模式將I/O操作的管理責(zé)任移交給了操作系統(tǒng),使得應(yīng)用程序無需關(guān)心底層I/O操作的實現(xiàn)細節(jié)。這使得應(yīng)用程序可以更加專注于業(yè)務(wù)邏輯的實現(xiàn),而無需過多關(guān)注I/O操作的處理。
Proactor模式被廣泛應(yīng)用于網(wǎng)絡(luò)編程、數(shù)據(jù)庫訪問等需要大量I/O操作的應(yīng)用程序中,以提高應(yīng)用程序的性能和可擴展性。

在每次循環(huán)中,我們使用FD_ZERO和FD_SET函數(shù)將服務(wù)器端的socket加入到讀事件集合中,并將所有客戶端的socket加入到讀事件集合中。然后,我們使用select函數(shù)等待一個I/O事件的發(fā)生。
如果服務(wù)器端的socket可讀,說明有新的連接請求,我們使用accept函數(shù)接受新的連接,并將其加入到客戶端socket數(shù)組中。如果客戶端socket可讀,說明有數(shù)據(jù)可讀,我們使用recv函數(shù)接收數(shù)據(jù),并使用send函數(shù)將數(shù)據(jù)轉(zhuǎn)換為大寫后發(fā)送回客戶端。如果客戶端關(guān)閉了連接,我們將其從客戶端socket數(shù)組中刪除。注意,在這個例子中,我們使用了一個固定大小的客戶端socket數(shù)組,最大值為MAX_CLIENTS。
這個例子中使用了阻塞I/O函數(shù)recv和send,因此它是同步I/O模式。雖然它不能像Proactor模式一樣實現(xiàn)真正的異步I/O操作,但是它可以有效地利用操作系統(tǒng)提供的多路復(fù)用機制來實現(xiàn)并發(fā)處理,從而提高系統(tǒng)的吞吐量和響應(yīng)速度。
五、線程同步機制類封裝及線程池實現(xiàn)

線程池通常包括以下幾個組成部分:
任務(wù)隊列:用于存儲等待執(zhí)行的任務(wù)。
線程池管理器:用于管理線程池中的線程,包括創(chuàng)建、銷毀、分配任務(wù)等操作。
線程池:由一組線程組成,用于執(zhí)行任務(wù)隊列中的任務(wù)。
線程池的工作流程如下:
初始化線程池:創(chuàng)建一組線程,并初始化任務(wù)隊列和其他相關(guān)參數(shù)。
添加任務(wù):將任務(wù)添加到任務(wù)隊列中。
線程池管理器從任務(wù)隊列中取出一個任務(wù),分配給空閑的線程來執(zhí)行。
線程執(zhí)行任務(wù)并將執(zhí)行結(jié)果返回。
線程池管理器將任務(wù)執(zhí)行結(jié)果存儲到一個結(jié)果隊列中。
應(yīng)用程序可以從結(jié)果隊列中獲取任務(wù)執(zhí)行結(jié)果。
當(dāng)任務(wù)隊列為空時,線程池管理器等待新任務(wù)的到來。
當(dāng)應(yīng)用程序退出時,銷毀線程池。
線程池的優(yōu)點:
提高性能和響應(yīng)速度:線程池可以重用線程,減少了線程創(chuàng)建和銷毀的開銷,同時也可以避免過多的線程數(shù)導(dǎo)致系統(tǒng)負載過高的問題。
簡化編程:線程池可以將任務(wù)分離出來,使得應(yīng)用程序的主線程可以專注于其他的工作,從而簡化了編程。
提高代碼可讀性和可維護性:線程池可以將任務(wù)和線程分離出來,使得代碼更加模塊化,易于理解和維護。
提供了線程管理功能:線程池可以自動管理線程的創(chuàng)建、銷毀和調(diào)度,使得程序員不必手動管理線程。
線程池的缺點:
需要額外的開銷:線程池需要額外的開銷來管理線程和任務(wù)隊列,這可能會降低系統(tǒng)的性能。
可能引起死鎖問題:線程池中的線程可能會因為競爭互斥鎖而導(dǎo)致死鎖問題。
任務(wù)分配可能存在不均衡問題:線程池中的線程可能會被分配到大量的繁重任務(wù),導(dǎo)致其他任務(wù)無法得到及時執(zhí)行。
線程池代碼——線程同步機制封裝類(互斥鎖類、條件變量類、信號量類)和線程池類
定義了三個線程同步機制的封裝類:互斥鎖類(Mutex)、條件變量類(Condition)和信號量類(Semaphore),它們都是使用POSIX線程庫(pthread)來實現(xiàn)的。
然后,我們定義了一個任務(wù)類(Task),用于封裝任務(wù)的執(zhí)行函數(shù)和參數(shù)。每個任務(wù)都有一個執(zhí)行函數(shù)和一個參數(shù),當(dāng)任務(wù)被執(zhí)行時,它會調(diào)用這個函數(shù),并將參數(shù)傳遞給它。
最后,我們定義了一個線程池類(ThreadPool),它包含一個線程數(shù)組、一個任務(wù)隊列、一個互斥鎖和一個條件變量。線程池的構(gòu)造函數(shù)會創(chuàng)建一組線程,并將它們存儲在線程數(shù)組中。當(dāng)任務(wù)被提交到線程池時,線程池會將任務(wù)添加到任務(wù)隊列中,并使用條件變量通知空閑線程來執(zhí)行任務(wù)。每個線程會重復(fù)執(zhí)行一個worker()函數(shù),該函數(shù)從任務(wù)隊列中獲取任務(wù)并執(zhí)行。
請注意,這只是一個簡單的線程池實現(xiàn),可能不適用于所有情況。在實際應(yīng)用中,您可能需要根據(jù)您的具體需求進行調(diào)整和優(yōu)化。例如,您可能需要添加一個任務(wù)隊列的最大大小限制,以避免任務(wù)隊列過大導(dǎo)致系統(tǒng)資源耗盡的問題。

EPOLLONESHOT標志在多個線程等待同一個epoll描述符上的事件時非常有用。如果沒有設(shè)置EPOLLONESHOT標志,多個線程可能會同時接收到同一個事件并嘗試同時處理它,導(dǎo)致競爭條件和其他同步問題。通過設(shè)置EPOLLONESHOT標志,每個事件只會被一個線程接收,其他線程直到epoll描述符重新啟用后才會被通知。
需要注意的是,設(shè)置EPOLLONESHOT標志并不意味著與事件相關(guān)聯(lián)的套接字或文件描述符會被關(guān)閉或禁用,它只影響epoll描述符本身的行為。
六、web服務(wù)器核心代碼
包括解析HTTP請求報文、解析請求完成及生成響應(yīng)信息。
文件結(jié)構(gòu):
httpConn.cpp
: HTTP 連接處理實現(xiàn)httpConn.h
: HTTP 連接處理類定義locker.h
: 互斥量和條件變量封裝main.cpp
: 主函數(shù)threadpool.h
: 線程池實現(xiàn)
httpConn.h
httpConn.cpp
locker.h
threadpool.h
main.cpp
七、web服務(wù)器優(yōu)化功能——定時檢測非活躍連接
lst_timer.h
nonactive_conn.cpp
八、服務(wù)器壓力測試

Webbench的運行方式比較簡單,用戶只需指定要測試的URL以及并發(fā)訪問的客戶端數(shù)量,就可以啟動測試。測試完成后,Webbench會輸出測試結(jié)果,包括服務(wù)器的響應(yīng)時間、吞吐量、錯誤率等信息。
Webbench的優(yōu)點是具有簡單易用、快速方便等特點,測試結(jié)果比較直觀,可以幫助開發(fā)人員快速了解Web服務(wù)器的性能瓶頸。但是Webbench也存在一些缺點,比如測試方法比較簡單,不考慮服務(wù)器的緩存機制等因素,測試結(jié)果可能不夠準確;同時,Webbench只能測試靜態(tài)頁面的性能,對于動態(tài)頁面的測試效果比較有限。
總的來說,Webbench是一款對于初學(xué)者來說比較適合的Web服務(wù)器性能測試工具,但是對于高級用戶或?qū)I(yè)測試人員來說,可能需要使用更加復(fù)雜和全面的測試工具來進行Web服務(wù)器性能測試。