并發模型:
在讨論HTTP面對并發連接配接之前我們先讨論一下銀行從業人員面對大量客戶時的工作機制,其實銀行的工作機制與HTTP的工作面對并發時的工作機制是類似的。
1. 如果一個銀行在剛開業隻有一個櫃台,假設接待一個客戶需要5分鐘的話,那麼如果同時來10個客戶,隻能先接待接待一個,讓另外的9個人先等着排列,如果隊列很多,很多人連大廳都坐不下,保安就不讓排隊了。
2. 銀行的人員越來越多,然後開了10個櫃台,同時來了100個使用者,還有90個等待,為什麼不能開100個櫃台呢?你不考慮成本的嗎!!又出現了隊列很長,很多人連大廳都坐不下,保安就不讓排隊了。
3. 開10個櫃台就已經是極限了,那麼怎樣才能加快工作的效率呢?我們可以做一些優化,原本一個櫃台隻有一個從業人員,服務一個客戶需要2分種,現在每個櫃台多個從業人員,第一個從業人員隻負責接待,第兩個從業人員負責列印票據,第三人從業人員負責處理文檔……,當第兩個從業人員列印票據的時候,第一個從業人員又可以為第二個客戶服務,從客戶的角度因為隔着窗戶,隻能看清最外面的負責接待的從業人員,其實裡面有很多從業人員工作,這話的話一個服務一個客戶可能僅需要幾秒的時間。

