0x14-套接字編程-HTTP服務(wù)器(2)
HTTP服務(wù)器的結(jié)構(gòu)
- HTTP服務(wù)器 本質(zhì)上就是一個 TCP的接收端 程序
- 但凡一個正常的 TCP 接收端程序,都逃不過那幾個流程:
- 創(chuàng)建監(jiān)聽
socket -> 綁定端口,IP -> 監(jiān)聽socket -> 接受新連接 -> 處理讀寫... -> 關(guān)閉完成的連接
- 其中前三步比較固定,最多對這個監(jiān)聽用的
socket,進行一些優(yōu)化處理,設(shè)置一些屬性之類的,但那都是固定模式,想想就能明白。硬要說重要的地方,也就是在于是否把socket設(shè)為非阻塞(non-blocking)了。
- 后面幾步,每個都是很重要的環(huán)節(jié),需要細細設(shè)計才行
所謂非阻塞,我還是不班門弄斧了,請移步 UNIX網(wǎng)絡(luò)編程-卷1-中文·第三版 127頁(英文版160頁) 的圖6-6,清楚的對比了,阻塞,非阻塞,異步,I/O復(fù)用的區(qū)別和含義。十分建議寫網(wǎng)絡(luò)程序之前,去把這本書的某些章節(jié)大致過一遍。
- 對于這章節(jié)需要寫的這個服務(wù)器而言,采用的是經(jīng)典且流行的 I/O復(fù)用+非阻塞套接字(socket)+多線程(線程池) 結(jié)構(gòu)。
- 吶,又出現(xiàn)一個新的知識點,I/O復(fù)用,這是什么鬼。
I/O復(fù)用
- 我給一個不太嚴(yán)密的解釋,那就是 將你這個程序需要等待的地方,集中起來
- 打個比方:
- 假設(shè)有100個新連接,被你的監(jiān)聽套接字給成功接受(
accept())了
- 這時候并不是所有新連接都立刻有數(shù)據(jù)可以讀,那此時你有兩種選擇:阻塞,非阻塞。但不論是哪一種都會導(dǎo)致同一個結(jié)果
- 阻塞: 那假設(shè)你只有一個線程處理這100個連接,萬一要是正好處理到這個暫時沒有數(shù)據(jù)的連接,就要一直等待它的數(shù)據(jù)到來,后面的幾十個連接都要閑著;假設(shè)有多個線程同時處理,理由還是一樣,換湯不換藥,而且難道你還能開100個線程去處理嗎?那如果更多的連接呢?
- 非阻塞: 比阻塞看起來稍微好一些,因為如果沒有數(shù)據(jù)到來的話,那就直接跳過這個連接,直接去處理下一個連接了,但是你想想,這不就是遍歷了嗎?萬一連接量一大,假設(shè)上萬,而且只有少數(shù)的幾個連接有數(shù)據(jù)活躍,這無用功做的是不是太多了?多開幾個線程去平攤壓力?那么要開多少比較合適?
- 這時候喜歡偷懶的程序員,自然就不愿意了,于是考慮是否可以有一個,讓我們可以在單個線程的情況下還能夠只處理那些活躍的連接?
- 這時候出現(xiàn)了所謂的 I/O復(fù)用 技術(shù),說是技術(shù),因為它使用的還是同步型的操作(
read, write),只不過套接字設(shè)為非阻塞的了。
- Linux平臺下的
epoll, Unix(包括Mac)平臺下的kqueue, Windows平臺下的IOCP,各平臺通用的select, poll,還有幾個歷史實現(xiàn)就不贅述了。
- 最后這兩個
select, poll在活躍連接明顯少于總連接數(shù)的情況下,性能比前三個要差許多,故本章使用的是epoll,(當(dāng)然還有資料比較多的原因啦
-
說說 epoll 的工作
- 首先它幫我們管理著所有的套接字,用來監(jiān)聽這些套接字哪些有了數(shù)據(jù),就返回誰。
- 將所有等待,阻塞都集中在了一個地方,那就是
epoll_wait()調(diào)用上
- 而且可以針對不同的事件進行不同的監(jiān)聽,這就是事件驅(qū)動這種模式的由來
-
事件驅(qū)動
- 簡單來說,就是針對某種事件進行觸發(fā)的一種編程模式
- 具體來說,假設(shè)你在網(wǎng)絡(luò)編程,正在處理一個套接字,由于TCP是全雙工的,意味著這個TCP套接字是可讀可寫,問題來了,什么時候可讀,什么時候可寫呢?這就延伸出了事件,讀事件,寫事件,錯誤事件等
- 可以通過
epoll_clt()來設(shè)置要監(jiān)聽的事件,當(dāng)然也可以同時監(jiān)聽多個事件,看你的設(shè)計了。
- 具體的
epoll接口的詳細介紹,可以直接在Linux上,使用man epoll進行查看手冊,這是基本功。
epoll_create, epoll_ctl, epoll_wait
服務(wù)器結(jié)構(gòu)
- 繼續(xù)回到服務(wù)器結(jié)構(gòu)
- 上面簡單的講述了一下什么是 I/O復(fù)用,以及將會用到的具體實現(xiàn)
epoll。那具體說一下,整個程序的流程
- 還是老規(guī)矩,寫程序之前要先構(gòu)思,自己在紙上畫一畫,大概的流程是什么
- 問題: 想要完整處理一個HTTP請求,需要哪些步驟?
- 解析HTTP請求報文
- 創(chuàng)建HTTP回復(fù)報文
- 邏輯就這么簡單啊,但是加上細節(jié)部分,就會稍微麻煩一些了:
- 完整地 從套接字中,讀取 HTTP請求報文
- 解析 HTTP請求報文,并判斷其有效性
- 生成 HTTP回復(fù)報文
- 完整地 通過對應(yīng)套接字,發(fā)送給請求者。
- 在這里我假設(shè),你已經(jīng)對TCP編程的模型很熟悉了,不熟的可以去頂部看看再回來
- 并發(fā)服務(wù)器的關(guān)鍵點就在于
- 高效且正確地接收盡可能多的連接
- 高效且正確地處理盡可能多的連接
- 以上忽略了安全性
- 該如何設(shè)計?
- 讓某個
epoll用來服務(wù)于接收新連接這個環(huán)節(jié)(accept)
- 讓某些
epoll用來處理這些新連接的事務(wù)。
- 這樣理論上我們既發(fā)揮了單核的極限(epoll),又用上了多核的優(yōu)勢(多個
epoll)
- 更具體的呢?
- 在主線程里使用單個
epoll來處理,監(jiān)聽套接字的讀事件,也就是接受新連接
- 再開幾個線程
epoll,用來平分處理這些新連接。
- 這樣也就是網(wǎng)絡(luò)編程的一整個流程,如果看到這里你已經(jīng)大概有了一個程序思路,實際上就已經(jīng)達到目的了,接下來就是直接上手代碼就行
- 還是迷迷糊糊的,就一步一步跟著我,寫出這個服務(wù)器,會大有脾益。
小經(jīng)驗,在編程中,讀往往比寫要復(fù)雜許多。在網(wǎng)絡(luò)編程里面亦是。
-
有圖有真相,希望能夠自己畫。

- 現(xiàn)在大致有了思路,可以整理整理自己接下來該干什么了
環(huán)境準(zhǔn)備
- 99%的中國大學(xué)學(xué)生的操作系統(tǒng),應(yīng)該都是 Windows或者Max OS(maxOS),那么建議你直接使用虛擬機進行環(huán)境的搭建,可以選擇開源免費的
Visual Box,Windows下也可以使用商業(yè)版的VMware,Mac下有一個更棒的商業(yè)版選擇Paralelle Desktop,但是這都是軟件,算是無關(guān)緊要的。
- 選擇一個
Linux發(fā)行版,由于我用的是 Debian系列的Ubuntu 16.04 LTS,所以我也推薦這個發(fā)行版,其他的發(fā)行版也許略有差異,不再多說。
- 裝好之后,直接進入開發(fā)階段吧。
- IDE可以選擇
Clion或者Kdevelop。
- 當(dāng)然你要用
Vim我也不會阻攔,但是請裝好兩個插件Nerdtree和YouCompleteMe,配合好另一個軟件tmux(簡單使用),不然你會想死。
- 除了
Vim,你也可以選擇 Visual Studio Code加裝一個C/C++ tools也是不錯的。
- 作為時尚的我,自然選擇
Clion了,簡單明了,且還是使用CMake作為構(gòu)建工具。
- 想要進行這么底層的網(wǎng)絡(luò)編程,請準(zhǔn)備好
Google和Unix網(wǎng)絡(luò)編程卷1,如果你兩個都沒有的話,不說了,再見。建議準(zhǔn)備一個那玩意兒去訪問Google。