設計師圖解TCP連線過程

寒東設計師發表於2018-02-11

前言

      我們知道Ip層包裹著tcp報文段把它從源Ip運送到目的Ip,如果過程中出現差錯(16位的Ip檢驗和錯誤),Ip協議會直接丟棄該資料包並且不生成差錯報文。這種情況tcp會發現資料丟失並進行重傳。
      這篇文章想探討一下TCP協議是通過什麼方式做到這些的,曾經做過設計師的我忍不住抄起老本行畫兩張圖。

IP、UDP、TCP差異

設計師圖解TCP連線過程
      IP是網路層協議,TCP和UDP都是運輸層協議,他們都是基於IP協議傳遞資料。接下來我們通過幾個重要首部分析一下它們的異同。
      三個協議首部中都有16位檢驗和,但是,IP協議首部中的檢驗和只覆蓋IP首部,而TCP和UDP首部中的檢驗和是包含所有資料的,也就是說IP協議不關心資料是否正確,而TCP和UDP關心。
      IP首部中最重要的是32位源IP和目的IP地址,其它首部都是配合它順利的把資料從源IP傳到目的IP,TCP和UDP負責標記對應埠,那麼TCP和UDP的區別有是什麼呢?
      我們先看UDP,它只有4個首部,前兩個確定埠,後兩個保證資料的準確。IP協議把資料傳遞到目的IP地址的機器上,UDP通過首部中的長度和檢驗和校驗資料,如果校驗沒有通過,那麼UDP協議會直接丟棄資料,而不會向源IP和埠傳送訊息,所以說UDP協議是面向資料包的。
      UDP的缺點就是:我們不能保證對方一定收到了我傳送的資料。但是從首部中很容易能看出它的優點:就是輕量、速度快。
      再看TCP,它除了關心資料之外明顯還關心更多的東西,其中最重要的就是關心連線的可靠性。下面我們將主要通過TCP連線和斷開過程看看這些首部都是什麼作用。
      TCP首部中有6個標誌位元位,它們預設都是0,需要時設定為1,通過它們完成詢問和應答。它們分別表示的含義如下:

URG: 緊急指標
ACK: 確認序號有效
PSH: 儘可能快地將資料送往接收程式
RST: 重建連線
SYN: 同步序號用來發起一個連線
FIN: 發端完成傳送任務

連線過程和狀態

      我畫了一張圖來描述TCP連線的過程和狀態變化:

設計師圖解TCP連線過程

一般情況(圖中紫色和綠色部分)

三次握手
第一條紫色箭頭(從上到下):

      伺服器端預設處於 LISTEN 狀態監聽著某個埠。當客戶端想要連線這個埠的時候,客戶端首先會隨機生成一個初始序號(圖中的j),首部中的32位序號(以下簡稱序號)就變成j+1,同時首部中的SYN由0置為1。客戶端傳送完帶有這些首部的TCP段後狀態變為 SYN_SENT

第一條綠色箭頭:

    當伺服器收到客戶端第一個帶有SYN的TCP段的時候,客戶端由 LISTEN 狀態變為 SYN_RCVD 狀態,併傳送一段資料,其中首部ACK置為1,32位確認號(以下簡稱確認號)為客戶端發過來的序號(j)+1,含義就是客戶端的資料已經收到。同時SYN也會置為1,序號為k。含義是我也要與你建立連線。

第二條紫色箭頭:

      當客戶端收到伺服器的ACK後,狀態變更為 ESTABLISLISHED ,同時傳送資料,首部ACK為k+1,含義是伺服器的資料已經收到。這時客戶端已經建立連線,而伺服器端是否能建立連線取決於它能否收到當前客戶端傳送的這段帶有ACK的資料。如果伺服器收到的話,它的狀態也會變成 ESTABLISLISHED
      到此,連線建立,可以繼續交換資料。

四次揮手
第三條紫色箭頭:

      在不考慮斷電等特殊情況下,主動關閉的一方(圖中為客戶端但不總是)傳送FIN+序號m,狀態變更為 FIN_WAIT_1

第二條綠色箭頭:

      被動關閉的一方(圖中為伺服器但不總是)收到後狀態變更為 CLOSE_WAIT 。同時裡立即傳送確認號m+1,頭部ACK置為1,這時服務端狀態變更為 LAST_ACK ,很容易理解,這是最後一次確認,客戶端收到ACK後,狀態變更為 FIN_WAIT_2

第三條綠色箭頭:

      伺服器 LAST_ACK 後還可能繼續做其它的操作,做完之後,伺服器也會傳送FIN+序號n。

第四條紫色箭頭:

      客戶端收到FIN後,狀態變更為 TIME_WAIT ,同時傳送確認資訊ACK n+1,伺服器收到ACK後,狀態變更為 CLOSE
      到此,連線斷開。

