網絡通信和IO——面試再不怕問TCP了

 

網絡通信與IO系列第二文的時候,把OSI五層串了起來,每層與之對應的協議也都做了介紹,幾個協議中最有可說的、面試問的最多的就是TCP,並且Dubbo的通信方式使用的是RPC,而RPC是基於TCP的,這個會單獨寫篇RPC的文章

與TCP對應的是UDP,TCP和UDP的區別第二文總結的時候說的都很詳細了,這裏就只說TCP吧,不多介紹其他協議了

通過第二文可以瞭解到TCP是傳輸層的協議,也就是負責傳輸層的,還說了一下三次握手四次分手,這個是爲了確定客戶端和服務端的通信是通的。

網絡通信和IO——面試再不怕問TCP了

 

TCP的中文全稱叫做傳輸控制協議,顧名思義,就是要對數據的傳輸進行一定的控制,以及經常提到的一個詞就是“可靠”,可靠就可靠在對數據傳輸上的控制。

比如面試的時候問TCP爲什麼是可靠的,大部分人肯定回答三次握手四次分手,這麼答也沒錯

但其實這個嚴格來說可靠指的不是三次握手,三次握手是爲了確認通信是通的,可靠其實體現在數據傳輸上的控制,就是第二文中抓包測試的時候,提到的窗口概念中的重發機制

那麼就圍着這些傳輸中的機制來總結一篇文章,雖然三次握手四次分手已經講過了,但是這裏想補充點這方面的面試題,所以再更詳細的講一次(實際是扒了一篇大佬文章。。。)

TCP建立連接(三次握手)

最開始的時候客戶端和服務器都是處於CLOSED狀態,主動打開連接的爲客戶端,被動打開連接的是服務器。

網絡通信和IO——面試再不怕問TCP了

 

圖中流程如下

1.TCP服務器進程先創建傳輸控制塊TCB,時刻準備接受客戶進程的連接請求,此時服務器就進入了LISTEN(監聽)狀態;

2.TCP客戶進程也是先創建傳輸控制塊TCB,然後向服務器發出連接請求報文,這時報文首部中的同部位SYN=1,同時選擇一個初始序列號 seq=x ,此時,TCP客戶端進程進入了 SYN-SENT(同步已發送狀態)狀態。

TCP規定,SYN報文段(SYN=1的報文段)不能攜帶數據,但需要消耗掉一個序號。

3.TCP服務器收到請求報文後,如果同意連接,則發出確認報文。確認報文中應該 ACK=1,SYN=1,確認號是ack=x+1,同時也要爲自己初始化一個序列號 seq=y,此時,TCP服務器進程進入了SYN-RCVD(同步收到)狀態。

這個報文也不能攜帶數據,但是同樣要消耗一個序號。

4.TCP客戶進程收到確認後,還要向服務器給出確認。確認報文的ACK=1,ack=y+1,自己的序列號seq=x+1,此時,TCP連接建立,客戶端進入ESTABLISHED(已建立連接)狀態。

TCP規定,ACK報文段可以攜帶數據,但是如果不攜帶數據則不消耗序號。

5.當服務器收到客戶端的確認後也進入ESTABLISHED狀態,此後雙方就可以開始通信了。

爲什麼TCP客戶端最後還要發送一次確認呢?

一句話,主要防止已經失效的連接請求報文突然又傳送到了服務器,從而產生錯誤。

如果使用的是兩次握手建立連接,假設有這樣一種場景,客戶端發送了第一個請求連接並且沒有丟失,只是因爲在網絡結點中滯留的時間太長了,由於TCP的客戶端遲遲沒有收到確認報文,以爲服務器沒有收到,此時重新向服務器發送這條報文,此後客戶端和服務器經過兩次握手完成連接,傳輸數據,然後關閉連接。

此時此前滯留的那一次請求連接,網絡通暢了到達了服務器,這個報文本該是失效的,但是,兩次握手的機制將會讓客戶端和服務器再次建立連接,這將導致不必要的錯誤和資源的浪費。

