完整的一次HTTP請求響應過程

轉:https://www.cnblogs.com/yangming1996/p/9120911.html

因特網無疑是人類有史以來最偉大的設計,它互聯了全球數億臺計算機、通訊設備,即便位於地球兩端的用戶也可在頃刻間完成通訊。

可以說『協議』是支撐這麼一個龐大而複雜的系統有條不紊運作的核心,而所謂『協議』就是通訊雙方所必須遵守的規則,在這種規則下,不同的數據報可能被解析爲不同的響應動作。

簡而言之,『協議』就是指如果發送和接收方按照這個規則進行數據報文的發送,即可在基本的數據傳輸之上得到某些特殊的功能或服務,否則你的數據別人是不認識的。例如:遵循 TCP 協議的兩端,可以在不可靠的網絡傳輸中得到可靠的數據傳輸能力。

整個計算機網絡是分層的,有七層模型,也有五層模型,個人覺得五層模型更利於理解。我們從上至下的介紹這五個層,它們分別是,應用層,運輸層,網絡層,數據鏈路層和物理層

應用層

『應用層』算是距離用戶最近的一層了,主機上的一個個的進程就構成了『應用層』。比如你在你的瀏覽器地址欄輸入了 「www.baidu.com」,你的瀏覽器在應用層會做哪些事情呢?

首先瀏覽器會使用 DNS 協議返回域名「www.baidu.com」所對應的 IP 地址,關於 DNS 我們待會詳細介紹。

接着,應用層決定創建一個『TCP 套接字』,然後將這個請求動作封裝成一個 Http 數據報並推入套接字中。

套接字分爲兩種類型,『TCP 套接字』和『UDP 套接字』,應用層同時可能會有幾十個數據報的發出,而運輸層也會收到所有的響應報文,那麼它該如何區分這些報文到底是誰的響應報文呢?

而套接字就是用於區分各個應用層應用的,往往由端口號和 IP 地址進行標識,運輸層只要查看響應報文的源端口號和 IP 地址就能夠知道該將報文推送給哪個套接字了。

當一個應用層數據報被推動進套接字之後,應用層的所有工作也算是全部完成了,關於後續報文的去向,它已經不用管了。

這裏還要說明一點的是,『TCP 套接字』和『UDP 套接字』兩者本質上的區別在於,前者保證數據報可靠地到達目的地,但是必然耗時,而後者不保證數據報一定能到達目的地,但是速度快,這也是應用層協議在選擇運輸層協議的時候需要考慮的一點。

關於 TCP 和 UDP,我們後續還會繼續說,下面我們看看域名解析協議 DNS 是如何運作的,它是如何將一個域名解析返回它的 IP 地址的。

DNS 原理

首先明確一點的是,DNS 是一個應用層協議,並且它選擇的運輸層協議是 UDP,所以你的域名解析過程一般會很快,但也會經常出現解析失敗的情況,然而刷新一下又好了。

image

在 DNS 服務器上,域名和它所對應的 IP 地址存儲爲一條記錄,而所有的記錄都不可能只存儲在一臺服務器上,我相信無論多麼強大的服務器都扛不住全球上億次的併發量吧。

大致來說,有三種類型的 DNS 服務器,根 DNS 服務器,頂級域 DNS 服務器和權威 DNS 服務器。

其中,頂級域 DNS 服務器主要負責諸如 com、org、net、edu、gov 等頂級域名。

根 DNS 服務器存儲了所有頂級域 DNS 服務器的 IP 地址,也就是說你可以通過根服務器找到頂級域服務器。例如:「www.baidu.com」,根服務器會返回所有維護 com 這個頂級域服務器的 IP 地址。

然後你任意選擇其中一個頂級域服務器,請求該頂級域服務器,該頂級域服務器拿到域名後應當能夠做出判斷並給出負責當前域的權威服務器地址,以百度爲例的話,頂級域服務器將返回所有負責 baidu 這個域的權威服務器地址。

於是你可以任意選擇其中一個權威服務器地址,向它繼續查詢 「www.baidu.com」 的具體 IP 地址,最終權威服務器會返回給你具體的 IP 地址。

至此,我們簡單描述了一個域名解析的大致過程,還有一些細節之處並未提及,我們等會會通過一個實例來完整的看一下,下面描述一個非常重要的概念。

整個 DNS 解析過程中,有一個非常核心的人物我們一直沒介紹它,它就像主機的『助理』一樣,幫助主機查詢域名的 IP 地址。它叫做『本地 DNS 服務器』。

image

