TCP:三次握手和四次揮手,面試無死角答復
圖片
什么是TCP
TCP是面向連接的,可靠的,基于字節(jié)流的傳輸層協(xié)議。
連接
所謂連接其實是保證可靠性和流量控制的狀態(tài)信息的總和,包括sokict,滑動窗口和序列號。
可靠性
tcp通過序列號,重傳機制,滑動窗口等一系列控制機制保證數(shù)據(jù)的無重復,無丟失,有序的被接受端處理。
字節(jié)流
tcp的數(shù)據(jù)是基于字節(jié)流,因此是無邊界,數(shù)據(jù)是可以無限大的,tcp可以通過分片機制將數(shù)據(jù)有序發(fā)送到接收端。
TCP結構
TCP的頭部在無“選項”字段的情況下是20個字節(jié)。包括:
- 2字節(jié)的源端口
- 2字節(jié)的目標端口
- 4字節(jié)序列號
- 4字節(jié)確認序列號
- 4位的首部長度
- 6位保留字段
- 6位標志位(SYN,ACK,RST,FIN,URG,PSH)
- 2字節(jié)窗口大小
- 2字節(jié)校驗和
- 2字節(jié)緊急指針
這里需要說明的是“選項”這個字段是用來輔助解決可靠性問題的,正是因為這個字段的長度是不確定的,所以需要“首部長度”這個字段來表示TCP頭部的長度。
TCP三次握手過程
圖片
什么是三次握手
TCP是基于連接的,所以TCP在使用前必須先建立連接,TCP建立連接的過程是基于三次握手的。
- 首先服務端的應用程序監(jiān)聽某個端口,也就是建立一個listened狀態(tài)的Socket,服務端處于listen狀態(tài)。
- 當客戶端創(chuàng)建一個Socket,并調(diào)用connect函數(shù)連接服務端的時候,會向服務端發(fā)送一個SYN狀態(tài)為1的tcp報文,并攜帶自己的隨機序列號??蛻舳颂幱趕yn_send狀態(tài)。
- 服務端接收到SYN報文后,會創(chuàng)建一個連接放入當前Socket的半連接隊列,然后回復ACK+SYN報文并攜帶自己的隨機序列號和確認序列號(客戶端序列號+1)。服務端處于syn_recv狀態(tài)。
- 客戶端接受到服務端的ack后,經(jīng)過一定處理,會給服務端回復一個ack報文,并攜帶確認序列號(服務端序列號+1)。此時客戶端處于establisten狀態(tài)。
- 服務端收到ack報文后,服務端會把半連接隊列中的連接放入全連接隊列。然后處于establisten狀態(tài)。
至此,tcp連接建立完成,注意第三次握手是可以傳輸數(shù)據(jù)的。在這之前不能傳輸數(shù)據(jù)。
為什么是三次握手
一般大家都會認為三次握手是為了保證客戶端和服務端雙方都能確認自身和接收端建立單向連接和保證自身能夠發(fā)送和接受成功數(shù)據(jù)。
這樣答本身也沒有錯,但是太粗化了。
既然握手是為保證連接的建立,那就要先知道什么是TCP連接。
TCP連接是保證可靠性和流量控制的狀態(tài)信息的總和,包括socket,序列號,滑動窗口。
在這里這個序列號至關重要,是保證消息無重復,無丟失,有序的關鍵,因此這里其實就是為了保證序列號的同步。
客戶端給服務端發(fā)送一個初始序列號,服務端回復syn+ack,就是告訴客戶端序列號已經(jīng)收到了并且把服務端的初始序列號發(fā)送給客戶端,客戶端收到后也要回復給服務端表示序列號已經(jīng)收到,這樣就能保證雙方都能確保序列號同步。
但是這還不是最重要的原因,最重要的原因是防止歷史連接初始化再次連接。比如有這樣一種情況,客戶端發(fā)送syn包給服務端,但是網(wǎng)絡阻塞了,服務端沒有收到,所以服務端也不會回復,客戶端收不到回復就會重新發(fā)送syn包,但是就在這時候服務端接收到了第一個syn包,并且回復客戶端,這個時候客戶端會進行比對校驗這是不是自己最新發(fā)送的syn回復包,如果不是的話就會給服務端發(fā)送rst包,表示要求服務端中斷這個連接。這也是三次握手的意義所在。
如果說沒有第三次握手,那么在發(fā)生上面的這個情況后,服務端就會為每個syn請求創(chuàng)建連接,連接是需要占用內(nèi)存的,就會耗費很多的資源。造成資源浪費,所以三次握手很有必要。
那么四次握手是否可以呢?
四次握手的話也是可以的,四次握手其實就是客戶端發(fā)送syn包給服務端,服務端回復ack包,服務端發(fā)送syn包給客戶端,客戶端回復ack包,三次握手中的第二次握手回復的是syn+ack包,所有相當于合并了四次握手中的中間兩次,所以三次握手最好。
圖片
四次揮手的過程
TCP是雙向連接,所以兩個方向上的連接都要斷開。
- 斷開前客戶端和服務端都處于ESTABLISTENED狀態(tài)。
- 客戶端調(diào)用close方法盡心斷開連接操作,客戶端會發(fā)送fin包給服務端??蛻舳颂幱趂in_wait1狀態(tài)
- 服務端接收到fin包后,會回復一個ack。此時服務端處于closed_wait狀態(tài)。
- 客戶端收到服務端的ack后,表示已經(jīng)斷開了自己到服務端的連接,但是服務端到客戶端的連接還沒有斷開,客戶端需要等待服務端主動請求斷開。此時客戶端處于fin_wait2狀態(tài)。
- 服務端之所以不會立刻給客戶端發(fā)送fin包是因為服務端可能還存在要發(fā)送的數(shù)據(jù),所以服務端需要把要處理的數(shù)據(jù)處理完在發(fā)送fin包給客戶端,此時數(shù)據(jù)已經(jīng)處理完,服務端主動給客戶端發(fā)送fin包,此時服務端處于last_ack狀態(tài)。
- 客戶端收到fin包后,會回復ack給服務端,此時客戶端處于time_wait狀態(tài)。
- 服務端收到ack后將狀態(tài)置為close.
- 客戶端此時并不會直接進入close狀態(tài),而是會進入time_wait狀態(tài), 這個狀態(tài)會持續(xù)2MSL時間。
在網(wǎng)絡傳輸?shù)氖澜缋?,有兩個值是用來表示數(shù)據(jù)包失效的:
- MSL是報文在網(wǎng)絡中的最大存活時間,超過這個時間就會被丟棄。
- TTL:在ip層的頭部中有一個TTL字段保存所經(jīng)過的路由數(shù),沒經(jīng)過一個路由數(shù)就會減1,當為0的時候,數(shù)據(jù)就會被丟棄。
所以一般情況下MSL會大于TTL減為0所消耗的時間。
這里為什么是2倍的MSL呢?
因為當客戶端接收到服務端的fin包后,會向服務端回復ack,但是客戶端不知道這個ack是否發(fā)送成功了,所以客戶端需要確認服務端接受成功后才能置為close狀態(tài),怎么確認呢,因為失敗重傳機制的存在,如果因為網(wǎng)絡阻塞服務端沒有收到ack,服務端會再次發(fā)送一次fin,一次ack包和再一次fin包就是2倍的MSL。MSL的計時是從收到fin包并且發(fā)送ack包開始的。
除了上面說的保證客戶端的ack發(fā)送到服務端,并被正確接收,從而保證被關閉連接的一方可以正確關閉。
還能保證那些阻塞在網(wǎng)絡中舊的連接,在端口又被復用的情況下,被接收到,這樣就會發(fā)生數(shù)據(jù)錯亂,而time_wait可以保證全部的網(wǎng)絡中的連接被丟棄。
MSL默認是30秒。需要注意的是time_wait 的狀態(tài)多了以后會占用內(nèi)存資源和端口資源,所以不宜太多。
為什么要進行四次揮手?
tcp是雙向連接,客戶端發(fā)送fin包給服務端,服務端回復ack,只是客戶端告訴服務端不再向服務端發(fā)送數(shù)據(jù)。
還需要服務端告訴客戶端,服務端不再向客戶端發(fā)送數(shù)據(jù)了,也就是服務端也要想客戶端發(fā)送fin包,客戶端也要給服務端回復ack包,這時候服務端和客戶端才能進入close狀態(tài)。
服務端在收到客戶端發(fā)送的fin包并回復ack包后,服務端并不能馬上向發(fā)送端發(fā)送fin包,因為此時可能還有連接在處理數(shù)據(jù),必須等到數(shù)據(jù)處理完后才能向客戶端發(fā)送fin包。
正因為這個原因,不能像三次握手那樣把中間兩次合并。