TCP/IP協議隨筆
今天翻博客的時候看到了TCP/IP協議相關的幾篇文章,寫的非常好,LZ打算把其中的重點整理一下,雖然都是一些概念性的東西,平時編碼的時候可能用不到,但是起碼我們應該知道自己是在哪一層編碼,又有哪些協議在默默的幫我們傳遞數據。
一、TCP/IP是什麽
LZ用自己的話回答一下這個問題,tcp/ip是什麽?
簡單的說,tcp/ip就是一套規矩。兩個計算機要互相通信,就得有規矩,一個非常普遍的例子就是發信件。想像一下,如果信件上面的地址和郵編等信息是隨便寫的,那麽目的地的郵遞員收到信件時,一定會一頭霧水,不知道到底要送到哪。同樣的,如果收件人是“額買瑞肯”,而你寫的字是“拆泥漬”,對方肯定是看不懂的,所以雙方的語言也要相通。
計算機之間要通信,也需要規矩,於是tcp/ip就出現了。tcp和ip分別是兩個協議,而tcp/ip一般泛指一個協議族。這個協議族一共包含四層,分別是鏈路層,網絡層,傳輸層以及應用層。其中tcp是傳輸層的協議,ip則是網絡層的協議。
之所以分層,原因就像mvc框架的作用一樣,是為了讓專業的人做專業的事。還記得設計模式當中的單一職責原則嗎,這裏其實也正是這個原則的體現。每一層都只做自己應該做的事情,並且把它做好,這樣有利於擴展和維護。
二、四層簡介
接下來,LZ就自己的理解,給大家簡單介紹一下各個分層。
鏈路層:這是最接近硬件的一層,也是俗稱的“底層”。它主要指我們俗稱的網卡和網卡驅動程序,網卡是硬件,驅動程序則負責控制網卡。鏈路層主要負責與傳輸介質(比如光纖)配合完成實際的數據傳輸工作。你可以把它想像成高速公路上可惡的收費站,每一條公路的入口和出口都有一個收費站,而這些收費站之間是有協議的,比如按路程收費。這些收費站就像網絡接口一樣,而公路則像光纖一樣。
網絡層:這一層淩駕於鏈路層之上,主要負責數據報在網絡中的活動,比如該往哪走。這一層就像高速公路上的路標,路標決定了你每到一個路口應該怎麽走。網絡層恰恰就是做這件事,比如ip協議,其實就是制定一套規則,讓數據按照這套規則可以找到目的地。
傳輸層:這一層已經比較接近我等屌絲了,它負責應用程序之間端到端的通信,這麽說可能比較難理解。這個比較像物流中的一個場景,比如現在LZ在北京,並且在上海買了一個商品要送過來,那麽過程應該是這樣的。首先由上海的快遞小哥把LZ的商品拿走,然後交給向北京出發運送貨物的司機,接下來LZ的商品到北京後會被交給物流公司的統一發貨處,最終由快遞小哥負責把商品送到LZ手中。請註意,這個過程中,貨車司機並不關心貨物到底送到哪,他只管送到北京,而最終快遞小哥需要把貨物送給具體的收貨人。這和網絡層與傳輸層的關系類似,網絡層只管把數據報從一個計算機送給另外一個計算機,具體是哪個進程的數據,網絡層可不管,這便是傳輸層負責的事了。這裏的端到端通信,其實就是指端口到端口的通信。
應用層:這一層與我等屌絲已經有肌膚之親,可謂是再熟悉不過了。比如你天天要用的http協議,它就是應用層的一個協議。http定義了各種協議頭,什麽請求協議頭,響應協議頭,亂七八糟的。這些協議頭都是幹嘛的呢,既然是應用層,當然是給應用程序用的了。比如響應協議頭中有文本格式這個頭,它就定義了瀏覽器應該如何展示web服務器發回來的響應信息,比如xml,html還是什麽其它的玩意。
三、四層小結
這麽一看,其實tcp/ip協議族挺好理解的,接下來LZ還用上面物流的例子來說明。
鏈路層就是負責運輸LZ女朋友的那個司機,他是實際負責運輸工作的(也就是實際傳輸數據的工作)。網絡層就是高速路上的路標,負責指引司機在高速上該如何走(也就是在網絡上的活動),並讓司機正確到達目的地。傳輸層則是快遞小哥,負責在具體的發貨人和收獲人之間(也就是發送端口和接收端口)的接收。應用層就像是LZ女朋友的使用說明,這就是LZ和賣家之間(也就是應用程序之間)的事了。
四、TCP/IP與OSI
我們經常聽到什麽七層模型,OSI和TCP/IP究竟是什麽關系?
OSI共有七層,分別是物理層,數據鏈路層,網絡層,傳輸層,會話層,表示層和應用層。TCP/IP協議族共有四層,分別是鏈路層,網絡層,傳輸層和應用層。
簡而言之,它們最大的區別是,OSI只是參考模型,而TCP/IP是目前實際使用的一個協議族,它已經被大部分操作系統所實現。它們的對應關系如下。
可以看到,TCP/IP協議族簡化了OSI模型,其實這種現象在實際的開發過程中也很常見,LZ舉個簡單的例子大家就清楚了。
相信web項目的開發大部分猿友都不陌生,一般情況下,咱們的分層是Action,Service,Dao這種三層方式,但是在實際開發中,往往不一定按照這個分層去開發。比如有些比較小的項目,會刪除Service這一層,由Action直接引用Dao。
這其實就和OSI與TCP/IP的關系一樣,參考模型始終是參考用的,實際當中不一定就得按照這個去實現。
五、HTTP協議中的短輪詢、長輪詢、長連接和短連接
以前的誤解
很久之前LZ就聽說過長連接的說法,而且還知道HTTP1.0協議不支持長連接,從HTTP1.1協議以後,連接默認都是長連接。但LZ終究覺得對於長連接一直懵懵懂懂的,有種抓不到關鍵點的感覺。
LZ一直認為,HTTP連接分為長連接和短連接,而我們現在常用的都是HTTP1.1,因此我們用的都是長連接。
這句話其實只對了一半,我們現如今的HTTP協議,大部分都是1.1的,因此我們平時用的基本上都是長連接。但是前半句是不對的,HTTP協議根本沒有長短連接這一說,也正因為誤解了這個,導致LZ對於長連接一直不明不白,始終不得其要領,具體下面一段會說到。
網絡上很多文章都是誤人子弟,根本沒有說明白這個概念。這裏LZ要強調一下,HTTP協議是基於請求/響應模式的,因此只要服務端給了響應,本次HTTP連接就結束了,或者更準確的說,是本次HTTP請求就結束了,根本沒有長連接這一說。那麽自然也就沒有短連接這一說了。
之所以網絡上說HTTP分為長連接和短連接,其實本質上是說的TCP連接。TCP連接是一個雙向的通道,它是可以保持一段時間不關閉的,因此TCP連接才有真正的長連接和短連接這一說。
其實知道了以後,會覺得這很好理解。HTTP協議說到底是應用層的協議,而TCP才是真正的傳輸層協議,只有負責傳輸的這一層才需要建立連接。
一個形象的例子就是,拿你在網上購物來說,HTTP協議是指的那個快遞單,你寄件的時候填的單子就像是發了一個HTTP請求,等貨物運到地方了,快遞員會根據你發的請求把貨物送給相應的收貨人。而TCP協議就是中間運貨的那個大貨車,也可能是火車或者飛機,但不管是什麽,它是負責運輸的,因此必須要有路,不管是地上還是天上。那麽這個路就是所謂的TCP連接,也就是一個雙向的數據通道。
因此,LZ現在甚至覺得,“HTTP連接”這個詞就不應該出現,它只是一個應用層的協議,根本就沒有所謂的連接這一說。
實際上,說HTTP請求和HTTP響應會更準確一些,而HTTP請求和HTTP響應,都是通過TCP連接這個通道來回傳輸的。
不管怎麽說,一定要務必記住,長連接是指的TCP連接,而不是HTTP連接。
一個疑問
之前LZ一直對一件事有些模糊不清,首先是怎麽樣就算是把HTTP變成長連接了,是不是只要設置Connection為keep-alive就算是了?
如果是的話,那都說HTTP1.1默認是長連接,而觀察我們平時開發的Web應用的HTTP頭部,Connection也確實是keep-alive,那就是說我們大部分都是用的長連接,但是長連接不是一般用於交互比較頻繁的應用嗎?像我們這種普通的Web應用,比如博客園這種,長連接有什麽用?
如果沒用的話,那到底應不應該把Connection為keep-alive這個header值給改掉,從而改成短連接?
明白了長連接實際上是指的TCP連接,LZ瞬間自己就想明白了上面的那些問題。
第一個問題是,是不是只要設置Connection為keep-alive就算是長連接了?
當然是的,但要服務器和客戶端都設置。
第二個問題是,我們平時用的是不是長連接?
這個也毫無疑問,當然是的。(現在用的基本上都是HTTP1.1協議,你觀察一下就會發現,基本上Connection都是keep-alive。而且HTTP協議文檔上也提到了,HTTP1.1默認是長連接,也就是默認Connection的值就是keep-alive)
第三個問題,也是LZ之前最想不明白的問題,那就是我們這種普通的Web應用(比如博客園,我的個人博客這種)用長連接有啥好處?需不需要關掉長連接而使用短連接?
這個問題LZ現在終於明白了,問題的答案是好處還是有的。
好處是什麽?
首先,剛才已經說了,長連接是為了復用,這個在之前LZ就明白。那既然長連接是指的TCP連接,也就是說復用的是TCP連接。那這就很好解釋了,也就是說,長連接情況下,多個HTTP請求可以復用同一個TCP連接,這就節省了很多TCP連接建立和斷開的消耗。
比如你請求了博客園的一個網頁,這個網頁裏肯定還包含了CSS、JS等等一系列資源,如果你是短連接(也就是每次都要重新建立TCP連接)的話,那你每打開一個網頁,基本要建立幾個甚至幾十個TCP連接,這浪費了多少資源就不用LZ去說了吧。
但如果是長連接的話,那麽這麽多次HTTP請求(這些請求包括請求網頁內容,CSS文件,JS文件,圖片等等),其實使用的都是一個TCP連接,很顯然是可以節省很多消耗的。
這樣一解釋,就很明白了,不知道大家看了這些解釋感覺如何,反正LZ在自己想明白以後,有種豁然開朗的感覺。
另外,最後關於長連接還要多提一句,那就是,長連接並不是永久連接的。如果一段時間內(具體的時間長短,是可以在header當中進行設置的,也就是所謂的超時時間),這個連接沒有HTTP請求發出的話,那麽這個長連接就會被斷掉。
這一點其實很容易理解,否則的話,TCP連接將會越來越多,直到把服務器的TCP連接數量撐爆到上限為止。現在想想,對於服務器來說,服務器裏的這些個長連接其實很有數據庫連接池的味道,大家都是為了節省連接重復利用嘛,對不對?
長輪詢和短輪詢
前面基本上LZ已經把長短連接說的差不多了,接下來說說長短輪詢,今天也正是為了研究長短輪詢,LZ才順便研究了下長短連接這回事。
短輪詢相信大家都不難理解,比如你現在要做一個電商中商品詳情的頁面,這個詳情界面中有一個字段是庫存量(相信這個大家都不陌生,隨便打開淘寶或者京東都能找到這種頁面)。而這個庫存量需要實時的變化,保持和服務器裏實際的庫存一致。
這個時候,你會怎麽做?
最簡單的一種方式,就是你用JS寫個死循環,不停的去請求服務器中的庫存量是多少,然後刷新到這個頁面當中,這其實就是所謂的短輪詢。
這種方式有明顯的壞處,那就是你很浪費服務器和客戶端的資源。客戶端還好點,現在PC機配置高了,你不停的請求還不至於把用戶的電腦整死,但是服務器就很蛋疼了。如果有1000個人停留在某個商品詳情頁面,那就是說會有1000個客戶端不停的去請求服務器獲取庫存量,這顯然是不合理的。
那怎麽辦呢?
長輪詢這個時候就出現了,其實長輪詢和短輪詢最大的區別是,短輪詢去服務端查詢的時候,不管庫存量有沒有變化,服務器就立即返回結果了。而長輪詢則不是,在長輪詢中,服務器如果檢測到庫存量沒有變化的話,將會把當前請求掛起一段時間(這個時間也叫作超時時間,一般是幾十秒)。在這個時間裏,服務器會去檢測庫存量有沒有變化,檢測到變化就立即返回,否則就一直等到超時為止。
而對於客戶端來說,不管是長輪詢還是短輪詢,客戶端的動作都是一樣的,就是不停的去請求,不同的是服務端,短輪詢情況下服務端每次請求不管有沒有變化都會立即返回結果,而長輪詢情況下,如果有變化才會立即返回結果,而沒有變化的話,則不會再立即給客戶端返回結果,直到超時為止。
這樣一來,客戶端的請求次數將會大量減少(這也就意味著節省了網絡流量,畢竟每次發請求,都會占用客戶端的上傳流量和服務端的下載流量),而且也解決了服務端一直疲於接受請求的窘境。
但是長輪詢也是有壞處的,因為把請求掛起同樣會導致資源的浪費,假設還是1000個人停留在某個商品詳情頁面,那就很有可能服務器這邊掛著1000個線程,在不停檢測庫存量,這依然是有問題的。
因此,從這裏可以看出,不管是長輪詢還是短輪詢,都不太適用於客戶端數量太多的情況,因為每個服務器所能承載的TCP連接數是有上限的,這種輪詢很容易把連接數頂滿。之所以舉這個例子,只是因為大家肯定都會網購,所以這個例子比較通俗一點。
長短輪詢和長短連接的區別
這裏簡單說一下它們的區別,LZ這裏只說最根本的區別。
第一個區別是決定的方式,一個TCP連接是否為長連接,是通過設置HTTP的Connection Header來決定的,而且是需要兩邊都設置才有效。而一種輪詢方式是否為長輪詢,是根據服務端的處理方式來決定的,與客戶端沒有關系。
第二個區別就是實現的方式,連接的長短是通過協議來規定和實現的。而輪詢的長短,是服務器通過編程的方式手動掛起請求來實現的。
TCP/IP協議隨筆