因特網(wǎng)無(wú)疑是人類(lèi)有史以來(lái)最偉大的設計,它互聯(lián)了全球數億臺計算機、通訊設備,即便位于地球兩端的用戶(hù)也可在頃刻間完成通訊。
可以說(shuō)『協(xié)議』是支撐這么一個(gè)龐大而復雜的系統有條不紊運作的核心,而所謂『協(xié)議』就是通訊雙方所必須遵守的規則,在這種規則下,不同的數據報可能被解析為不同的響應動(dòng)作。
簡(jiǎn)而言之,『協(xié)議』就是指如果發(fā)送和接收方按照這個(gè)規則進(jìn)行數據報文的發(fā)送,即可在基本的數據傳輸之上得到某些特殊的功能或服務(wù),否則你的數據別人是不認識的。例如:遵循 TCP 協(xié)議的兩端,可以在不可靠的網(wǎng)絡(luò )傳輸中得到可靠的數據傳輸能力。
整個(gè)計算機網(wǎng)絡(luò )是分層的,有七層模型,也有五層模型,個(gè)人覺(jué)得五層模型更利于理解。我們從上至下的介紹這五個(gè)層,它們分別是,應用層,運輸層,網(wǎng)絡(luò )層,數據鏈路層和物理層。
『應用層』算是距離用戶(hù)最近的一層了,主機上的一個(gè)個(gè)的進(jìn)程就構成了『應用層』。比如你在你的瀏覽器地址欄輸入了 「www.baidu.com」,你的瀏覽器在應用層會(huì )做哪些事情呢?
首先瀏覽器會(huì )使用 DNS 協(xié)議返回域名「www.baidu.com」所對應的 IP 地址,關(guān)于 DNS 我們待會(huì )詳細介紹。
接著(zhù),應用層決定創(chuàng )建一個(gè)『TCP 套接字』,然后將這個(gè)請求動(dòng)作封裝成一個(gè) Http 數據報并推入套接字中。
套接字分為兩種類(lèi)型,『TCP 套接字』和『UDP 套接字』,應用層同時(shí)可能會(huì )有幾十個(gè)數據報的發(fā)出,而運輸層也會(huì )收到所有的響應報文,那么它該如何區分這些報文到底是誰(shuí)的響應報文呢?
而套接字就是用于區分各個(gè)應用層應用的,往往由端口號和 IP 地址進(jìn)行標識,運輸層只要查看響應報文的源端口號和 IP 地址就能夠知道該將報文推送給哪個(gè)套接字了。
當一個(gè)應用層數據報被推動(dòng)進(jìn)套接字之后,應用層的所有工作也算是全部完成了,關(guān)于后續報文的去向,它已經(jīng)不用管了。
這里還要說(shuō)明一點(diǎn)的是,『TCP 套接字』和『UDP 套接字』兩者本質(zhì)上的區別在于,前者保證數據報可靠地到達目的地,但是必然耗時(shí),而后者不保證數據報一定能到達目的地,但是速度快,這也是應用層協(xié)議在選擇運輸層協(xié)議的時(shí)候需要考慮的一點(diǎn)。
關(guān)于 TCP 和 UDP,我們后續還會(huì )繼續說(shuō),下面我們看看域名解析協(xié)議 DNS 是如何運作的,它是如何將一個(gè)域名解析返回它的 IP 地址的。
DNS 原理
首先明確一點(diǎn)的是,DNS 是一個(gè)應用層協(xié)議,并且它選擇的運輸層協(xié)議是 UDP,所以你的域名解析過(guò)程一般會(huì )很快,但也會(huì )經(jīng)常出現解析失敗的情況,然而刷新一下又好了。
在 DNS 服務(wù)器上,域名和它所對應的 IP 地址存儲為一條記錄,而所有的記錄都不可能只存儲在一臺服務(wù)器上,我相信無(wú)論多么強大的服務(wù)器都扛不住全球上億次的并發(fā)量吧。
大致來(lái)說(shuō),有三種類(lèi)型的 DNS 服務(wù)器,根 DNS 服務(wù)器,頂級域 DNS 服務(wù)器和權威 DNS 服務(wù)器。
其中,頂級域 DNS 服務(wù)器主要負責諸如 com、org、net、edu、gov 等頂級域名。
根 DNS 服務(wù)器存儲了所有頂級域 DNS 服務(wù)器的 IP 地址,也就是說(shuō)你可以通過(guò)根服務(wù)器找到頂級域服務(wù)器。例如:「www.baidu.com」,根服務(wù)器會(huì )返回所有維護 com 這個(gè)頂級域服務(wù)器的 IP 地址。
然后你任意選擇其中一個(gè)頂級域服務(wù)器,請求該頂級域服務(wù)器,該頂級域服務(wù)器拿到域名后應當能夠做出判斷并給出負責當前域的權威服務(wù)器地址,以百度為例的話(huà),頂級域服務(wù)器將返回所有負責 baidu 這個(gè)域的權威服務(wù)器地址。
于是你可以任意選擇其中一個(gè)權威服務(wù)器地址,向它繼續查詢(xún) 「www.baidu.com」 的具體 IP 地址,最終權威服務(wù)器會(huì )返回給你具體的 IP 地址。
至此,我們簡(jiǎn)單描述了一個(gè)域名解析的大致過(guò)程,還有一些細節之處并未提及,我們等會(huì )會(huì )通過(guò)一個(gè)實(shí)例來(lái)完整的看一下,下面描述一個(gè)非常重要的概念。
整個(gè) DNS 解析過(guò)程中,有一個(gè)非常核心的人物我們一直沒(méi)介紹它,它就像主機的『助理』一樣,幫助主機查詢(xún)域名的 IP 地址。它叫做『本地 DNS 服務(wù)器』。
大家每次通過(guò) DHCP 動(dòng)態(tài)獲取 IP 地址的時(shí)候,這一點(diǎn)后文會(huì )說(shuō)。其實(shí)路由器不僅給你返回了 IP 地址,還會(huì )告訴你一個(gè) DNS 服務(wù)器地址,這個(gè)就是你的本地 DNS 服務(wù)器地址,也就是說(shuō),你的所有域名解析請求只要告訴它就行了,它會(huì )幫你查并返回結果給你的。
除此之外,本地 DNS 服務(wù)器往往是具有緩存功能的,通常兩天內的記錄都會(huì )被緩存,所以大部分時(shí)候你是感覺(jué)不到域名解析過(guò)程的,因為往往就是從緩存里拿的,非???。
下面我們看一個(gè)簡(jiǎn)單的案例:
網(wǎng)上找的一個(gè)圖,自己畫(huà)實(shí)在太費時(shí)間了,但足以說(shuō)明問(wèn)題,現在假設請求 「www.xx.com」 。
①:主機向負責自己的本地 DNS 發(fā)送查詢(xún)報文,如果本地服務(wù)器緩存中有,將直接返回結果
②:本地服務(wù)器發(fā)現緩存中沒(méi)有,于是從內置在內部的根服務(wù)器列表中選一個(gè)發(fā)送查詢(xún)報文
③:根服務(wù)器解析一下后綴名,告訴本地服務(wù)器負責 .com 的所有頂級服務(wù)器列表
④:本地服務(wù)器選擇一個(gè)頂級域服務(wù)器繼續查詢(xún),.com 域服務(wù)器拿到域名后繼續解析,返回負責 .xx 域的所有權威服務(wù)器列表
⑥:本地服務(wù)器從返回的權威服務(wù)器之一再次發(fā)送查詢(xún)報文,最終會(huì )從某一個(gè)權威服務(wù)器上得到具體的 IP 地址
⑧:向主機返回結果
其實(shí)整個(gè) DNS 報文的發(fā)送與響應過(guò)程都是要走我們的五層協(xié)議的,只是這里重點(diǎn)在于理解 DNS 協(xié)議本身,所以并未提及其他層的具體細節,這里的強調是提醒你 DNS 只是一個(gè)應用層協(xié)議。
運輸層的任務(wù)就是將應用層推出套接字的所有數據報收集起來(lái),并且按照應用層指定的運輸層協(xié)議,TCP 或 UDP,重新封裝應用層數據報,并推給網(wǎng)絡(luò )層等待發(fā)送。
TCP 和 UDP 是運輸層的兩個(gè)協(xié)議,前者是基于連接的可靠傳輸協(xié)議,后者是無(wú)連接的不可靠傳輸協(xié)議,所以前者更適合于一些對數據完整性要求高的場(chǎng)合,后者則適合于那種可以允許數據丟失但對傳輸速率要求特別高的場(chǎng)景,例如:語(yǔ)音電話(huà),視頻等,丟一兩個(gè)包最多卡頓一下,無(wú)傷大雅。
UDP
UDP 不同于 TCP 那樣復雜,它既不保證數據可靠的傳輸到目的地,也不保證數據按序到達目的地,僅僅提供了簡(jiǎn)單的差錯檢驗。報文格式如下:
其中,數據就是應用層推出來(lái)的數據,源端口號用于響應報文的交付,目的端口號用于向目的進(jìn)程交付數據,校驗和用于檢查傳輸過(guò)程中數據是否受損,如果受損,UDP 將直接丟棄該報文。
TCP
TCP 要稍微復雜些,它是面向連接的,并且基于連接提供了可靠的數據傳輸服務(wù),它的數據報文格式如下:
單純的解釋報文格式中各個(gè)字段的含義并沒(méi)有太過(guò)實(shí)際的意義,你也很難理解了,在我們介紹 TCP 是如何『三次握手』,『四次揮手』以及『丟包重傳』等動(dòng)作時(shí),不間斷的會(huì )說(shuō)明這些動(dòng)作時(shí)如何使用報文中的相關(guān)字段的。
首先我們來(lái)看耳熟能詳的『三次握手』,這基本上是 TCP 的代名詞了,無(wú)論懂不懂具體原理的人,提到 TCP,基本上都是知道『三次握手』的。
而本身,TCP 的三次握手就是為了確保通訊雙方能夠穩定的建立連接并完成數據報文的請求與響應動(dòng)作,至于為什么是三次握手而不是四次五次,這是一個(gè)哲學(xué)問(wèn)題,這里就不做討論了。
第一步:
客戶(hù)端向服務(wù)端發(fā)送一份特殊的 TCP 報文,該報文并不包含應用層的數據,是一份特殊的報文,它的 TCP 首部中 SYN 字段值為 1 (參見(jiàn)上述報文格式)。
除此之外,客戶(hù)端還會(huì )隨機生成一個(gè)初始序號,填在報文的「序號」字段,代表當前報文的序號是這個(gè),并且我后續的分組會(huì )基于這個(gè)序號遞增。
然后該報文將會(huì )經(jīng)網(wǎng)絡(luò )層、鏈路層、物理層發(fā)送到服務(wù)端。
第二步:
如果分組丟失了,那么客戶(hù)端會(huì )經(jīng)過(guò)某個(gè)時(shí)間間隔再次嘗試發(fā)送。
而如果分組準確的到達服務(wù)端了,服務(wù)端拆開(kāi) TCP 首部會(huì )看到,這是一個(gè)特殊的 SYN 握手報文,于是為此次連接分配緩存等資源。
接著(zhù)服務(wù)端開(kāi)始構建響應報文,SYN 是一個(gè)用于同步需要的字段,響應報文中依然會(huì )被置為 1,并且服務(wù)端也將隨機生成一個(gè)初始序號放置的響應報文的序號字段中。
最后,服務(wù)端還會(huì )為響應報文中的確認字段賦值,這個(gè)值就是客戶(hù)端發(fā)過(guò)來(lái)的那個(gè)序號值加一。
整體上的意思就是說(shuō),「我同意你的連接請求,我的初始序號為 xxx,你的初始序號我收到了,我等著(zhù)你的下一個(gè)分組到來(lái)」
第三步:
客戶(hù)端收到服務(wù)端的響應報文,于是分配客戶(hù)端 TCP 連接所必須的緩存等資源,于是連接已經(jīng)建立。
實(shí)際上從第三步開(kāi)始,客戶(hù)端就可以攜帶應用層數據向服務(wù)端交換報文了,以后的每份報文中,SYN 都為 0,因為它只是用于同步初始序號的,這一點(diǎn)需要明確。
總的來(lái)說(shuō),整個(gè)『握手』過(guò)程大致如下圖所示:

下面我們看看拆除一條 TCP 連接的『四次揮手』是怎樣的過(guò)程。
因為一條 TCP 連接會(huì )消耗大量的主機資源,不僅僅服務(wù)端需要分配各種緩存資源,客戶(hù)端也同樣需要分配相應資源。因為 TCP 是『全雙工通信』,服務(wù)端和客戶(hù)端兩方其實(shí)是一樣的,誰(shuí)是客戶(hù)誰(shuí)是服務(wù)器是相對的。
強調這一點(diǎn)是為了說(shuō)明,一條 TCP 連接不是只有客戶(hù)端才能斷開(kāi),服務(wù)端也同樣可以主動(dòng)斷開(kāi)連接,這一點(diǎn)需要清楚。
我們這里假設客戶(hù)端主動(dòng)發(fā)起斷開(kāi)連接的請求為例:
第一步:
客戶(hù)端構建一份特殊的 TCP 報文,該報文首部字段 FIN 被置為 1,然后發(fā)送該報文。
第二步:
服務(wù)端收到該特殊的 FIN 報文,于是響應客戶(hù)端一個(gè) ACK 報文,告訴客戶(hù)端,請求關(guān)閉的報文已經(jīng)收到,我正在處理。
第三步:
服務(wù)端發(fā)送一個(gè) FIN 報文,告訴客戶(hù)端,我將要關(guān)閉連接了。
第四步:
客戶(hù)端返回一個(gè) ACK 響應報文,告訴服務(wù)端,我收到你剛才發(fā)的報文了,我已經(jīng)確認,你可以關(guān)閉連接了。
當服務(wù)端收到客戶(hù)端發(fā)送的 ACK 響應報文時(shí),將釋放服務(wù)端用于該 TCP 連接的所有資源,與此同時(shí),客戶(hù)端也會(huì )定時(shí)等待一定時(shí)間后完全釋放自己用于該連接的相關(guān)資源。
用一張圖更直觀(guān)的描述一下:

結合著(zhù)圖與相關(guān)序號信息,我們再詳細說(shuō)說(shuō)其中的一些細節。
首先,客戶(hù)端發(fā)送一個(gè)特殊分組,該分組的序號為 u。發(fā)送完成之后,客戶(hù)端進(jìn)入 FIN-WAIT-1 這個(gè)狀態(tài),這個(gè)狀態(tài)下,該 TCP 連接的客戶(hù)端不再能發(fā)送數據報,但是是可以接受數據報的,它等待著(zhù)服務(wù)端的響應報文。
接著(zhù),服務(wù)端收到客戶(hù)端發(fā)送的終止連接報文請求,服務(wù)端構建響應報文,告訴客戶(hù)端「序號 u 1 以前的分組我都收到了」,并且進(jìn)入 CLOSE-WAIT 狀態(tài),這個(gè)狀態(tài)持續時(shí)間很短。
服務(wù)端會(huì )緊接著(zhù)發(fā)送它的 FIN 數據報,通知客戶(hù)端我服務(wù)端即將關(guān)閉連接,并隨即進(jìn)入 LAST_ACK 狀態(tài)等待客戶(hù)端響應報文。
一旦客戶(hù)端收到這個(gè) FIN 報文,將返回確認報文并進(jìn)入 TIME-WAIT 狀態(tài),等待 2MSL 時(shí)間間隔后完全釋放客戶(hù)端 TCP 連接所占用資源。
與此同時(shí),當服務(wù)端收到客戶(hù)端最后的確認報文,就將直接斷開(kāi)服務(wù)端連接并釋放相關(guān)資源。
至于為什么最后客戶(hù)端需要等 2MSL 時(shí)間長(cháng)度再完全釋放 TCP 相關(guān)資源呢?
那是因為 2MSL 是一份報文存在于網(wǎng)絡(luò )中最長(cháng)的時(shí)間,超過(guò)該時(shí)間到達的報文都將被丟棄,而如果客戶(hù)端最后的確認報文于網(wǎng)絡(luò )中丟失的話(huà),服務(wù)端必將發(fā)起超時(shí)請求,重新發(fā)送第三次揮手動(dòng)作,此時(shí)等待中的客戶(hù)端就可隨即重新發(fā)送一份確認請求。
這是為什么客戶(hù)端等待一個(gè)最長(cháng)報文傳輸時(shí)間的原因。有人可能好奇為什么前面的各次請求都沒(méi)有做超時(shí)等待而只最后一次數據發(fā)送做了超時(shí)等待?
其實(shí)原因很簡(jiǎn)單,相信你也能想到,就是 TCP 自帶計時(shí)能力,超過(guò)一定時(shí)間沒(méi)有收到某個(gè)報文的確認報文,會(huì )自動(dòng)重新發(fā)送,而這里如果不做等待而直接關(guān)閉連接,那么我如何知道服務(wù)端到底收到?jīng)]我的確認報文呢。
通過(guò)等待一個(gè)最長(cháng)周期,如果這個(gè)周期內沒(méi)有收到服務(wù)端的報文請求,那么我們的確認報文必然是到達了服務(wù)端了的,否則重復發(fā)送一次即可。
至此,TCP 的『三次握手』和『四次揮手』我們已經(jīng)簡(jiǎn)單描述完成了,下面我們看看 TCP 的一些其他特性,比如:可靠傳輸,擁塞控制等
首先我們來(lái)看 TCP 是如何實(shí)現可靠傳輸的,即如何解決網(wǎng)絡(luò )傳輸中丟包的問(wèn)題。
TCP 使用『回退 N 步』協(xié)議實(shí)現的可靠傳輸,準確來(lái)說(shuō),TCP 是在它的基礎上進(jìn)行了一部分優(yōu)化。