如果採用的是三次握手,就算是那一次失效的報文傳送過來了,服務端接受到了那條失效報文並且回覆了確認報文,但是客戶端不會再次發出確認。由於服務器收不到確認,就知道客戶端並沒有請求連接。

TCP連接的釋放(四次分手)

數據傳輸完畢後,雙方都可釋放連接。最開始的時候,客戶端和服務器都是處於ESTABLISHED狀態,然後客戶端主動關閉,服務器被動關閉。

網絡通信和IO——面試再不怕問TCP了

 

1.客戶端進程發出連接釋放報文,並且停止發送數據。釋放數據報文首部,FIN=1,其序列號爲seq=u(等於前面已經傳送過來的數據的最後一個字節的序號加1),此時,客戶端進入FIN-WAIT-1(終止等待1)狀態。

TCP規定,FIN報文段即使不攜帶數據,也要消耗一個序號。

2.服務器收到連接釋放報文,發出確認報文,ACK=1,ack=u+1,並且帶上自己的序列號seq=v,此時,服務端就進入了CLOSE-WAIT(關閉等待)狀態。

TCP服務器通知高層的應用進程,客戶端向服務器的方向就釋放了,這時候處於半關閉狀態,即客戶端已經沒有數據要發送了,但是服務器若發送數據,客戶端依然要接受。

這個狀態還要持續一段時間,也就是整個CLOSE-WAIT狀態持續的時間。

3.客戶端收到服務器的確認請求後,此時,客戶端就進入FIN-WAIT-2(終止等待2)狀態,等待服務器發送連接釋放報文(在這之前還需要接受服務器發送的最後的數據)。

4.服務器將最後的數據發送完畢後,就向客戶端發送連接釋放報文,FIN=1,ack=u+1,由於在半關閉狀態,服務器很可能又發送了一些數據,假定此時的序列號爲seq=w,此時,服務器就進入了LAST-ACK(最後確認)狀態,等待客戶端的確認。

5.客戶端收到服務器的連接釋放報文後,必鬚髮出確認,ACK=1,ack=w+1,而自己的序列號是seq=u+1,此時,客戶端就進入了TIME-WAIT(時間等待)狀態。

注意此時TCP連接還沒有釋放,必須經過2∗*∗MSL(最長報文段壽命)的時間後,當客戶端撤銷相應的TCB後,才進入CLOSED狀態。

6.服務器只要收到了客戶端發出的確認,立即進入CLOSED狀態。同樣,撤銷TCB後,就結束了這次的TCP連接。可以看到,服務器結束TCP連接的時間要比客戶端早一些。

網絡通信和IO——面試再不怕問TCP了

 

爲什麼客戶端最後還要等待2MSL?

MSL(Maximum Segment Lifetime),TCP允許不同的實現可以設置不同的MSL值。

第一,保證客戶端發送的最後一個ACK報文能夠到達服務器,因爲這個ACK報文可能丟失,站在服務器的角度看來,我已經發送了FIN+ACK報文請求斷開了,客戶端還沒有給我回應,應該是我發送的請求斷開報文它沒有收到,於是服務器又會重新發送一次,而客戶端就能在這個2MSL時間段內收到這個重傳的報文,接着給出迴應報文,並且會重啓2MSL計時器。

第二,防止類似與“三次握手”中提到了的“已經失效的連接請求報文段”出現在本連接中。客戶端發送完最後一個確認報文後,在這個2MSL時間中,就可以使本連接持續的時間內所產生的所有報文段都從網絡中消失。這樣新的連接中不會出現舊連接的請求報文。

爲什麼建立連接是三次握手,關閉連接確是四次揮手呢?

建立連接的時候, 服務器在LISTEN狀態下,收到建立連接請求的SYN報文後,把ACK和SYN放在一個報文裏發送給客戶端。