大家每次通過 DHCP 動態獲取 IP 地址的時候,這一點後文會說。其實路由器不僅給你返回了 IP 地址,還會告訴你一個 DNS 服務器地址,這個就是你的本地 DNS 服務器地址,也就是說,你的所有域名解析請求只要告訴它就行了,它會幫你查並返回結果給你的。

除此之外,本地 DNS 服務器往往是具有緩存功能的,通常兩天內的記錄都會被緩存,所以大部分時候你是感覺不到域名解析過程的,因爲往往就是從緩存裏拿的,非常快。

下面我們看一個簡單的案例:

網上找的一個圖,自己畫實在太費時間了,但足以說明問題,現在假設請求 「www.xx.com」 。

image

  • ①:主機向負責自己的本地 DNS 發送查詢報文,如果本地服務器緩存中有,將直接返回結果
  • ②:本地服務器發現緩存中沒有,於是從內置在內部的根服務器列表中選一個發送查詢報文
  • ③:根服務器解析一下後綴名,告訴本地服務器負責 .com 的所有頂級服務器列表
  • ④:本地服務器選擇一個頂級域服務器繼續查詢,.com 域服務器拿到域名後繼續解析,返回負責 .xx 域的所有權威服務器列表
  • ⑥:本地服務器從返回的權威服務器之一再次發送查詢報文,最終會從某一個權威服務器上得到具體的 IP 地址
  • ⑧:向主機返回結果

其實整個 DNS 報文的發送與響應過程都是要走我們的五層協議的,只是這裏重點在於理解 DNS 協議本身,所以並未提及其他層的具體細節,這裏的強調是提醒你 DNS 只是一個應用層協議。

運輸層

運輸層的任務就是將應用層推出套接字的所有數據報收集起來,並且按照應用層指定的運輸層協議,TCP 或 UDP,重新封裝應用層數據報,並推給網絡層等待發送。

TCP 和 UDP 是運輸層的兩個協議,前者是基於連接的可靠傳輸協議,後者是無連接的不可靠傳輸協議,所以前者更適合於一些對數據完整性要求高的場合,後者則適合於那種可以允許數據丟失但對傳輸速率要求特別高的場景,例如:語音電話,視頻等,丟一兩個包最多卡頓一下,無傷大雅。

UDP

UDP 不同於 TCP 那樣複雜,它既不保證數據可靠的傳輸到目的地,也不保證數據按序到達目的地,僅僅提供了簡單的差錯檢驗。報文格式如下:

image

其中,數據就是應用層推出來的數據,源端口號用於響應報文的交付,目的端口號用於向目的進程交付數據,校驗和用於檢查傳輸過程中數據是否受損,如果受損,UDP 將直接丟棄該報文。

TCP

TCP 要稍微複雜些,它是面向連接的,並且基於連接提供了可靠的數據傳輸服務,它的數據報文格式如下:

image

單純的解釋報文格式中各個字段的含義並沒有太過實際的意義,你也很難理解了,在我們介紹 TCP 是如何『三次握手』,『四次揮手』以及『丟包重傳』等動作時,不間斷的會說明這些動作時如何使用報文中的相關字段的。

首先我們來看耳熟能詳的『三次握手』,這基本上是 TCP 的代名詞了,無論懂不懂具體原理的人,提到 TCP,基本上都是知道『三次握手』的。

而本身,TCP 的三次握手就是爲了確保通訊雙方能夠穩定的建立連接並完成數據報文的請求與響應動作,至於爲什麼是三次握手而不是四次五次,這是一個哲學問題,這裏就不做討論了。

第一步:

客戶端向服務端發送一份特殊的 TCP 報文,該報文並不包含應用層的數據,是一份特殊的報文,它的 TCP 首部中 SYN 字段值爲 1 (參見上述報文格式)。

除此之外,客戶端還會隨機生成一個初始序號,填在報文的「序號」字段,代表當前報文的序號是這個,並且我後續的分組會基於這個序號遞增。

然後該報文將會經網絡層、鏈路層、物理層發送到服務端。

第二步:

如果分組丟失了,那麼客戶端會經過某個時間間隔再次嘗試發送。

而如果分組準確的到達服務端了,服務端拆開 TCP 首部會看到,這是一個特殊的 SYN 握手報文,於是爲此次連接分配緩存等資源。

接着服務端開始構建響應報文,SYN 是一個用於同步需要的字段,響應報文中依然會被置爲 1,並且服務端也將隨機生成一個初始序號放置的響應報文的序號字段中。

最後,服務端還會爲響應報文中的確認字段賦值,這個值就是客戶端發過來的那個序號值加一。