『回退 N 步』協(xié)議也被稱(chēng)作『滑動(dòng)窗口』協(xié)議,即最多允許發(fā)送方有 N 個(gè)「已發(fā)送但未被確認」的數據報文,如圖所示,p1 到 p3 長(cháng)度即為 N,這里的窗口指的就是 p1 到 p3 這個(gè)區間。
只有當發(fā)送端收到 p1 的確認報文后,整個(gè)窗口才能向前滑動(dòng),而實(shí)際上在沒(méi)有收到 p1 的確認報文前,即便它后面的報文已經(jīng)被接收,服務(wù)端也僅僅會(huì )緩存這些『非預期的報文』
直到服務(wù)端收到最小預期的那個(gè)報文后,從緩存中取出已經(jīng)到達的后續報文,合并并向上交付,然后向發(fā)送端返回一個(gè)確認報文。
當發(fā)送端窗口從左往右已經(jīng)連續多個(gè)報文被確認后,整個(gè)窗口將向前滑動(dòng)多個(gè)單位長(cháng)度。
下面我們看一個(gè)例子:

這是一個(gè)發(fā)送方的窗口,灰色表示已經(jīng)被確認的報文,黃色表示已發(fā)送但未被確認的報文,綠色表示下一個(gè)待發(fā)送的報文,白色表示不可用的報文。
這是我們假設服務(wù)端已經(jīng)收到 6、7 兩份報文,但是它上一次向上交付給應用層的是 4 號報文,也就是說(shuō)它在等 5 號報文,所以它暫時(shí)會(huì )將 6、7 兩個(gè)報文緩存起來(lái),等到 5 號報文來(lái)了一并交付給應用層。
現在 5 號報文由于超時(shí)被重傳了,終于到達目的地了,如愿以?xún)?,服?wù)端向上交付 5、6、7 三份報文,并返回一份確認報文,ACK = 8,表示序號 8 以前的所有報文都收到了。
當發(fā)送端收到這份確認報文后,5、6、7 變成灰色,窗口向前移動(dòng)三個(gè)單位長(cháng)度。
此外,我還想強調一個(gè)細節,TCP 是沒(méi)有否定確認的,所以如果服務(wù)端連續響應的多份報文是對同一序號的確認,那很有可能該序號以后的某個(gè)報文丟失。
例如:如果服務(wù)端發(fā)送多個(gè)對分組 5 的 ACK 確認,那說(shuō)明什么?說(shuō)明目前我服務(wù)端完整的向上交付的序號是 5 號,后續的報文我沒(méi)收到,你最好重新發(fā)一下別等待超時(shí)了。
這也是『快速重傳』的核心原理。
那么 TCP 的可靠傳輸我們也基本介紹完了,下面我們看看如果網(wǎng)絡(luò )擁塞的時(shí)候,TCP 是如何控制發(fā)送流量的呢?
TCP 認為:丟包即擁塞,需要降低發(fā)送效率,而每一次收到確認數據報即認為網(wǎng)絡(luò )通暢,會(huì )增加發(fā)送效率。
TCP 的擁塞控制算法包含三個(gè)部分,慢啟動(dòng)、擁塞避免和快速恢復。
慢啟動(dòng)的思想是,剛開(kāi)始緩慢的發(fā)送,比如某個(gè)時(shí)間段內只發(fā)送一次數據報,當收到確認報文后,下一次同樣的時(shí)間間隔內,將發(fā)送兩倍速率的兩份數據報,并以此類(lèi)推。
所以,短時(shí)間內,一個(gè) TCP 連接的發(fā)送方將以指數級增長(cháng),但一旦出現丟包,即收到冗余的 ACK 確認,或者對于一個(gè)包的確認 ACK 始終沒(méi)收到而不得不啟動(dòng)一次超時(shí)重傳,那么發(fā)送方認為「網(wǎng)絡(luò )是擁塞的」。
于是將速率直接調成一,即一個(gè)往返時(shí)間段,只發(fā)送一個(gè)分組,并且設置一個(gè)變量 ssthresh 表述一個(gè)閾值的概念,這個(gè)值是上次丟包時(shí)發(fā)送方發(fā)送速率的一半。
之后的發(fā)送方的發(fā)送效率一樣會(huì )以指數級增長(cháng),但是不同于第一次,這次一旦達到這個(gè)閾值,TCP 將進(jìn)入『擁塞避免』模式,該模式下的發(fā)送效率將不再指數級增長(cháng),會(huì )謹慎的增長(cháng)。
擁塞避免的思想是,每個(gè)往返時(shí)間段發(fā)送的所有數據報全部得到確認后,下一次就增加一個(gè)分組的發(fā)送,這樣緩慢的增長(cháng)效率是謹慎的。
那么一旦出現發(fā)送端超時(shí)丟包,注意這里是超時(shí),將發(fā)送速率置為一并重新進(jìn)入慢啟動(dòng)狀態(tài),閾值就是當前發(fā)送效率的一半。
而如果是服務(wù)端返回多個(gè)冗余 ACK 以明確你丟包,TCP 認為這不是嚴重的,對于這種情況,TCP 減半當前發(fā)送效率并進(jìn)入快速恢復階段。
快速恢復的基本思想是,收到幾個(gè)冗余的 ACK 就增加幾個(gè)分組的發(fā)送效率,就是說(shuō),你服務(wù)端不是沒(méi)收到我的幾個(gè)報文嗎,這兩次發(fā)送我提升速率迅速發(fā)給你。
當這期間出現了由發(fā)送端超時(shí)導致的丟包,同樣的處理方式,初始化發(fā)送速率為一并減半當前發(fā)送效率作為閾值,進(jìn)入慢啟動(dòng)階段。
當然,如果這期間收到了對丟失報文的確認,那么將適當降低發(fā)送效率并進(jìn)入擁塞避免狀態(tài)。
這樣,整個(gè) TCP 最核心的幾個(gè)思想都已經(jīng)介紹完了,整個(gè)運輸層基本上也算明了了。關(guān)于運輸層,你應當有了一定的理解,我再總結一下。
運輸層的任務(wù)就是從應用層的各個(gè)進(jìn)程的套接字那取回來(lái)所有需要發(fā)送的數據,然后選擇 TCP 或者 UDP 將數據封裝并推給下面的網(wǎng)絡(luò )層待發(fā)送。
未完,待續。。。
聯(lián)系客服