而關閉連接時,服務器收到對方的FIN報文時,僅僅表示對方不再發送數據了但是還能接收數據,而自己也未必全部數據都發送給對方了,所以己方可以立即關閉,也可以發送一些數據給對方後,再發送FIN報文給對方來表示同意現在關閉連接,因此,己方ACK和FIN一般都會分開發送,從而導致多了一次。

如果已經建立了連接,但是客戶端突然出現故障了怎麼辦?

TCP還設有一個保活計時器,顯然,客戶端如果出現故障,服務器不能一直等下去,白白浪費資源。服務器每收到一次客戶端的請求後都會重新復位這個計時器,時間通常是設置爲2小時,若兩小時還沒有收到客戶端的任何數據,服務器就會發送一個探測報文段,以後每隔75秒發送一次。若一連發送10個探測報文仍然沒反應,服務器就認爲客戶端出了故障,接着就關閉連接。

如何保證可靠傳輸

前面說完了建立連接和斷開連接的流程,這裏該說傳輸中的一些機制了

用來保證傳輸可靠的機制如下

  • 應用數據被分割成TCP認爲最適合發送的數據包,TCP給發送的每一個包進行編號,接收方對數據包進行排序,把有序數據傳送給應用層。
  • 超時重傳: 當TCP發出一個段後,它啓動一個定時器,等待目標端確認收到這個報文段。如果不能及時收到一個確認,將重發這個報文段。
  • 且TCP的接收端會丟棄重複的數據
  • 校驗和:TCP將保持它首部和數據的檢驗和。這是一個端到端的檢驗和,目的是檢測數據在傳輸過程中的任何變化。如果收到段的檢驗和有差錯,TCP接收端將丟棄這個報文段和不確認收到此報文段。
  • 流量控制:TCP連接的每一方都有固定大小的緩衝空間,TCP的接收端只允許發送端發送接收端緩衝區能接納的數據。當接收方來不及處理髮送方的數據,能提示發送方降低發送的速率,防止包丟失。TCP使用的流量控制協議是可變大小的滑動窗口協議。(TCP利用滑動窗口實現流量控制)
  • 擁塞控制: 當網絡擁塞時,減少數據的發送。
  • 停止等待ARQ協議(stop and wait) 也是爲了實現可靠傳輸的,它的基本原理就是每發完一個分組就停止發送,等待對方確認。在收到確認後再發下一個分組。

超時重傳

停止等待協議中超時重傳是指只要超過一段時間仍然沒有收到確認,就重傳前面發送過的分組(認爲剛纔發送過的分組丟失了)。

因此每發送完一個分組需要設置一個超時計時器,其重轉時間應比數據在分組傳輸的平均往返時間更長一些。

這種自動重傳方式常稱爲自動重傳請求ARQ。另外在停止等待協議中若收到重複分組,就丟棄該分組,但同時還要發送確認。連續ARQ協議可提高信道利用率。

發送維持一個發送窗口,凡位於發送窗口內的分組可連續發送出去,而不需要等待對方確認。接收方一般採用累積確認,對按序到達的最後一個分組發送確認,表明到這個分組位置的所有分組都已經正確收到了。

停止等待協議

停止等待協議是爲了實現可靠傳輸的,它的基本原理就是每發完一個分組就停止發送,等待對方確認。在收到確認後再發下一個分組。

爲了提高傳輸效率,發送方可以不使用低效率的停止等待協議,而是採用流水線傳輸。流水線傳輸就是發送方可連續發送多個分組,不必每發完一個分組就停下來等待對方確認。這樣可使信道上一直有數據不間斷的在傳送。這種傳輸方式可以明顯提高信道利用率。

滑動窗口

TCP利用滑動窗口實現流量控制的機制。

發送窗口裏面的序號表示允許發送的序號。發送窗口後沿的後面部分表示已發送且已收到確認,而發送窗口前沿的前面部分表示不允許發送。

