TCP協議的“三次握手”和“四次揮手”及面試題

TCP(TCP,Transmission Control Protocol)是一種面向連接的、可靠的、基於字節流的傳輸層通信協議,它提供着可靠的網絡傳輸服務。首先我們認識下TCP報文及其重要的一些字段屬性。
在這裏插入圖片描述

  • 源端口和目的端口,各佔2個字節(1個字節=8bit),分別寫入源端口和目的端口;
  • 序號,佔4個字節,TCP連接中傳送的字節流中的每個字節都按順序編號。例如,一段報文的序號字段值是 301 ,而攜帶的數據共有100字段,顯然下一個報文段(如果還有的話)的數據序號應該從401開始;
  • 確認號,佔4個字節,是期望收到對方下一個報文的第一個數據字節的序號。例如,B收到了A發送過來的報文,其序列號字段是501,而數據長度是200字節,這表明B正確的收到了A發送的到序號700爲止的數據。因此,B期望收到A的下一個數據序號是701,於是B在發送給A的確認報文段中把確認號置爲701;
  • 數據偏移,佔4位,它指出TCP報文的數據距離TCP報文段的起始處有多遠;
  • 保留,佔6位,保留今後使用,但目前應都位0;
  • 緊急URG,當URG=1,表明緊急指針字段有效。告訴系統此報文段中有緊急數據;
  • 確認ACK,僅當ACK=1時,確認號字段纔有效。TCP規定,在連接建立後所有報文的傳輸都必須把ACK置1;
  • 推送PSH,當兩個應用進程進行交互式通信時,有時在一端的應用進程希望在鍵入一個命令後立即就能收到對方的響應,這時候就將PSH=1;
  • 復位RST,當RST=1,表明TCP連接中出現嚴重差錯,必須釋放連接,然後再重新建立連接;
  • 同步SYN,在連接建立時用來同步序號。當SYN=1,ACK=0,表明是連接請求報文,若同意連接,則響應報文中應該使SYN=1,ACK=1;
  • 終止FIN,用來釋放連接。當FIN=1,表明此報文的發送方的數據已經發送完畢,並且要求釋放;
  • 窗口,佔2字節,指的是通知接收方,發送本報文你需要有多大的空間來接受;
  • 檢驗和,佔2字節,校驗首部和數據這兩部分;
  • 緊急指針,佔2字節,指出本報文段中的緊急數據的字節數;
    選項,長度可變,定義一些其他的可選的參數。

“三次握手”

TCP建立連接通過“三次握手”方式。握手之前主動打開連接的客戶端結束CLOSED階段,被動打開的服務器端也結束CLOSED階段,並進入LISTEN階段。隨後開始“三次握手”:
(1)首先客戶端向服務器端發送一段TCP報文,其中:

  • 標記位爲SYN,表示“請求建立新連接”;
  • 序號爲Seq=X(X一般爲1);
  • 隨後客戶端進入SYN-SENT階段。
    (2)服務器端接收到來自客戶端的TCP報文之後,結束LISTEN階段。並返回一段TCP報文,其中:
  • 標誌位爲SYN和ACK,表示“確認客戶端的報文Seq序號有效,服務器能正常接收客戶端發送的數據,並同意創建新連接”(即告訴客戶端,服務器收到了你的數據);
  • 序號爲Seq=y;
  • 確認號爲Ack=x+1,表示收到客戶端的序號Seq並將其值加1作爲自己確認號Ack的值;隨後服務器端進入SYN-RCVD階段。
    (3)客戶端接收到來自服務器端的確認收到數據的TCP報文之後,明確了從客戶端到服務器的數據傳輸是正常的,結束SYN-SENT階段。並返回最後一段TCP報文。其中:
  • 標誌位爲ACK,表示“確認收到服務器端同意連接的信號”(即告訴服務器,我知道你收到我發的數據了);
  • 序號爲Seq=x+1,表示收到服務器端的確認號Ack,並將其值作爲自己的序號值;
  • 確認號爲Ack=y+1,表示收到服務器端序號Seq,並將其值加1作爲自己的確認號Ack的值;
  • 隨後客戶端進入ESTABLISHED階段。
  • 服務器收到來自客戶端的“確認收到服務器數據”的TCP報文之後,明確了從服務器到客戶端的數據傳輸是正常的。結束SYN-SENT階段,進入ESTABLISHED階段。

在客戶端與服務器端傳輸的TCP報文中,雙方的確認號Ack和序號Seq的值,都是在彼此Ack和Seq值的基礎上進行計算的,這樣做保證了TCP報文傳輸的連貫性。一旦出現某一方發出的TCP報文丟失,便無法繼續"握手",以此確保了"三次握手"的順利完成。
以下爲客戶端主動發起連接的圖解:
動態圖

