天天看點

TCP三向交握四次回收圖解

【建立TCP連接配接】(三次握手)

由于TCP協定提供可靠的連接配接服務,于是采用有保障的三次握手方式來建立一個TCP連接配接。三次握手的具體過程如下:

1. 用戶端發送一個帶SYN标志的TCP封包(封包1)到伺服器端,表示希望建立一個TCP連接配接。

2. 伺服器發送一個帶ACK标志和SYN标志的TCP封包(封包2)給用戶端,ACK用于對封包1的回應,SYN用于詢問用戶端是否準備好進行資料傳輸。

3. 用戶端發送一個帶ACK标志的TCP封包(封包3),作為封包2的回應。

至此,一個TCP連接配接就建立起來了。(詳見下圖)

【終止TCP連接配接】(四次揮手)

由于TCP連接配接是全雙工的,是以每個方向都必須單獨進行關閉。原則是主動關閉的一方(如已傳輸完所有資料等原因)發送一個FIN封包來表示終止這個方向的連接配接,收到一個FIN意味着這個方向不再有資料流動,但另一個方向仍能繼續發送資料,直到另一個方向也發送FIN封包。四次揮手的具體過程如下:

1. 用戶端發送一個FIN封包(封包4)給伺服器,表示我将關閉用戶端到伺服器端這個方向的連接配接。

2. 伺服器收到封包4後,發送一個ACK封包(封包5)給用戶端,序号為封包4的序号加1。

3. 伺服器發送一個FIN封包(封包6)給用戶端,表示自己也将關閉伺服器端到用戶端這個方向的連接配接。

4. 用戶端收到封包6後,發回一個ACK封包(封包7)給伺服器,序号為封包6的序号加1。

至此,一個TCP連接配接就關閉了。(4次揮手不是關閉TCP連接配接的唯一辦法,見下文Q3疑問)

【TCP連接配接狀态】

下面是每一個TCP連接配接在任意時刻可能處于的狀态,在Linux下可以在netstat指令的最後一列(State列)裡看到。

各個狀态的含義如下:

- CLOSED :初始狀态,表示TCP連接配接是“關閉着的”或“未打開的”。

- LISTEN :表示伺服器端的某個SOCKET處于監聽狀态,可以接受用戶端的連接配接。

- SYN_RCVD :表示接收到了SYN封包。在正常情況下,這個狀态是伺服器端的SOCKET在建立TCP連接配接時的三次握手會話過程中的一個中間狀态,很短暫,基本上用netstat很難看到這種狀态,除非故意寫一個監測程式,将三次TCP握手過程中最後一個ACK封包不予發送。當TCP連接配接處于此狀态時,再收到用戶端的ACK封包,它就會進入到ESTABLISHED 狀态。

- SYN_SENT :這個狀态與SYN_RCVD 狀态相呼應,當用戶端SOCKET執行connect()進行連接配接時,它首先發送SYN封包,然後随即進入到SYN_SENT 狀态,并等待服務端的發送三次握手中的第2個封包。

- SYN_SENT 狀态表示用戶端已發送SYN封包。

- ESTABLISHED :表示TCP連接配接已經成功建立。

- FIN_WAIT_1 :這個狀态得好好解釋一下,其實FIN_WAIT_1 和

- FIN_WAIT_2 兩種狀态的真正含義都是表示等待對方的FIN封包。而這兩種狀态的差別是:FIN_WAIT_1狀态實際上是當SOCKET在ESTABLISHED狀态時,它想主動關閉連接配接,向對方發送了FIN封包,此時該SOCKET進入到FIN_WAIT_1 狀态。而當對方回應ACK封包後,則進入到FIN_WAIT_2 狀态。當然在實際的正常情況下,無論對方處于任何種情況下,都應該馬上回應ACK封包,是以FIN_WAIT_1 狀态一般是比較難見到的,而FIN_WAIT_2 狀态有時仍可以用netstat看到。

- TIME_WAIT :表示收到了對方的FIN封包,并發送出了ACK封包。 TIME_WAIT狀态下的TCP連接配接會等待2*MSL(Max Segment Lifetime,最大分段生存期,指一個TCP封包在Internet上的最長生存時間。每個具體的TCP協定實作都必須選擇一個确定的MSL值,RFC 1122建議是2分鐘,但BSD傳統實作采用了30秒,Linux可以cat /proc/sys/net/ipv4/tcp_fin_timeout看到本機的這個值),然後即可回到CLOSED 可用狀态了。如果FIN_WAIT_1狀态下,收到了對方同時帶FIN标志和ACK标志的封包時,可以直接進入到TIME_WAIT狀态,而無須經過FIN_WAIT_2狀态。