發送窗口後沿的變化情況有兩種可能,即不動(沒有收到新的確認)和前移(收到了新的確認)。發送窗口的前沿通常是不斷向前移動的。

一般來說,我們總是希望數據傳輸更快一些。但如果發送方把數據發送的過快,接收方就可能來不及接收,這就會造成數據的丟失。所謂流量控制就是讓發送方的發送速率不要太快,要讓接收方來得及接收。

流量控制

流量控制是爲了控制發送方發送速率,保證接收方來得及接收。

接收方發送的確認報文中的窗口字段可以用來控制發送方窗口大小,從而影響發送方的發送速率。將窗口字段設置爲 0,則發送方不能發送數據。

擁塞控制

在某段時間,若對網絡中某一資源的需求超過了該資源所能提供的可用部分,網絡的性能就要變壞。這種情況就叫擁塞。

擁塞控制就是爲了防止過多的數據注入到網絡中,這樣就可以使網絡中的路由器或鏈路不致過載。擁塞控制所要做的都有一個前提,就是網絡能夠承受現有的網絡負荷。

擁塞控制是一個全局性的過程,涉及到所有的主機,所有的路由器,以及與降低網絡傳輸性能有關的所有因素。

相反,流量控制往往是點對點通信量的控制,是個端到端的問題。流量控制所要做到的就是抑制發送端發送數據的速率,以便使接收端來得及接收。

爲了進行擁塞控制,TCP發送方要維持一個 擁塞窗口(cwnd) 的狀態變量。擁塞控制窗口的大小取決於網絡的擁塞程度,並且動態變化。發送方讓自己的發送窗口取爲擁塞窗口和接收方的接受窗口中較小的一個。

TCP的擁塞控制採用了四種算法,即 慢開始 、 擁塞避免 、快重傳 和 快恢復。在網絡層也可以使路由器採用適當的分組丟棄策略(如主動隊列管理AQM),以減少網絡擁塞的發生。

  • 慢開始:慢開始算法的思路是當主機開始發送數據時,如果立即把大量數據字節注入到網絡,那麼可能會引起網絡阻塞,因爲現在還不知道網絡的符合情況。經驗表明,較好的方法是先探測一下,即由小到大逐漸增大發送窗口,也就是由小到大逐漸增大擁塞窗口數值。cwnd初始值爲1,每經過一個傳播輪次,cwnd加倍。

網絡通信和IO——面試再不怕問TCP了

 

  • 擁塞避免:擁塞避免算法的思路是讓擁塞窗口cwnd緩慢增大,即每經過一個往返時間RTT就把發送放的cwnd加1.
  • 快重傳與快恢復: 在TCP/IP中,快速重傳和恢復(fast retransmit and recovery,FRR)是一種擁塞控制算法,它能快速恢復丟失的數據包。沒有FRR,如果數據包丟失了,TCP將會使用定時器來要求傳輸暫停。在暫停的這段時間內,沒有新的或複製的數據包被髮送。有了FRR,如果接收機接收到一個不按順序的數據段,它會立即給發送機發送一個重複確認。如果發送機接收到三個重複確認,它會假定確認件指出的數據段丟失了,並立即重傳這些丟失的數據段。有了FRR,就不會因爲重傳時要求的暫停被耽誤。當有單獨的數據包丟失時,快速重傳和恢復(FRR)能最有效地工作。當有多個數據信息包在某一段很短的時間內丟失時,它則不能很有效地工作。

網絡通信和IO——面試再不怕問TCP了

 

補充面試題

一道經典的面試題是從 URL 在瀏覽器被輸入到頁面展現的過程中發生了什麼,大多數回答都是說請求響應之後 DOM 怎麼被構建,被繪製出來。但是你有沒有想過,收到的 HTML 如果包含幾十個圖片標籤,這些圖片是以什麼方式、什麼順序、建立了多少連接、使用什麼協議被下載下來的呢?

要搞懂這個問題,我們需要先解決下面五個問題:

1.現代瀏覽器在與服務器建立了一個 TCP 連接後是否會在一個 HTTP 請求完成後斷開?什麼情況下會斷開?

2.一個 TCP 連接可以對應幾個 HTTP 請求?

3.一個 TCP 連接中 HTTP 請求發送可以一起發送麼(比如一起發三個請求,再三個響應一起接收)?

4.爲什麼有的時候刷新頁面不需要重新建立 SSL 連接?

5.瀏覽器對同一 Host 建立 TCP 連接到數量有沒有限制?

先來談談第一個問題:現代瀏覽器在與服務器建立了一個 TCP 連接後是否會在一個 HTTP 請求完成後斷開?什麼情況下會斷開?

在 HTTP/1.0 中,一個服務器在發送完一個 HTTP 響應後,會斷開 TCP 鏈接。但是這樣每次請求都會重新建立和斷開 TCP 連接,代價過大。所以雖然標準中沒有設定,某些服務器對 Connection: keep-alive 的 Header 進行了支持。

意思是說,完成這個 HTTP 請求之後,不要斷開 HTTP 請求使用的 TCP 連接。這樣的好處是連接可以被重新使用,之後發送 HTTP 請求的時候不需要重新建立 TCP 連接,以及如果維持連接,那麼 SSL 的開銷也可以避免,兩張圖片是我短時間內兩次訪問 https://www.github.com 的時間統計:

網絡通信和IO——面試再不怕問TCP了

 

頭一次訪問,有初始化連接和 SSL 開銷

網絡通信和IO——面試再不怕問TCP了

 

初始化連接和 SSL 開銷消失了,說明使用的是同一個 TCP 連接

持久連接:既然維持 TCP 連接好處這麼多,HTTP/1.1 就把 Connection 頭寫進標準,並且默認開啓持久連接,除非請求中寫明 Connection: close,那麼瀏覽器和服務器之間是會維持一段時間的 TCP 連接,不會一個請求結束就斷掉。

所以第一個問題的答案是:默認情況下建立 TCP 連接不會斷開,只有在請求報頭中聲明 Connection: close 纔會在請求完成後關閉連接。(詳細文檔見下面的鏈接)

Hypertext Transfer Protocol -- HTTP/1.1tools.ietf.org

第二個問題:一個 TCP 連接可以對應幾個 HTTP 請求?

瞭解了第一個問題之後,其實這個問題已經有了答案,如果維持連接,一個 TCP 連接是可以發送多個 HTTP 請求的。

第三個問題:一個 TCP 連接中 HTTP 請求發送可以一起發送麼(比如一起發三個請求,再三個響應一起接收)?

HTTP/1.1 存在一個問題,單個 TCP 連接在同一時刻只能處理一個請求,意思是說:兩個請求的生命週期不能重疊,任意兩個 HTTP 請求從開始到結束的時間在同一個 TCP 連接裏不能重疊。

雖然 HTTP/1.1 規範中規定了 Pipelining 來試圖解決這個問題,但是這個功能在瀏覽器中默認是關閉的。

先來看一下 Pipelining 是什麼,RFC 2616 中規定了:

A client that supports persistent connections MAY "pipeline" its requests (i.e., send multiple requests without waiting for each response). A server MUST send its responses to those requests in the same order that the requests were received. 一個支持持久連接的客戶端可以在一個連接中發送多個請求(不需要等待任意請求的響應)。收到請求的服務器必須按照請求收到的順序發送響應。

至於標準爲什麼這麼設定,我們可以大概推測一個原因:由於 HTTP/1.1 是個文本協議,同時返回的內容也並不能區分對應於哪個發送的請求,所以順序必須維持一致。

比如你向服務器發送了兩個請求 GET /query?q=A 和 GET /query?q=B,服務器返回了兩個結果,瀏覽器是沒有辦法根據響應結果來判斷響應對應於哪一個請求的。