WEB伺服器的IO結構:(閱讀下面的文字請參照上文當中銀行的工作案例)
單線程結構:一次隻能接收一個請求,餘下的排隊,也可以稱做是單線程結構,因為在linux當中線程與程序的差別沒什麼差别,一般來講linux一個程序裡面也通常隻有一個線程。
多線程多請求結構:每個使用者都建立一個程序對應,但是建立的程序數是有限的
請注意是多請求結構而不是多使用者結構,為什麼?因為一個使用者可以發起多個請求,有100個請求并不意味着有100個使用者,多請求的是這樣的,HTTP的主程式偵聽到TCP80端口,一旦有有連接配接的話就把自己複制一份(生成一個子程序)到記憶體當中去處理連接配接的請求,而自己繼續偵聽TCP的80,當這個子程序處理完請求,建構好響應封包之後就會由其父程序将其銷毀,也就是說必須有一個程序(父程序)做配置設定管理才行。
複用的IO結構:也叫做單程序多請求模型,一個程序可以響應多個請求,它是怎樣做到的呢?向外看來是一個程序服務了多個請求,但是對内看來是這樣的:服務于多個請求的程序是并不是單純的一個程序,而是一個團隊,這個程序裡面有很多的“人”隻是外人看起來是一個程序在響應多個請求。
既然有三種模型那麼哪個占用的資源更少而且使用者體驗較好呢?
答:
在講明白這個問題之前,首先了解一點:在程序的角度看記憶體,整個記憶體都由自己占用。
CPU程序的切換,切換本身就會浪費時間
單程序的壞處有:崩潰,機制複雜,穩定性來說多線程多請求更好,資源節約來講單程序多請求最好。
伺服器接入并響應過程:
上圖中的虛線指的是核心空間與使用者空間的分隔.
其實上圖中的七步就基本的描述了用戶端通路站點的過程。下面用文字解釋一下
1) 建立連接配接,當然是用戶端先發起請求,與伺服器完成三次握手之後建立連接配接
2) 接收請求,當伺服器接收了請求之後,核心根據套節字把用戶端要請求的請求資源交給應用程式
3) 處理請求,應用程式不能調用硬碟當中的資源肯定是找核心調用
4) 通路資源,核心去硬碟調用資源
5) 建構響應,應用程式要建構響應封包
6) 發送響應,通過套節字發送給用戶端
7) 記錄事務的處理過程,這一步也要IO
從IO的角度看待響應:
第一步:用戶端與伺服器建立連接配接時,伺服器的通信子網(核心)首先判斷是不是給自己的資料,然後取出此資料通路的套節字,然後把資料的内容給偵聽到此套節字的程序。
第二步:用戶端的請求無非也就是請求資源,而資源一般都放在磁盤上,而資源子網的應用無權限通路磁盤,這時程式會發起系統調用,代碼的執行由使用者空間切換到了核心空間,然後核心去磁盤調用加載磁盤當中資料,那麼問題來了?核心把資料調用到哪裡去了?是直接把資料放在程序的記憶體空間了嗎?
第三步:雖然核心有這個能力,但是沒有那麼做,核心通常把從磁盤調用的資料放在自己的核心空間的當中了,而這部分空間被我們稱之為cache,然後再給程序多配置設定一些頁框,最後把cache當中的資料複制一份到程序的頁框當中,注意,程序沒有權限通路核心空間的資料。
第四步:資料進入使用者空間程序建構響應封包,送出給核心。
讓我們總結一下:上文當中的資料流向是這樣的:核心使用者-----使用者空間------核心空間--------使用者空間------核心空間
通過上文我們可以知道被用戶端請求的檔案被調用了2次,,事實上所有的IO都是這樣.
那麼我們可不可以讓核心調用完資料後不用交給核心直接給用戶端響應呢?可能你會問,如果不交給應用層的應用的話怎樣建構響應封包呢?事實上,核心是可以建構響應封包的,也就是說核心調用完資源之後直接給用戶端回應不用經過使用者空間建構響應封包,它是怎樣做到的呢?在一個系統調用 sed-file可以幫助我們把建構響應封包,我們可以在httpd的配置檔案當中,直接就調用它,這樣的話,使用者的通路的速度會加快很多,伺服器本身的資源也會節省很多,就變成了下面這樣:
從函數的角度看待響應:
某個服務想要偵聽到某個套接字上“坐診”,必須要調用四個函數(如果是C語言):socket(),bind(),.listen(),accept(),首先,服務先人調用socket()API用來申請一個套接字,當然隻申請完一個套接字是遠遠不夠的,申請了之後要把自己(服務綁定(bind())到申請的套接字上,僅僅綁定還是不夠,還要調用listen()API偵聽在此套接字上,當申請、綁定、偵聽完成之後接下來就可以“接客”了,會調用一個accept()API等待接收别人的通路,會處于一直阻塞的狀态等待用戶端的連接配接。
做為用戶端同樣也是需要申請套接字的socket(),申請完成之後就是下一步主動連接配接(connetct)服務端,建立連接配接有四個元素,源IP端口,目标IP端口,這也就是為什麼用戶端也要申請套接字的原因了,建立連接配接當然是通過三次握手建立 ,三次握手完成之間意味着從用戶端到伺服器的虛鍊路建立完成,虛鍊路建立完成之後就會把此虛鍊路儲存成一個套接字檔案(當然伺服器也會這麼做了),一切皆檔案嘛!當然這個檔案當中主要也就是有四個元素:本地的IP端口,伺服器端的IP端口。下一步是用戶端要向伺服器發出一個申請,怎樣發的呢?因為用戶端有了虛鍊路的套接字檔案以此來辨別與伺服器之間的連接配接,用戶端發起請求時可以直接調用write()函數直接把請求的資訊寫入到套接字檔案當中,然後用戶端會通過此套接字連接配接起來的虛鍊路把請求發給伺服器那邊的套接字檔案(當建立完成三次握手,伺服器和用戶端各自維護一個套接字),伺服器端的套接字接收到請求之後,伺服器端就要從套接字檔案當中讀出(read())一些資訊出來,這裡面就是伺服器端的請求資訊,請求的資源無非也就是伺服器端磁盤的資源,很有可能是html檔案,然後就處理請求,怎樣處理呢?處理就非常的簡單了,http通過核心去存儲器當中讀取資料,讀取完之後建構響應封包,把響應封包寫入到伺服器自己維護的套接字檔案當中,這下論到用戶端方接收資訊了,用戶端就會去套節字檔案當中讀取資源,發現此資源并不完整,裡面包含很多的圖檔,用戶端會再向伺服器端請求,伺服器端會再次響應,循環下去直到資源完成。
當資源傳輸完成之後,用戶端會終止此連接配接并且會向套節字當中發起close()終止信号,當伺服器收到之後同樣還是先讀取,讀取一看是是終止資訊,于是自己也處于終止狀态。用戶端是主動關閉的,而伺服器是被動關閉的。
日志處理:
日志也會産生IO,每處理一份事務HTTP都會産生IO,那麼這個IO如果每次都要寫入到磁盤當中的話,IO次數太過頻繁,影響伺服器的性能,還好我們可以自己規定多長時間IO一次,比如我們規定1分鐘IO一次,1分鐘中所有的日志先暫時放入到記憶體裡面,到了規定的時候,一塊打包放入磁盤裡面.這樣的話能夠減少一部分的IO.
如果規定的時間過長,一旦斷電就都丢失的資料比較多,但是IO次數減少了
如果規定的時間過短,一旦斷電丢失的資料比較少,但是IO次數要多一些.