C++網(wǎng)絡(luò)編程入門:輕量級Web并發(fā)服務(wù)器開發(fā)
時間:2023-05-30 16:57:01 | 來源:網(wǎng)站運(yùn)營
時間:2023-05-30 16:57:01 來源:網(wǎng)站運(yùn)營
C++網(wǎng)絡(luò)編程入門:輕量級Web并發(fā)服務(wù)器開發(fā):
目錄1. 前言
2. 學(xué)習(xí)路線
2.1 C++
2.2 計算機(jī)網(wǎng)絡(luò)基礎(chǔ)
2.3 計算機(jī)系統(tǒng)基礎(chǔ)
2.4 linux基礎(chǔ)
2.5 服務(wù)端開發(fā)規(guī)范
3. 并發(fā)服務(wù)器開發(fā)入門
3.1 建立連接
3.2 IO多路復(fù)用
3.3 多進(jìn)程并發(fā)
3.4 多線程并發(fā)
3.6 基于多線程并發(fā)服務(wù)器開發(fā)實(shí)例
3.6.1 常見問題
3.6.2 確定要實(shí)現(xiàn)的功能
3.6.3 選定事件處理模式和并發(fā)模式
3.6.4 確定線程交互方式
3.6.5 類的定義與功能
一、前言入坑C++以后雖然也參加了一些比賽和做了一些項目,但是迫于時間等各方面的因素很多時候?qū)++的應(yīng)用也只停留在STL和業(yè)務(wù)邏輯編程,很多C++的新特性都沒有用到,講道理也沒有嚴(yán)格遵循編程規(guī)范。剛好這個寒假長得離譜,有了時間將以前買的幾本磚頭書重新開始看一遍,拉通思路后總算把以前一直想寫的C++服務(wù)器寫好了。這里順便把我一個老萌新一些微不足道的經(jīng)驗分享給新入坑的新萌新們,希望能在學(xué)習(xí)C++網(wǎng)絡(luò)編程的路上有所幫助吧。這里順便附上本人的渣渣小服務(wù)器源碼TinyWeb,歡迎大佬們指教。
二、學(xué)習(xí)路線 一說到C++網(wǎng)絡(luò)編程如何入門,大佬們的回答出奇一致:看
《Unix環(huán)境高級編程》和
《Unix網(wǎng)絡(luò)編程 卷一》。但是這兩本書都是800多頁,對萌新來說很不友好,很多人在這里就望而卻步了。就我個人觀點(diǎn)而言兩本書其實(shí)也并不適合入門,嚴(yán)格來說是不適合系統(tǒng)的入門,因為這兩本書的定位其實(shí)更接近于“字典”,《Unix環(huán)境高級編程》可以看做是linux并發(fā)編程的一本字典,而《Unix網(wǎng)絡(luò)編程 卷一》則是linux網(wǎng)絡(luò)編程的一本字典。正如我們系統(tǒng)學(xué)習(xí)一門語言一樣,我們會使用字典,但絕不會說一開始靠著字典就融會貫通了,總要借助由淺到深的教材的輔助才能入門,入門后才能做到更好地使用字典。
就C++網(wǎng)絡(luò)編程這個領(lǐng)域而言,基本上所需要點(diǎn)亮的基本技能點(diǎn)有:
- C++基礎(chǔ)
- 計算機(jī)網(wǎng)絡(luò)基礎(chǔ)
- 計算機(jī)系統(tǒng)基礎(chǔ)
- linux基礎(chǔ)
- 服務(wù)端開發(fā)規(guī)范
(1). C++入門 C++入門其實(shí)大家公認(rèn)的是
《C++ Primer Plus》,這本書確實(shí)寫得很詳細(xì)也很容易懂,唯一的美中不足是編程練習(xí)過于無聊,個人建議可以配套
《算法筆記》進(jìn)行學(xué)習(xí),算法筆記是浙大一個研究生針對算法考試和計算機(jī)考研機(jī)試而編寫的一本教材,里面主要有兩部分:語言基礎(chǔ)+數(shù)據(jù)結(jié)構(gòu),該書同時配備了大量的OJ題,利用里面的OJ題來學(xué)習(xí)C++我個人覺得是能達(dá)到事半功倍的效果,這就是應(yīng)試教育的優(yōu)越之處所在了。
(2)計算機(jī)網(wǎng)絡(luò) 網(wǎng)絡(luò)這方面我看的是
《計算機(jī)網(wǎng)絡(luò):自頂向下方法》,這本書其實(shí)只要看前面幾章就好了,計算機(jī)網(wǎng)絡(luò)基礎(chǔ)結(jié)構(gòu)其實(shí)就是應(yīng)用層-傳輸層-網(wǎng)絡(luò)層-數(shù)據(jù)鏈路層-物理層,我們只要重點(diǎn)掌握前面三層的基礎(chǔ)知識基本上對開發(fā)來說也就差不多了。針對Web服務(wù)器開發(fā)而言,看完應(yīng)用層對應(yīng)的章節(jié)能讓我們知道如何分析http請求和做出對應(yīng)的http應(yīng)答,以及理解http和https協(xié)議的差異;看完傳輸層這一章能讓我們知道怎么設(shè)置tcp連接和udp連接;看完網(wǎng)絡(luò)層這一章能讓我們處理ipv4連接和ipv6連接。
(3)計算機(jī)系統(tǒng) 理解計算機(jī)系統(tǒng)工作基本原理是十分重要的,如果沒有這方面的相關(guān)知識的話其實(shí)是很難理解多線程與多進(jìn)程工作原理的,在并發(fā)編程過程中也很容易踩坑。對于計算機(jī)系統(tǒng)基礎(chǔ)的學(xué)習(xí)我個人是強(qiáng)推
《深入理解計算機(jī)系統(tǒng)》,這本書對入門來說極其友好。對于服務(wù)器開發(fā)而言,我個人推薦重點(diǎn)看第3章、第8~12章這6個章節(jié)。第3章是從匯編語言的層次來理解程序運(yùn)行過程,看完這一章對并發(fā)編程中“競爭”問題的理解將會很有幫助;第8章是進(jìn)程、異常和信號的入門,看完這一章有助于我們理解內(nèi)核是如何切換控制流;第9章是虛擬內(nèi)存,看完這一章有助于我們理解多線程和多進(jìn)程環(huán)境的內(nèi)存分配;第10~12章這三章是服務(wù)器并發(fā)編程基礎(chǔ),看完這幾章我們能對服務(wù)器開發(fā)的基本流程有個基本的了解。
(4)linux基礎(chǔ) 由于服務(wù)器大多數(shù)時候是在unix環(huán)境下運(yùn)行的,因此我們也需要掌握linux的一些基本指令。其實(shí)linux和windows一樣也是一個操作系統(tǒng),只不過操作繁瑣了一些,需要我們掌握一些比點(diǎn)鼠標(biāo)更高級的操作。linux操作的基本指令學(xué)習(xí)基本上大家用的都是
《鳥哥的linux私房菜》,這本書很基礎(chǔ)也很厚,但是其實(shí)我們只需要掌握一些基礎(chǔ)的指令因此也沒必要整本書全看完,我個人建議可以只看第4~6章,剩下的以后有時間再慢慢看。另外,linux的
IDE也是與windows不同的,目前比較主流的的C++ IDE有Clion、Qtcreator和Vscode,我個人是推薦
Qtcreator,主要是免費(fèi)而且配置簡單。
(5)服務(wù)端開發(fā)規(guī)范 基本上點(diǎn)亮上面的技能點(diǎn)之后對于如何開發(fā)一個簡單的Web并發(fā)服務(wù)器應(yīng)該已經(jīng)有了自己的想法,這個時候當(dāng)然可以開始搭建自己的服務(wù)器。不過這個時候?qū)懗鰜淼姆?wù)器大概率效率不會太高而且也不符合規(guī)范。C++服務(wù)端開發(fā)其實(shí)已經(jīng)是一個很成熟的領(lǐng)域,存在著大量公認(rèn)的編程規(guī)范和主流技術(shù)。如果想針對性地提高服務(wù)器運(yùn)行效率和開發(fā)效率,以及了解服務(wù)器開發(fā)中的某些規(guī)范,個人建議可以繼續(xù)接著看
《Linux高性能服務(wù)器編程》,這本書講得很全面,也給出了基于進(jìn)程池服務(wù)器開發(fā)和基于線程池服務(wù)器開發(fā)的兩個實(shí)例,看完這本書能避免很多在服務(wù)器開發(fā)過程中可能遇到的坑,也能理解目前服務(wù)器開發(fā)的某些主流技術(shù),例如:
- 主流服務(wù)器模型:C/S模型和P2P模型
- I/O模型:同步I/O與異步I/O
- 事件處理模式:Reactor模式/Proactor模式
- 并發(fā)模式:半同步/半異步模式、領(lǐng)導(dǎo)者/追隨者模式
針對以上主流技術(shù),大牛們開發(fā)了標(biāo)準(zhǔn)庫以提高開發(fā)效率,目前主流的C++網(wǎng)絡(luò)編程庫和I/O框架庫有:
Boost.Asio、libevent和
muduo,這些庫封裝了網(wǎng)絡(luò)編程中的繁瑣操作同時兼顧了效率和安全性。如果想進(jìn)一步提高自己的技術(shù),建議可以看一下陳碩大佬的
《Linux多線程服務(wù)端編程》,從源碼的層次進(jìn)一步理解C++網(wǎng)絡(luò)編程。
三、并發(fā)服務(wù)器開發(fā)入門(1)建立連接 網(wǎng)絡(luò)連接的發(fā)起依賴于
套接字接口,套接字接口是一組函數(shù),它們與Unix I/O函數(shù)結(jié)合起來,用于創(chuàng)建網(wǎng)絡(luò)應(yīng)用,其中我們最常用到的函數(shù)有
getaddrinfo、getnameinfo、socket、connect、bind、listen、accept。建立連接包括兩部分,一部分是客戶端發(fā)起連接,另一部分是服務(wù)端接收連接。
對于客戶端,在發(fā)起連接之前首先要知道服務(wù)端的地址,地址包括兩部分,分別是
IP地址與
端口,但是大多數(shù)情況下我們并不知道某個網(wǎng)站的IP地址和端口,只知道該網(wǎng)站的網(wǎng)站和提供的服務(wù),這就是
getaddrinfo函數(shù)的用途所在了,該函數(shù)通過讀取系統(tǒng)配置文件和訪問DNS服務(wù)器將網(wǎng)址和服務(wù)轉(zhuǎn)換為套接字接口能夠識別的地址。獲得網(wǎng)站地址后,我們可以通
socket函數(shù)來創(chuàng)建一個
套接字描述符,再利用
connect函數(shù)和已知的地址來建立和服務(wù)器的連接。
對于服務(wù)器而言,同樣需要socket函數(shù)來創(chuàng)建一個套接字描述符,與客戶端不同的是,服務(wù)端創(chuàng)建后的描述符還應(yīng)通過
bind函數(shù)綁定某個特定的套接字地址(包括IP地址和端口)??蛻舳说亩丝谝话闶请S意分配的,但是對服務(wù)端而言,端口與服務(wù)端提供的服務(wù)是一一對應(yīng)的,例如,
端口80就規(guī)定用以提供www代理服務(wù),因此通過bind函數(shù)綁定地址這一步對服務(wù)端而言必不可少。默認(rèn)情況下,內(nèi)核會認(rèn)為socket函數(shù)創(chuàng)建的套接字對應(yīng)于
主動套接字,它存在于一個連接的客戶端。服務(wù)器通過
listen函數(shù)將
主動套接字轉(zhuǎn)換為
被動套接字(監(jiān)聽描述符),從而告訴內(nèi)核該套接字是被服務(wù)端使用而不是被客戶端使用的。最后,服務(wù)器通過
accept函數(shù)來等待客戶端的連接請求,在建立連接后,該函數(shù)獲得客戶端的地址(我們可以通過
getnameinfo函數(shù)將該地址轉(zhuǎn)換為我們熟悉的IP地址和端口)并返回一個
已連接描述符,服務(wù)端通過已連接描述符實(shí)現(xiàn)與客戶端的通信。
總體連接建立流程如下圖所示:
圖1 連接建立過程 初學(xué)者可能會對監(jiān)聽描述符和已連接描述符的區(qū)別感到困惑。一般而言,監(jiān)聽描述符是作為客戶端連接請求的一個端點(diǎn),它通常被創(chuàng)建一次,并存在于整個服務(wù)器的生命周期。已連接描述符是客戶端和服務(wù)器已經(jīng)建立起來的連接的一個端點(diǎn),服務(wù)器每次接收連接請求都會創(chuàng)建一次,它只存在于服務(wù)器為一個客戶端服務(wù)的過程之中,即對于每個客戶端連接服務(wù)器都會建立一個已連接描述符為其服務(wù)。
建立連接注意事項:
listen函數(shù)存在的陷阱。我們首先來看一下listen函數(shù)的函數(shù)原型:int listen(int sockfd,int backlog),其中sockfd是監(jiān)聽描述符,為了理解backlog參數(shù),我們必須認(rèn)識到內(nèi)核為任何一個給定的監(jiān)聽套接口維護(hù)兩個隊列:
- 未完成連接隊列:這個隊列對應(yīng)的連接是已由某個客戶端發(fā)出并到達(dá)服務(wù)器,而服務(wù)器正在等待完成相應(yīng)的TCP三路握手的過程,此時這些套接口處于SYN_RCVD狀態(tài)。
- 已完成連接隊列:每個已完成TCP三次握手過程的客戶端連接對應(yīng)其中一項,此時這些套接口處于ESTABLISHED狀態(tài)。
兩隊列之和不超過我們設(shè)置的backlog參數(shù),圖解過程如圖2所示:
圖2 TCP為監(jiān)聽套接口維護(hù)的兩個隊列 對于listen函數(shù)的使用要注意的問題是,等待accpct的總連接數(shù)不能超過我們設(shè)置的backlog參數(shù),否則在accpect時內(nèi)核將報錯并設(shè)置errno。另一個問題是,已完成連接隊列代表的是曾經(jīng)完成TCP三次握手的隊列,并不是實(shí)時的,因此也有可能出現(xiàn)accpct成功但讀寫失敗的情況。
(2)I/O多路復(fù)用 I/O多路復(fù)用,I/O指的是I/O事件(包括I/O讀寫、I/O異常等事件),多路指多個獨(dú)立連接(或多個Channel),復(fù)用指多個事件復(fù)用一個控制流(線程或進(jìn)程)。串起來理解就是很多個獨(dú)立I/O事件的處理依賴于一個控制流。
I/O復(fù)用使得程序能夠同時監(jiān)聽多個獨(dú)立的文件描述符從而提高程序性能。但要指出的一點(diǎn)是,I/O復(fù)用本身是阻塞的,但多個文件描述符同時就緒時,如果不采取額外的措施,程序就只能按順序依次處理其中的一個文件描述符,這使得服務(wù)器程序看起來像是串行工作的。如果要實(shí)現(xiàn)并發(fā),只能使用多進(jìn)程或多線程等編程手段。
事件的到來是隨機(jī)的、異步的,為了處理這些相互獨(dú)立的事件我們需要借助內(nèi)核的幫助,在事件發(fā)生之前,我們可以通過I/O復(fù)用函數(shù)將我們感興趣的事件和文件描述符綁定起來并注冊在內(nèi)核中,由內(nèi)核來監(jiān)控事件何時發(fā)生,并通過I/O復(fù)用函數(shù)來通知用戶事件的發(fā)生。從這個角度看,I/O復(fù)用函數(shù)可以看做是用戶和內(nèi)核之間通信的一個媒介。用戶通過I/O復(fù)用函數(shù)注冊自己感興趣的事件,而內(nèi)核通過I/O復(fù)用函數(shù)通知事件的發(fā)生。
目前廣泛使用的I/O復(fù)用函數(shù)有select、poll和epoll三組函數(shù),這3組函數(shù)都通過某種結(jié)構(gòu)體變量來告訴內(nèi)核監(jiān)聽哪些文件描述符上的哪些事件,并使用該結(jié)構(gòu)體類型的參數(shù)來獲取內(nèi)核處理的結(jié)果。
(a)Select
select本質(zhì)上是通過設(shè)置或者檢查存放fd標(biāo)志位的數(shù)據(jù)結(jié)構(gòu)來進(jìn)行下一步處理。這樣所帶來的缺點(diǎn)是:1 單個進(jìn)程可監(jiān)視的fd數(shù)量被限制;2 需要維護(hù)一個用來存放大量fd的數(shù)據(jù)結(jié)構(gòu),這樣會使得用戶空間和內(nèi)核空間在傳遞該結(jié)構(gòu)時復(fù)制開銷大;3 對socket進(jìn)行掃描時是線性掃描。
(b)Poll
poll本質(zhì)上和select沒有區(qū)別,它將用戶傳入的數(shù)組拷貝到內(nèi)核空間,然后查詢每個fd對應(yīng)的設(shè)備狀態(tài),如果設(shè)備就緒則在設(shè)備等待隊列中加入一項并繼續(xù)遍歷,如果遍歷完所有fd后沒有發(fā)現(xiàn)就緒設(shè)備,則掛起當(dāng)前進(jìn)程,直到設(shè)備就緒或者主動超時,被喚醒后它又要再次遍歷fd。這個過程經(jīng)歷了多次無謂的遍歷。它沒有最杭州接數(shù)的限制,原因是它是基于鏈表來存儲的,但是同樣有一個缺點(diǎn):大量的fd的數(shù)組被整體復(fù)制于用戶態(tài)和內(nèi)核地址空間之間,而不管這樣的復(fù)制是不是有意義。poll還有一個特點(diǎn)是“水平觸發(fā)”,如果報告了fd后,沒有被處理,那么下次poll時會再次報告該fd。
(c)Epoll
epoll支持水平觸發(fā)和邊緣觸發(fā),最大的特點(diǎn)在于邊緣觸發(fā),它只告訴進(jìn)程哪些fd剛剛變?yōu)榫托钁B(tài),并且只會通知一次。在前面說到的復(fù)制問題上,epoll使用mmap減少復(fù)制開銷。還有一個特點(diǎn)是,epoll使用“事件”的就緒通知方式,通過epoll_ctl注冊fd,一旦該fd就緒,內(nèi)核就會采用類似callback的回調(diào)機(jī)制來激活該fd,epoll_wait便可以收到通知。
這三組函數(shù)的區(qū)別如表1所示:
表1 3組I/O復(fù)用函數(shù)的區(qū)別(3)多進(jìn)程并發(fā) 進(jìn)程的的經(jīng)典定義就是一個執(zhí)行中的程序的實(shí)例。系統(tǒng)的每個程序都運(yùn)行在一個進(jìn)程的上下文中。上下文是由程序正常運(yùn)行所需維護(hù)的狀態(tài)組成的。這個狀態(tài)包括存放在內(nèi)存中的程序的代碼和數(shù)據(jù),它的棧、通用目的寄存器的內(nèi)容、程序計數(shù)器、環(huán)境變量以及打開文件描述符的集合。內(nèi)核通過調(diào)度器來實(shí)現(xiàn)對不同進(jìn)程的調(diào)度,并通過上下文切換實(shí)現(xiàn)控制到新進(jìn)程的轉(zhuǎn)移,從而提供給應(yīng)用程序兩個抽象:
- 一個獨(dú)立的邏輯控制流,它提供一個假象,好像我們程序獨(dú)占地使用處理器。
- 一個私有的地址空間,它提供一個假象,好像我們的程序獨(dú)立地使用內(nèi)存系統(tǒng)。
進(jìn)程的內(nèi)存地址空間如圖3所示,對于多進(jìn)程并發(fā),我們重點(diǎn)關(guān)注的是內(nèi)存泄漏問題和進(jìn)程通信問題。
圖3 內(nèi)存地址空間(a)多進(jìn)程環(huán)境的內(nèi)存泄漏問題
一般而言,多進(jìn)程的實(shí)現(xiàn)依賴于父進(jìn)程派生子進(jìn)程,這個過程通過
fork函數(shù)實(shí)現(xiàn)。新創(chuàng)建的子進(jìn)程與父進(jìn)程幾乎但不完全相同,子進(jìn)程得到與父進(jìn)程完全相同(但相互獨(dú)立)的用戶虛擬地址空間,包括代碼和數(shù)據(jù)段、堆、棧以及打開文件描述符的副本,它們最大的區(qū)別在于PID不同。由于子進(jìn)程與父進(jìn)程共享文件描述符,這意味著子進(jìn)程可以打開父進(jìn)程中打開的任何文件,這也導(dǎo)致了共享文件的在多進(jìn)程環(huán)境下存在的內(nèi)存泄漏問題。
內(nèi)核用3個相關(guān)的數(shù)據(jù)結(jié)構(gòu)來表示打開的文件,包括:
- 描述符表:每個進(jìn)程都有它獨(dú)立的描述符表,它的表項是由打開的文件描述符來索引的。每個打開的描述符表項指向文件表中的一個表項。
- 文件表:打開文件的集合是由一張文件表來表示的,所有的進(jìn)程共享這張表。每個文件表的表項組成包括當(dāng)前文件位置、引用計數(shù)、以及一個指向v-node表對應(yīng)表項的指針。關(guān)閉一個文件描述符會減少相應(yīng)的文件表表項引用計數(shù),內(nèi)核不會刪除這個文件表表項,直至它的引用計數(shù)減少為0。
- v-node表:所有進(jìn)程共享一張v-node表,該表的表項包括的stat結(jié)構(gòu)的大多數(shù)信息。
由于文件表的引用計數(shù)的存在,因此父進(jìn)程派生子進(jìn)程時會導(dǎo)致父進(jìn)程中打開的文件描述符對應(yīng)的文件表表項引用計數(shù)加1,如果子進(jìn)程和父進(jìn)程其中有一個進(jìn)程沒有關(guān)閉文件描述符,則會導(dǎo)致無用文件表表項對內(nèi)存空間的占用從而導(dǎo)致內(nèi)存泄漏。因此,我們需要保證在父進(jìn)程足夠“干凈”(沒有打開大量的文件描述符和申請大量空間)的時候派生子進(jìn)程來得到一個同樣足夠干凈的子進(jìn)程,從而避免內(nèi)存泄漏。
(b)多進(jìn)程環(huán)境的通信問題
因為多進(jìn)程環(huán)境下各個進(jìn)程內(nèi)存地址空間相互獨(dú)立,因此內(nèi)存的通信只能依賴于內(nèi)核。內(nèi)核為多進(jìn)程提供的通信機(jī)制有管道(以先進(jìn)先出的方式接受數(shù)據(jù))、共享內(nèi)存(最高效的IPC機(jī)制,不涉及進(jìn)程之間的數(shù)據(jù)傳輸,提供共享讀機(jī)制)、消息隊列(在兩個進(jìn)程之間傳輸二進(jìn)制數(shù)據(jù)塊)。
(4)多線程并發(fā) 線程是運(yùn)行在進(jìn)程上下文之中的邏輯流,線程由內(nèi)核調(diào)度,每個線程都有它自己的線程上下文,包括一個唯一的整數(shù)線程ID、棧、棧指針、程序計數(shù)器、通用目的寄存器和條件碼。所有運(yùn)行在一個進(jìn)程的線程共享該進(jìn)程的虛擬地址空間。
由于多線程共享一個進(jìn)程的虛擬地址空間,因此在多線程訪問某些共享量時可能會出現(xiàn)“競爭”問題和“同步錯誤”,因此多線程并發(fā)要考慮的一個重要問題就是多線程同步。避免“同步錯誤”主要有以下幾種機(jī)制。
- 使用內(nèi)核輔助來避免同步錯誤:在多線程環(huán)境中我們可以像多進(jìn)程環(huán)境一樣,依賴于內(nèi)核的管道機(jī)制或者IPC機(jī)制來實(shí)現(xiàn)不同線程之間的通信從而避免對共享變量的讀寫帶來的同步錯誤,然而這種機(jī)制會造成邏輯流在用戶態(tài)和內(nèi)核態(tài)的反復(fù)切換,從而降低程序運(yùn)行效率。
- 使用互斥鎖來避免同步錯誤:互斥鎖由信號量實(shí)現(xiàn),獲得鎖的線程獨(dú)占對共享變量的訪問,其他線程在訪問該變量時會由于鎖的存在而阻塞從而進(jìn)入睡眠狀態(tài),這種機(jī)制保證了在任一時刻只有一個線程對共享變量的操作從而避免同步錯誤。然而鎖的創(chuàng)建和解鎖都依賴于內(nèi)核,這也會導(dǎo)致一定的開銷從而降低程序運(yùn)行效率。
- 使用自旋鎖來避免同步錯誤:自旋鎖與互斥鎖有點(diǎn)類似,只是自旋鎖不會引起調(diào)用者睡眠,如果自旋鎖已經(jīng)被別的執(zhí)行單元保持,調(diào)用者就一直循環(huán)在那里看是否該自旋鎖的保持者已經(jīng)釋放了鎖,"自旋"一詞就是因此而得名。其作用是為了解決某項資源的互斥使用。因為自旋鎖不會引起調(diào)用者睡眠,所以自旋鎖的效率遠(yuǎn) 高于互斥鎖。雖然它的效率比互斥鎖高,但是自旋鎖在自旋期間一直占用CPU,如果不能在很短的時間內(nèi)獲得鎖這無疑會使CPU效率降低。
- 使用原子變量來避免同步錯誤:C++ 11提供atomic原子類來實(shí)現(xiàn)對變量的原子操作,我們可以使用atomic_flag類型和atomic_int類型避免同步錯誤,使用原子變量還可以減少上鎖帶來的開銷。
(6)基于多線程并發(fā)服務(wù)器開發(fā)實(shí)例(a)常見問題
- 服務(wù)器如何同時支持tcp連接和udp連接?
可以創(chuàng)建兩個socket描述符,一個描述符指定服務(wù)類型為tcp,另一個指定為udp,將這兩個文件描述符綁定在同一個服務(wù)端地址上,通過I/O多路復(fù)用來監(jiān)聽這兩個文件描述符事件的就緒。
可以使用getaddrinfo函數(shù),getaddrinfo 函數(shù)在 IPv6 和 IPv4 網(wǎng)絡(luò)下都能實(shí)現(xiàn)獨(dú)立于協(xié)議的名稱解析,而且它返回的指向 addrinfo 結(jié)構(gòu)的鏈表中會存放所有由輸入?yún)?shù) nodename 解析出的所有對應(yīng)的 IP 信息,包括 IP 地址,協(xié)議族信息等。
我們通過http請求報文的connection頭部字段來判斷當(dāng)前連接是長連接還是短連接,如何是短連接,我們發(fā)送完http應(yīng)答報文后立即關(guān)閉連接;對于長連接,我們利用時間輪或時間堆為長連接分配一個定時器并設(shè)定一個超時時間,如果在超時時間到來時長連接仍處于非活動連接狀態(tài),服務(wù)器則主動關(guān)閉長連接。
- 服務(wù)器如何同時支持獲取靜態(tài)內(nèi)容和動態(tài)內(nèi)容?
我們將靜態(tài)內(nèi)容和動態(tài)內(nèi)容放在服務(wù)器不同的文件夾下,根據(jù)http請求報文中的url地址來確定用戶請求的是靜態(tài)內(nèi)容還是動態(tài)內(nèi)容。請求靜態(tài)內(nèi)容則將對應(yīng)內(nèi)容通過文件映射或者集中寫的機(jī)制將內(nèi)容寫到已連接描述符中。如果請求的是動態(tài)內(nèi)容,則將參數(shù)從請求報文中提取出來并設(shè)置環(huán)境變量,并創(chuàng)建一個子進(jìn)程,該子進(jìn)程通過對環(huán)境變量的讀取來獲得程序參數(shù),通過execve來加載CGI程序并把最后獲得的結(jié)果通過標(biāo)準(zhǔn)輸出重定向傳遞給對應(yīng)的連接描述符。
根據(jù)機(jī)器性能決定,機(jī)器有幾個核就建立幾個線程,為了避免線程搶占,可以使用pthread_setaffinity_np函數(shù)將線程綁定到某個CPU核心。
(b)確定自己要實(shí)現(xiàn)的功能:支持get請求;支持請求靜態(tài)內(nèi)容;支持ipv4;支持tcp;支持長連接/短連接;支持并發(fā)訪問。
(c)選定事件處理模式和并發(fā)模式:事件處理模式采用Reactor模式,主線程只負(fù)責(zé)監(jiān)聽文件描述符是否有事件發(fā)生,讀寫數(shù)據(jù)、接收新的連接、以及處理客戶請求均在工作線程中實(shí)現(xiàn);使用半同步/半異步模式,每個線程(主線程和工作線程)都通過一個epoll維護(hù)自己的事件循環(huán),它們各自獨(dú)立地監(jiān)聽不同事件。
(d)選定線程交互方式:通過pipe管道和C++ 11的原子變量實(shí)現(xiàn)各線程之間的交互方式。
(e)類的定義與功能:
- web_thread.h
(1). 定義了webthread類;
(2). 該類是主線程與子線程通信的媒介,本程序?qū)τ诿總€子線程建立對應(yīng)的wedthread全局對象,主線程通過該對象來與子線程通信,子線程通過該對象接收來自主線程的消息,并運(yùn)行該對象的work()函數(shù)來處理主線程消息和用戶的http請求。
- http_conn.h
(1).定義了http_conn類和util_timer類;
(2).http_conn類是用于處理http請求和作出http應(yīng)答的一個類,本程序?qū)τ诿總€新連接的用戶都分配一個http_conn對象用于處理http請求;
(3).util_timer類是定時器類,本程序?qū)τ诿總€新連接的用戶都分配一個定時器對象,并設(shè)置該定時器的超時時間,如果長連接用戶在超時時間內(nèi)都處于非活動狀態(tài)則關(guān)閉用戶連接,如果用戶在超時時間到達(dá)之前重新活動則延長超時時間。
web_function.h (1).該文件用于定義全局變量和常量,包括線程數(shù)、當(dāng)前總用戶數(shù)、讀寫緩沖區(qū)大小等,如果要支持更多的并發(fā)用戶請求,應(yīng)該修改該文件中定義的這些常量;
(2).該文件還提供了一些通用的基本功能函數(shù),例如顯示當(dāng)前時區(qū)時間的函數(shù),顯示用戶ip地址的函數(shù)等。
關(guān)鍵詞:服務(wù),入門,網(wǎng)絡(luò)