整體上的意思就是說,「我同意你的連接請求,我的初始序號爲 xxx,你的初始序號我收到了,我等着你的下一個分組到來」

第三步:

客戶端收到服務端的響應報文,於是分配客戶端 TCP 連接所必須的緩存等資源,於是連接已經建立。

實際上從第三步開始,客戶端就可以攜帶應用層數據向服務端交換報文了,以後的每份報文中,SYN 都爲 0,因爲它只是用於同步初始序號的,這一點需要明確。

總的來說,整個『握手』過程大致如下圖所示:

image

下面我們看看拆除一條 TCP 連接的『四次揮手』是怎樣的過程。

因爲一條 TCP 連接會消耗大量的主機資源,不僅僅服務端需要分配各種緩存資源,客戶端也同樣需要分配相應資源。因爲 TCP 是『全雙工通信』,服務端和客戶端兩方其實是一樣的,誰是客戶誰是服務器是相對的。

強調這一點是爲了說明,一條 TCP 連接不是隻有客戶端才能斷開,服務端也同樣可以主動斷開連接,這一點需要清楚。

我們這裏假設客戶端主動發起斷開連接的請求爲例:

第一步:

客戶端構建一份特殊的 TCP 報文,該報文首部字段 FIN 被置爲 1,然後發送該報文。

第二步:

服務端收到該特殊的 FIN 報文,於是響應客戶端一個 ACK 報文,告訴客戶端,請求關閉的報文已經收到,我正在處理。

第三步:

服務端發送一個 FIN 報文,告訴客戶端,我將要關閉連接了。

第四步:

客戶端返回一個 ACK 響應報文,告訴服務端,我收到你剛纔發的報文了,我已經確認,你可以關閉連接了。

當服務端收到客戶端發送的 ACK 響應報文時,將釋放服務端用於該 TCP 連接的所有資源,與此同時,客戶端也會定時等待一定時間後完全釋放自己用於該連接的相關資源。

用一張圖更直觀的描述一下:

image

結合着圖與相關序號信息,我們再詳細說說其中的一些細節。

首先,客戶端發送一個特殊分組,該分組的序號爲 u。發送完成之後,客戶端進入 FIN-WAIT-1 這個狀態,這個狀態下,該 TCP 連接的客戶端不再能發送數據報,但是是可以接受數據報的,它等待着服務端的響應報文。

接着,服務端收到客戶端發送的終止連接報文請求,服務端構建響應報文,告訴客戶端「序號 u+1 以前的分組我都收到了」,並且進入 CLOSE-WAIT 狀態,這個狀態持續時間很短。

服務端會緊接着發送它的 FIN 數據報,通知客戶端我服務端即將關閉連接,並隨即進入 LAST_ACK 狀態等待客戶端響應報文。

一旦客戶端收到這個 FIN 報文,將返回確認報文並進入 TIME-WAIT 狀態,等待 2MSL 時間間隔後完全釋放客戶端 TCP 連接所佔用資源。

與此同時,當服務端收到客戶端最後的確認報文,就將直接斷開服務端連接並釋放相關資源。

至於爲什麼最後客戶端需要等 2MSL 時間長度再完全釋放 TCP 相關資源呢?

那是因爲 2MSL 是一份報文存在於網絡中最長的時間,超過該時間到達的報文都將被丟棄,而如果客戶端最後的確認報文於網絡中丟失的話,服務端必將發起超時請求,重新發送第三次揮手動作,此時等待中的客戶端就可隨即重新發送一份確認請求。

這是爲什麼客戶端等待一個最長報文傳輸時間的原因。有人可能好奇爲什麼前面的各次請求都沒有做超時等待而只最後一次數據發送做了超時等待?

其實原因很簡單,相信你也能想到,就是 TCP 自帶計時能力,超過一定時間沒有收到某個報文的確認報文,會自動重新發送,而這裏如果不做等待而直接關閉連接,那麼我如何知道服務端到底收到沒我的確認報文呢。

通過等待一個最長週期,如果這個週期內沒有收到服務端的報文請求,那麼我們的確認報文必然是到達了服務端了的,否則重複發送一次即可。

至此,TCP 的『三次握手』和『四次揮手』我們已經簡單描述完成了,下面我們看看 TCP 的一些其他特性,比如:可靠傳輸,擁塞控制等

首先我們來看 TCP 是如何實現可靠傳輸的,即如何解決網絡傳輸中丟包的問題。

TCP 使用『回退 N 步』協議實現的可靠傳輸,準確來說,TCP 是在它的基礎上進行了一部分優化。

image