靜態圖
   通俗點講,TCP的三次握手的基本思想就是“讓我知道你已經知道了”。服務器監聽請求,客戶端發起連接請求(第一次連接),請求在路上可能存在丟失的風險,所以當請求到了服務器後如果服務器同意建立連接會給客戶端一個回信(第二次連接),告訴它:我已經收到請求,可以連接。但是回信也存在一個問題,那就是回信能不能到客戶端?它需要客戶端給他一個回信說我已經收到批准通知了,如果客戶端一直不回覆的話意味着客戶端沒有收到批准通知。因此客戶端一收到批准通知就立馬回覆(第三次握手):OK,我收到你的批准通知了。至此,三次握手結束。這裏舉一個在信號不好的地方打電話情景的例子:

甲:聽得到嗎?
乙:聽得到,你呢?
甲:我也聽到了。
。。。開始了愉快的暢談

“四次揮手”

TCP終止連接通過“四次揮手”方式。
在這裏插入圖片描述
揮手之前主動釋放連接的客戶端結束ESTABLISHED階段。隨後開始“四次揮手”:
(1)首先客戶端想要釋放連接,向服務器端發送一段TCP報文,其中:

  • 標記位爲FIN,表示“請求釋放連接“;
  • 序號爲Seq=U;
  • 隨後客戶端進入FIN-WAIT-1階段,即半關閉階段。並且停止在客戶端到服務器端方向上發送數據,但是客戶端仍然能接收從服務器端傳輸過來的數據。
注意:這裏發送的不是正常連接時傳輸的數據(非確認報文),而不是一切數據,所以客戶端仍然能發送ACK確認報文。

(2)服務器端接收到從客戶端發出的TCP報文之後,確認了客戶端想要釋放連接,隨後服務器端結束ESTABLISHED階段,進入CLOSE-WAIT階段(半關閉狀態)並返回一段TCP報文,其中:

  • 標記位爲ACK,表示“接收到客戶端發送的釋放連接的請求”;
  • 序號爲Seq=V;
  • 確認號爲Ack=U+1,表示是在收到客戶端報文的基礎上,將其序號Seq值加1作爲本段報文確認號Ack的值;
  • 隨後服務器端開始準備釋放服務器端到客戶端方向上的連接。
  • 客戶端收到從服務器端發出的TCP報文之後,確認了服務器收到了客戶端發出的釋放連接請求,隨後客戶端結束FIN-WAIT-1階段,進入FIN-WAIT-2階段
"前兩次揮手"既讓服務器端知道了客戶端想要釋放連接,也讓客戶端知道了服務器端了解了自己想要釋放連接的請求。於是,可以確認關閉客戶端到服務器端方向上的連接了。

(3)服務器端自從發出ACK確認報文之後,經過CLOSED-WAIT階段,做好了釋放服務器端到客戶端方向上的連接準備,再次向客戶端發出一段TCP報文,其中:

  • 標記位爲FIN,ACK,表示“已經準備好釋放連接了”。注意:這裏的ACK並不是確認收到服務器端報文的確認報文。
  • 序號爲Seq=W;
  • 確認號爲Ack=U+1;表示是在收到客戶端報文的基礎上,將其序號Seq值加1作爲本段報文確認號Ack的值。
  • 隨後服務器端結束CLOSE-WAIT階段,進入LAST-ACK階段。並且停止在服務器端到客戶端的方向上發送數據,但是服務器端仍然能夠接收從客戶端傳輸過來的數據。
    (4)客戶端收到從服務器端發出的TCP報文,確認了服務器端已做好釋放連接的準備,結束FIN-WAIT-2階段,進入TIME-WAIT階段,並向服務器端發送一段報文,其中:
  • 標記位爲ACK,表示“接收到服務器準備好釋放連接的信號”。
  • 序號爲Seq=U+1;表示是在收到了服務器端報文的基礎上,將其確認號Ack值作爲本段報文序號的值。
  • 確認號爲Ack=W+1;表示是在收到了服務器端報文的基礎上,將其序號Seq值作爲本段報文確認號的值。
  • 隨後客戶端開始在TIME-WAIT階段等待2MSL
  • 服務器端收到從客戶端發出的TCP報文之後結束LAST-ACK階段,進入CLOSED階段。由此正式確認關閉服務器端到客戶端方向上的連接。
  • 客戶端等待完2MSL之後,結束TIME-WAIT階段,進入CLOSED階段,由此完成“四次揮手”。