Pipelining 這種設想看起來比較美好,但是在實踐中會出現許多問題:

  • 一些代理服務器不能正確的處理 HTTP Pipelining。
  • 正確的流水線實現是複雜的。詳見:HTTP/1.x 的連接管理developer.mozilla.org
  • Head-of-line Blocking 連接頭阻塞:在建立起一個 TCP 連接之後,假設客戶端在這個連接連續向服務器發送了幾個請求。按照標準,服務器應該按照收到請求的順序返回結果,假設服務器在處理首個請求時花費了大量時間,那麼後面所有的請求都需要等着首個請求結束才能響應。

所以現代瀏覽器默認是不開啓 HTTP Pipelining 的。

但是,HTTP2 提供了 Multiplexing 多路傳輸特性,可以在一個 TCP 連接中同時完成多個 HTTP 請求。至於 Multiplexing 具體怎麼實現的就是另一個問題了。我們可以看一下使用 HTTP2 的效果。

網絡通信和IO——面試再不怕問TCP了

 

綠色是發起請求到請求返回的等待時間,藍色是響應的下載時間,可以看到都是在同一個 Connection,並行完成的

所以這個問題也有了答案:在 HTTP/1.1 存在 Pipelining 技術可以完成這個多個請求同時發送,但是由於瀏覽器默認關閉,所以可以認爲這是不可行的。在 HTTP2 中由於 Multiplexing 特點的存在,多個 HTTP 請求可以在同一個 TCP 連接中並行進行。

那麼在 HTTP/1.1 時代,瀏覽器是如何提高頁面加載效率的呢?主要有下面兩點:

1.維持和服務器已經建立的 TCP 連接,在同一連接上順序處理多個請求。

2.和服務器建立多個 TCP 連接。

第四個問題:爲什麼有的時候刷新頁面不需要重新建立 SSL 連接?

在第一個問題的討論中已經有答案了,TCP 連接有的時候會被瀏覽器和服務端維持一段時間。TCP 不需要重新建立,SSL 自然也會用之前的。

第五個問題:瀏覽器對同一 Host 建立 TCP 連接到數量有沒有限制?

假設我們還處在 HTTP/1.1 時代,那個時候沒有多路傳輸,當瀏覽器拿到一個有幾十張圖片的網頁該怎麼辦呢?

肯定不能只開一個 TCP 連接順序下載,那樣用戶肯定等的很難受,但是如果每個圖片都開一個 TCP 連接發 HTTP 請求,那電腦或者服務器都可能受不了,要是有 1000 張圖片的話總不能開 1000 個TCP 連接吧,你的電腦同意 NAT 也不一定會同意。

所以答案是:有。Chrome 最多允許對同一個 Host 建立六個 TCP 連接。不同的瀏覽器有一些區別。

https://developers.google.com/web/tools/chrome-devtools/network/issues#queued-or-stalled-requestsdevelopers.google.com

那麼回到最開始的問題,收到的 HTML 如果包含幾十個圖片標籤,這些圖片是以什麼方式、什麼順序、建立了多少連接、使用什麼協議被下載下來的呢?

如果圖片都是 HTTPS 連接並且在同一個域名下,那麼瀏覽器在 SSL 握手之後會和服務器商量能不能用 HTTP2,如果能的話就使用 Multiplexing 功能在這個連接上進行多路傳輸。不過也未必會所有掛在這個域名的資源都會使用一個 TCP 連接去獲取,但是可以確定的是 Multiplexing 很可能會被用到。

如果發現用不了 HTTP2 呢?或者用不了 HTTPS(現實中的 HTTP2 都是在 HTTPS 上實現的,所以也就是隻能使用 HTTP/1.1)。

那瀏覽器就會在一個 HOST 上建立多個 TCP 連接,連接數量的最大限制取決於瀏覽器設置,這些連接會在空閒的時候被瀏覽器用來發送新的請求,如果所有的連接都正在發送請求呢?那其他的請求就只能等等了。

 

 

 

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