『回退 N 步』協議也被稱作『滑動窗口』協議,即最多允許發送方有 N 個「已發送但未被確認」的數據報文,如圖所示,p1 到 p3 長度即爲 N,這裏的窗口指的就是 p1 到 p3 這個區間。

只有當發送端收到 p1 的確認報文後,整個窗口才能向前滑動,而實際上在沒有收到 p1 的確認報文前,即便它後面的報文已經被接收,服務端也僅僅會緩存這些『非預期的報文』

直到服務端收到最小預期的那個報文後,從緩存中取出已經到達的後續報文,合併並向上交付,然後向發送端返回一個確認報文。

當發送端窗口從左往右已經連續多個報文被確認後,整個窗口將向前滑動多個單位長度。

下面我們看一個例子:

image

這是一個發送方的窗口,灰色表示已經被確認的報文,黃色表示已發送但未被確認的報文,綠色表示下一個待發送的報文,白色表示不可用的報文。

這是我們假設服務端已經收到 6、7 兩份報文,但是它上一次向上交付給應用層的是 4 號報文,也就是說它在等 5 號報文,所以它暫時會將 6、7 兩個報文緩存起來,等到 5 號報文來了一併交付給應用層。

現在 5 號報文由於超時被重傳了,終於到達目的地了,如願以償,服務端向上交付 5、6、7 三份報文,並返回一份確認報文,ACK = 8,表示序號 8 以前的所有報文都收到了

當發送端收到這份確認報文後,5、6、7 變成灰色,窗口向前移動三個單位長度。

此外,我還想強調一個細節,TCP 是沒有否定確認的,所以如果服務端連續響應的多份報文是對同一序號的確認,那很有可能該序號以後的某個報文丟失。

例如:如果服務端發送多個對分組 5 的 ACK 確認,那說明什麼?說明目前我服務端完整的向上交付的序號是 5 號,後續的報文我沒收到,你最好重新發一下別等待超時了。

這也是『快速重傳』的核心原理。

那麼 TCP 的可靠傳輸我們也基本介紹完了,下面我們看看如果網絡擁塞的時候,TCP 是如何控制發送流量的呢?

TCP 認爲:丟包即擁塞,需要降低發送效率,而每一次收到確認數據報即認爲網絡通暢,會增加發送效率。

TCP 的擁塞控制算法包含三個部分,慢啓動、擁塞避免和快速恢復

慢啓動的思想是,剛開始緩慢的發送,比如某個時間段內只發送一次數據報,當收到確認報文後,下一次同樣的時間間隔內,將發送兩倍速率的兩份數據報,並以此類推。

所以,短時間內,一個 TCP 連接的發送方將以指數級增長,但一旦出現丟包,即收到冗餘的 ACK 確認,或者對於一個包的確認 ACK 始終沒收到而不得不啓動一次超時重傳,那麼發送方認爲「網絡是擁塞的」。

於是將速率直接調成一,即一個往返時間段,只發送一個分組,並且設置一個變量 ssthresh 表述一個閾值的概念,這個值是上次丟包時發送方發送速率的一半。

之後的發送方的發送效率一樣會以指數級增長,但是不同於第一次,這次一旦達到這個閾值,TCP 將進入『擁塞避免』模式,該模式下的發送效率將不再指數級增長,會謹慎的增長。

擁塞避免的思想是,每個往返時間段發送的所有數據報全部得到確認後,下一次就增加一個分組的發送,這樣緩慢的增長效率是謹慎的。

那麼一旦出現發送端超時丟包,注意這裏是超時,將發送速率置爲一併重新進入慢啓動狀態,閾值就是當前發送效率的一半。

而如果是服務端返回多個冗餘 ACK 以明確你丟包,TCP 認爲這不是嚴重的,對於這種情況,TCP 減半當前發送效率並進入快速恢復階段。

快速恢復的基本思想是,收到幾個冗餘的 ACK 就增加幾個分組的發送效率,就是說,你服務端不是沒收到我的幾個報文嗎,這兩次發送我提升速率迅速發給你。

當這期間出現了由發送端超時導致的丟包,同樣的處理方式,初始化發送速率爲一併減半當前發送效率作爲閾值,進入慢啓動階段。

當然,如果這期間收到了對丟失報文的確認,那麼將適當降低發送效率並進入擁塞避免狀態。

這樣,整個 TCP 最核心的幾個思想都已經介紹完了,整個運輸層基本上也算明瞭了。關於運輸層,你應當有了一定的理解,我再總結一下。

運輸層的任務就是從應用層的各個進程的套接字那取回來所有需要發送的數據,然後選擇 TCP 或者 UDP 將數據封裝並推給下面的網絡層待發送。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章