- CLOSING :這種狀态在實際情況中應該很少見,屬于一種比較罕見的例外狀态。正常情況下,當一方發送FIN封包後,按理來說是應該先收到(或同時收到)對方的ACK封包,再收到對方的FIN封包。但是CLOSING 狀态表示一方發送FIN封包後,并沒有收到對方的ACK封包,反而卻也收到了對方的FIN封包。什麼情況下會出現此種情況呢?那就是當雙方幾乎在同時close()一個SOCKET的話,就出現了雙方同時發送FIN封包的情況,這是就會出現CLOSING 狀态,表示雙方都正在關閉SOCKET連接配接。

- CLOSE_WAIT :表示正在等待關閉。怎麼了解呢?當對方close()一個SOCKET後發送FIN封包給自己,你的系統毫無疑問地将會回應一個ACK封包給對方,此時TCP連接配接則進入到CLOSE_WAIT狀态。接下來呢,你需要檢查自己是否還有資料要發送給對方,如果沒有的話,那你也就可以close()這個SOCKET并發送FIN封包給對方,即關閉自己到對方這個方向的連接配接。有資料的話則看程式的政策,繼續發送或丢棄。簡單地說,當你處于CLOSE_WAIT 狀态下,需要完成的事情是等待你去關閉連接配接。

LAST_ACK :當被動關閉的一方在發送FIN封包後,等待對方的ACK封包的時候,就處于LAST_ACK 狀态。當收到對方的ACK封包後,也就可以進入到CLOSED 可用狀态了。

【TCP狀态變遷圖】

下面是收集自網上的幾張圖檔,展示TCP連接配接的各種狀态的變遷可能:

【TCP相關疑問】

最後整理幾個常見的TCP相關的疑問:

- Q1 為什麼在TCP協定裡,建立連接配接是三次握手,而關閉連接配接卻是四次握手呢?

- A1 因為當處于LISTEN 狀态的伺服器端SOCKET當收到SYN封包(用戶端希望建立一個TCP連接配接)後,它可以把ACK(應答作用)和SYN(同步作用)放在同一個封包裡來發送給用戶端。但在關閉TCP連接配接時,當收到對方的FIN封包時,對方僅僅表示對方沒有資料發送給你了,但未必你的所有資料都已經全部發送給了對方,是以你大可不必馬上關閉SOCKET(發送一個FIN封包),等你發送完剩餘的資料給對方之後,再發送FIN封包給對方來表示你同意現在關閉連接配接了,是以通常情況下,這裡的ACK封包和FIN封包都是分開發送的。

- Q2 為什麼TIME_WAIT 狀态還需要等2*MSL秒之後才能傳回到CLOSED 狀态呢?

- A2 因為雖然雙方都同意關閉連接配接了,而且握手的4個封包也都發送完畢,按理可以直接回到CLOSED 狀态(就好比從SYN_SENT 狀态到ESTABLISH 狀态那樣),但是我們必須假想網絡是不可靠的,你無法保證你最後發送的ACK封包一定會被對方收到,就是說對方處于LAST_ACK 狀态下的SOCKET可能會因為逾時未收到ACK封包,而重發FIN封包,是以這個TIME_WAIT 狀态的作用就是用來重發可能丢失的ACK封包。

- Q3 關閉TCP連接配接一定需要4次揮手嗎?

- A3 不一定,4次揮手關閉TCP連接配接是最安全的做法。但在有些時候,我們不喜歡TIME_WAIT 狀态(如當MSL數值設定過大導緻伺服器端有太多TIME_WAIT狀态的TCP連接配接,減少這些條目數可以更快地關閉連接配接,為新連接配接釋放更多資源),這時我們可以通過設定SOCKET變量的SO_LINGER标志來避免SOCKET在close()之後進入TIME_WAIT狀态,這時将通過發送RST強制終止TCP連接配接(取代正常的TCP四次握手的終止方式)。但這并不是一個很好的主意,TIME_WAIT 對于我們來說往往是有利的。

繼續閱讀