TCP三次握手與四次揮手

烏有先生ii發表於2022-03-05

三次握手?

什麼是三次握手?

一般情況下,連線是由客戶端向服務端發起的。

第一次,客戶端傳送一個TCP資料包並將SYN同步位置為1,表示要建立連線,此時客戶端會從CLOSED狀態變為SYN_SEND狀態;

第二次是服務端向客戶端傳送ACK,並且也將SYN置為1,一是表示自己收到了客戶端建立連線的請求,並且從LISTEN狀態變為SYN_RCVD,二是表示自己要建立連線;

當客戶端收到服務端的ACK + SYN後,便會處於ESTABLISHED狀態,並且傳送第三次握手,表示自己收到了客戶端的SYN。當服務端收到客戶端的ACK後,便會從SYN_RCVD狀態變為ESTABLISHED狀態。

此時,雙方的連線才算真正的建立。

為什麼需要三次握手,而不是兩次握手或者四次握手?

1.雙方之所以進行多次握手,本質上還是因為通道是不可靠的。

由於TCP本身的要求是可靠連線,因此,客戶端和服務端都必須確認,對方的收發能力都是正常的。因此,當第二次握手結束,客戶端就可以確保服務端的收發能力正常,建立起連線,但是,服務端只能確保客戶端的傳送能力正常,因此必須等收到第三次握手的ACK後才會建立連線。

理論上四次五次都可以,但是三次就可以保證,所以是三次握手。

2.最好是奇數次握手

在socket程式設計時,服務端一般是先listen再accept。也就是說,發起連線的都是客戶端。

誰傳送最後的ACK,誰就會先建立連線。如果是偶數次,那麼最後一次傳送ACK的就是服務端。一般情況下,伺服器比客戶端的數量是1:n,眾所周知,維護連線是需要付出時間和空間成本的,比如需要在核心建立相關結構體,如果伺服器建立連線而客戶端未建立,那麼服務端可能會有多個空連線,這其實是不利於伺服器的。

奇數次的握手是一種成本轉移,即便出現空連線的現象,成本也會分攤在多個客戶端身上,而不至於給服務端太大壓力。

四次揮手

什麼是四次揮手?

四次揮手與三次握手對應,是斷開連線的過程。

由於雙方之前進行了大量資料的互動,因此通過傳送與確認機制就可以保證雙方連線的斷開。

首先斷開的一方會傳送FIN,便會處於FIN_WAIT_1狀態,只要收到ACK後,便會處於FIN_WAIT_2狀態。表示自己關閉了傳送緩衝區,不再傳送資料,但依然具有接受資料的能力。

而另一方收到FIN後,便會處於CLOSE_WAIT狀態,即半關閉狀態。它還可以繼續傳送資料,當資料傳送完畢後,又會傳送FIN,當收到ACK後便關閉自己的傳送緩衝區。

這就是四次揮手。

為什麼是四次?

我們在寫客戶端或者服務端時,都有一個close(fd),即關閉檔案描述符的操作。每一個close代表的就是兩次揮手。

如果說只有兩次,也就是說一方在執行程式碼邏輯時沒有執行close(fd),第一,會造成檔案描述符洩露,第二,會發現這一端存在CLOSE_WAIT狀態的連線,即半關閉狀態。這種狀態並沒有釋放連線相關的資料和結構體,其實也會造成記憶體洩漏。

為什麼四次揮手後,主動斷開連線的一方會先進入TIME_WAIT狀態?

四次揮手的前三次,由於超時重傳機制的存在,可以保證一定被對方收到,但是最後一次的ACK,是可能丟失的。如果四次揮手後,主動斷開的一方直接CLOSED,那麼將無法保證最後一個ACK被對方收到。

如果ACK丟失,對方就會重新傳送FIN,如果處於CLOSED狀態,將不會收到FIN,使得對方長時間處於LAST_ACK狀態,並且多次傳送FIN消耗資源。因此,通過TIME_WAIT,可以較大概率地保證最後一個ACK被對方收到。

另一個原因是可以保證通道上的資料儘可能的消散,防止斷開連線後又立馬重新建立連線後,產生的資料錯誤等問題。

為什麼TIME_WAIT是2MSL?

2MSL是兩個最大報文段壽命,ACK丟失最多是一個MSL,再次收到重傳的FIN最多是一個MSL,因此2MSL可以儘可能的保證在CLOSE之前收到對方重傳的FIN。

相關文章