為什麼揮手是4次而不是3次?

      揮手的時候有個問題,為什麼ACK和FIN不能一起傳送呢?這是由TCP的半關閉(half-close)造成的。TCP連線是全雙工(即資料在兩個方向上能同時傳遞),因此每個方向必須單獨地進行關閉。
      收到一個FIN只意味著對方不會再向我傳送資料了,但是TCP仍然支援我繼續向對方傳送資料(當然,在實際的應用中,直有很少的程式這樣做),如果我們用Wireshark等工具抓包時也經常會看到揮手只有3次的情況。

TIME_WAIT旁邊的2MSL是什麼?

       TIME_WAIT 狀態也稱為2倍MSL等待狀態。MSL是TCP的最大報文生存時間。當一個TCP主動關閉併傳送最後一個ACK(圖中右下角最後一條紫色線)後,必須在 TIME_WAIT 狀態等待兩倍的MSL時間,防止對方沒有收到這個ACK然後重發FIN。所以一去一回最多就是兩倍時間。
      作業系統預設240s(也就是2倍的兩分鐘)後會關閉處於 TIME_WAIT 的連線,在這之前埠一直會被佔用。
      在Linux伺服器上可以通過變更/etc/sysctl.conf檔案去修改該預設值(秒):net.ipv4.tcp_fin_timeout=30。但這通常也會出現一些問題。

同時開啟(圖中紫色和橙色部分)

      兩個應用程式彼此同時開啟的可能性很小。這時雙方都是客戶端也都是伺服器。具體流程和一般情況一樣,只不過揮手的時候是4次而不是3次。TCP協議認為這種情況是建立一條連線而不是兩條。

同時關閉(圖中紫色和橙色部分)

      為了方便我把同時開啟和同時關閉畫在了一起,其實同時開啟不一定同時關閉。不同時開啟也有可能同時關閉。

TCP伺服器處理埠號

      我們先呼叫natstat命令看一下telnet伺服器。-a:顯示所有;-n:以點分十進位制表示IP;-f inet:只顯示TCP和UDP。首先,沒有連線的時候它是這樣的:

設計師圖解TCP連線過程
      表頭依次為協議、接收請求數、傳送請求數、本地地址、遠端地址和狀態。圖中資料顯示:當前本機的23埠正在處於LISTEN狀態,監聽著任意IP發來的請求。下面再看一下當請求過來時的狀態:
設計師圖解TCP連線過程
      狀態一共有3條,這三條狀態對應著三個程式,LISTEN狀態的程式一直存在並接受SYN報文段,一旦握手成功,系統核心中的TCP模組就建立一個處於ESTABLISHED狀態的程式。

呼入連線請求佇列

      如果伺服器在同一時間收到大量請求,而伺服器當前沒有能力處理(或有更高優先順序的程式)。TCP會先將這個請求快取在一個固定長度的佇列中(佇列長度一般為5,稱為積壓值)。
      應用層會不斷消費該佇列,一旦產生積壓,伺服器會停止接收SYN報文,並且不返回任何訊息,這時客戶端會顯示超時。

延時確認時間

      通常TCP在接收到資料時並不立即傳送ACK;而是推遲傳送,以便將ACK與需要沿該方向傳送的資料一起傳送(這種現象稱為資料捎帶ACK)。絕大多數實現採用的時延為200ms。
      200ms是最大時間,如果一直有資料等待傳送,那麼就不存在延時確認時間。

Nagle演算法

      Nagle演算法是為了解決小段問題。所謂“小段”,指的是小於最大MSS尺寸的資料塊。例如應用程式一次產生一位元組資料(例如互動式輸入),這樣會導致網路由於太多的包而過載。
      Nagle演算法的基本定義是任意時刻,最多隻能有一個未被確認的小段。規則如下:

1、如果包長度達到MSS,則允許傳送;
2、如果該包含有FIN,則允許傳送;
3、設定了TCP_NODELAY選項,則允許傳送;
4、未設定TCP_CORK選項時,若所有發出去的小包均被確認,則允許傳送;
5、上述條件都未滿足,但發生了超時(一般為200ms),則立即傳送。

      Nagle演算法的優點是自適應:確認到達的越快,傳送的就越快。
      Nagle演算法的缺點是傳送存在延時。我們可以通過TCP_NODELAY套接字選項來關閉Nagle演算法。

總結

      對於前端工程師來講,瞭解TCP協議能夠幫助我們更好的理解並使用HTTP協議。也能夠幫助我們對網路進行更加細緻的優化。我的理解也很有限,希望能夠和大家共同討論。

參考資料

  • 《Tcp/ip詳解-卷一》
  • 《圖解Tcp/ip》

相關文章