"後兩次揮手"既讓客戶端知道了服務器端準備好釋放連接了,也讓服務器端知道了客戶端了解了自己準備好釋放連接了。於是,可以確認關閉服務器端到客戶端方向上的連接了,由此完成“四次揮手”。

    通俗點講,釋放連接的時候:客戶端發起關閉連接的請求,關閉連接意味着客戶端結束了自己的工作即發送數據,但此時仍然處於數據傳輸的過程中,服務器可能未數據傳輸完畢,因此當請求到服務器時服務器知道了這個請求,但服務器數據傳輸未完成無法關閉連接,因此服務器先發送一個ACK告訴客戶端關閉請求已收到,但老子正忙,一會再關,你再等一會。等服務器工作完成了,就把FIN信號發送給客戶端,此時服務器要等着客戶端給他一個回信,讓服務器知道客戶端已經知道了。因此客戶端收到後就給服務器一個回信,爲了防止回信丟失,客戶端就再等2MSL個時間,之所以是2個,是因爲涉及到來回,第一個MSL中是回信在路上的最大時間,第二個MSL是萬一回信沒到服務端,服務端重發的FIN確認在路上的時間。這裏也舉個老師上課的例子:

學生:老師,下課了。
老師:好,我知道了,我說完這點。
。。。老師繼續balabala講課
老師:好了,說完了,下課吧。
學生:謝謝老師,老師再見。
。。。學生等待老師走出了教室之後,再走。

常見面試題

【問題1】爲什麼連接的時候是三次握手,關閉的時候卻是四次握手?
答:因爲當Server端收到Client端的SYN連接請求報文後,可以直接發送SYN+ACK報文。其中ACK報文是用來應答的,SYN報文是用來同步的。但是關閉連接時,當Server端收到FIN報文時,很可能並不會立即關閉SOCKET,所以只能先回復一個ACK報文,告訴Client端,“你發的FIN報文我收到了”。只有等到我Server端所有的報文都發送完了,我才能發送FIN報文,因此不能一起發送。故需要四步握手。

【問題2】爲什麼TIME_WAIT狀態需要經過2MSL(最大報文段生存時間)才能返回到CLOSE狀態?
答:雖然按道理,四個報文都發送完畢,我們可以直接進入CLOSE狀態了,但是我們必須假象網絡是不可靠的,有可以最後一個ACK丟失。所以TIME_WAIT狀態就是用來重發可能丟失的ACK報文。在Client發送出最後的ACK回覆,但該ACK可能丟失。Server如果沒有收到ACK,將不斷重複發送FIN片段。所以Client不能立即關閉,它必須確認Server接收到了該ACK。Client會在發送出ACK之後進入到TIME_WAIT狀態。Client會設置一個計時器,等待2MSL的時間。如果在該時間內再次收到FIN,那麼Client會重發ACK並再次等待2MSL。所謂的2MSL是兩倍的MSL(Maximum Segment Lifetime)。MSL指一個片段在網絡中最大的存活時間,2MSL就是一個發送和一個回覆所需的最大時間。如果直到2MSL,Client都沒有再次收到FIN,那麼Client推斷ACK已經被成功接收,則結束TCP連接。

【問題3】爲什麼不能用兩次握手進行連接?
答:3次握手完成兩個重要的功能,既要雙方做好發送數據的準備工作(雙方都知道彼此已準備好),也要允許雙方就初始序列號進行協商,這個序列號在握手過程中被髮送和確認。
現在把三次握手改成僅需要兩次握手,死鎖是可能發生的。作爲例子,考慮計算機S和C之間的通信,假定C給S發送一個連接請求分組,S收到了這個分組,併發 送了確認應答分組。按照兩次握手的協定,S認爲連接已經成功地建立了,可以開始發送數據分組。可是,C在S的應答分組在傳輸中被丟失的情況下,將不知道S 是否已準備好,不知道S建立什麼樣的序列號,C甚至懷疑S是否收到自己的連接請求分組。在這種情況下,C認爲連接還未建立成功,將忽略S發來的任何數據分 組,只等待連接確認應答分組。而S在發出的分組超時後,重複發送同樣的分組。這樣就形成了死鎖。

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

【問題5】SYN攻擊是怎麼回事?
在三次握手過程中,Server發送SYN-ACK之後,收到Client的ACK之前的TCP連接稱爲半連接(half-open connect),此時Server處於SYN_RCVD狀態,當收到ACK後,Server轉入ESTABLISHED狀態。SYN攻擊就是Client在短時間內僞造大量不存在的IP地址,並向Server不斷地發送SYN包,Server回覆確認包,並等待Client的確認,由於源地址是不存在的,因此,Server需要不斷重發直至超時,這些僞造的SYN包將產時間佔用未連接隊列,導致正常的SYN請求因爲隊列滿而被丟棄,從而引起網絡堵塞甚至系統癱瘓。SYN攻擊時一種典型的DDOS攻擊,檢測SYN攻擊的方式非常簡單,即當Server上有大量半連接狀態且源IP地址是隨機的,則可以斷定遭到SYN攻擊了,使用如下命令可以查看:

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