天天看點

阿裡面試官: HTTP、HTTPS、TCP/IP、Socket通信、三次握手四次揮手過程?(附全網最具深度的三次握手、四次揮手講解)前言2020金三銀四提前備戰完整精編面試解析下載下傳位址:https://shimo.im/docs/3Tvytq686Yyv83KX

前言

這段時間面試官都挺忙的,頻頻出現在部落格文章标題,雖然我不是特别想蹭熱度,但是實在想不到好的标題了-。-,蹭蹭就蹭蹭 :)

事實上我在阿裡面試的時候确實被問到了這個問題,HTTP、HTTPS、TCP/IP、Socket通信、三次握手四次揮手過程?當時雖然思路正确,可惜最終也并不算完全答對

結束後花了一段時間整理了下思路,參考和查閱了一下資料,整理如下:

來源:程式設計充電寶

作者:在所不辭

問題描述

你能否講解一下

TCP

的三次握手與四次揮手呢?

面試官如果從整體到局部入手,那我們就先講講整個三次握手和四次揮手的過程,但不要忘記,講的同時應該适當展現你對該知識點掌握的深度和廣度,具體怎麼說,我們後面慢慢道來。

三次握手

所謂的

握手

即一次發包到接收的過程,可能從用戶端發送到服務端,也可能從服務端發送到用戶端。

過程描述

先上一張

TCP封包結構

圖,待會我們會回來看這張圖:

TCP封包結構

先上三次握手的流程圖:

三次握手

接下來我們來詳細講解下上圖的過程:

  • 客戶主機發起連接配接請求,設定

    SYN

    标志位為

    1

    ,同時用戶端

    随機

    選擇了一個初始序号

    client_isn

    ,并且存放在

    TCP封包

    字段的

    序号

    中,如下圖:
    ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8xODQ1MjUzNi03MDJmZWJhZmYwZjNiNjE2?x-oss-process=image/format,png) 第一次握手:SYN封包
               
  • 接下來,當服務端接收到該封包後,會為其配置設定

    TCP 緩存和變量

    (這使得TCP容易受到被稱為

    SYN 洪泛攻擊

    的拒絕服務攻擊)緊接着,服務端會傳回一個

    SYNACK 封包

    到用戶端,其中

    SYN

    1

    确認号

    設定為

    client_isn + 1

    ,并且選一個自己的初始序号

    server_isn

    ,并放置在

    序号

    字段中,如下圖:
    ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8xODQ1MjUzNi03ODJjM2JmYTM1MmI4MmVk?x-oss-process=image/format,png) 第二次握手:SYNACK封包
               
  • 當收到伺服器發來的

    SYNACK

    封包段後,用戶端也需要給該連接配接配置設定緩存和變量,然後再次發送一個确認封包給服務端,其中,

    SYN

    标志位設定為 ,将

    确認号

    server_isn + 1

    ,另外,此次封包可以攜帶負載資料:
    ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8xODQ1MjUzNi04ZDZhNGQwM2ZhNmVlYTU3?x-oss-process=image/format,png) 第三次握手:ACK封包
               

細節拓展

  • 三次握手的狀态轉換圖(建議達到能默寫下來的熟練程度)
    ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8xODQ1MjUzNi1jNzNkNTkwYzc1NDMwZjkx?x-oss-process=image/format,png) 三次握手狀态圖
               
  • 伺服器為什麼要使用特殊的初始序号

    server_isn

    ?這為什麼是必要的呢?

這個細節和

問題深究第3題

是一緻的,伺服器使用

特定的初始序列号 server_isn

(從

目的地IP

端口

散列

中擷取)可以用來抵禦SYN洪水攻擊。具體為什麼,以及什麼是

SYN 洪泛攻擊

,問題深究部分我們會再詳談。

為了下文描述友善,我們将三次握手分别稱為:

SYN 封包

SYNACK 封包

ACK 封包

問題深究

1.為什麼要三次握手而不是兩次?

簡單來說,三次握手的目的是為了讓雙方驗證各自的接收能力和發送能力。

  • 第一次握手,A 發送

    SYN

    B

    B

    接收到了後,能确認什麼呢? 顯然,

    B

    能确認

    A

    發送

    能力和

    B

    接收

    能力;
  • 第二次握手,

    B

    發送

    SYNACK

    A

    A

    接收到後,能确認什麼呢?

    A

    B

    的發送能力和

    A

    自己的接收能力,此外,

    A

    收到了

    SYNACK

    ,那麼說明前面

    A

    發的

    SYN

    成功到達

    B

    的手中,是以也能确認

    A

    自己的

    發送

    B

    接收

    能力;至此,

    A

    已經确認了雙方各自的發送能力和接收能力都是

    OK

    的,是以轉為

    ESTABLISHED

    狀态;
  • 第三次握手,

    A

    ACK

    B

    B

    接收後,能确認什麼呢?
    直接的,`B`能确認`A`的`發送`能力和`B`的`接收`能力,另外由于`B`能收到`ACK`說明前面發送的`SYNACK`已經成功被接受了,說明能确認`A`的`接收`能力和`B`的`發送`能力。
               

如果使用兩次握手,就不能确認上述所說的四種能力,那麼就會導緻問題。

假定不采用第三次封包握手,那麼隻要B發出确認,新的連接配接就建立了。

現假定一種異常情況,即

A

發出的

SYN

封包段并沒有丢失,而是在某些網絡節點長時間滞留了,以緻延誤到連接配接釋放後的某個時間才到達

B

。本來這是一個早已失效的封包段。但

B

收到此失效的連接配接請求封包段後,卻誤以為是

A

又發出一次新的連接配接請求,于是就向

A

發出确認封包段,同意建立連接配接。

由于現在

A

并沒有發出建立連接配接的請求,是以不會理睬

B

的确認,也不會向

B

發送資料,但

B

卻以為新的運輸連接配接已經建立了,并一直等待

A

發來的資料。

B

的許多資源就這樣白白浪費了。

2.兩個TCP建立請求互相之間同時發起時會發生什麼?建立幾個連接配接?

首先了解題意,我們知道

三次握手

正是建立

TCP連接配接

的過程,我們假設 A 是用戶端,B 是服務端:

rfc793-同時啟動

這裡先給大家講一下上圖中一些符号的含義:

  • 右箭頭 (-->) :從 A 發送到 B 的 TCP 封包段,且 B 接收到了;
  • 左箭頭 (<--) :從 B 發送到 A 的 TCP 封包段,且 A 接收到了;
  • 省略号 (…) :TCP 封包段仍在網絡中(delayed);
  • 丢失 ("XXX") :TCP 封包段丢失或者被拒絕。
  • 注釋會放在括号中;
  • TCP 狀态代表了處于中間的封包段到達之後的狀态(AFTER);
  • 封包段的内容隻顯示了序列号(SEQ)、控制符(CTL)和 ACK,其餘内容被省略。

接下來我們詳細來看看上圖中,兩個請求同時互相發起時,兩個

TCP

均會經曆如下狀态的轉換:

同步請求的狀态轉換

上述的狀态轉換圖可以跟前面的三次握手的轉換圖進行對比了解,同時發起的兩個請求最終

隻會建立一個連接配接

SYN-RECEIVED

SYN-RCVD

是一樣的,前者

rcf793

中的描述方法,後者是《計算機網絡-自頂向下方法》中的使用方法。

3. 用戶端正在和服務端建立 TCP 連接配接,然而當伺服器變 SYN-RCVD 後,此時一個舊的 SYN 封包 又到達了,伺服器會如何處理?

其實這道題更加深挖了

TCP 建立連接配接

的過程,我們可以在

rfc793

中了解到詳細資訊。

rfc793-RST

從上圖可以看到,第三行就是舊的

SYN 連接配接

到達伺服器時,第四行是伺服器照常傳回,第五行是用戶端給服務端發送

RST 封包

,将服務端重置為

LISTEN

我們需要從上圖了解到的一點是,服務端在

SYN_RECEIVED

狀态下,接收到舊的

SYN 封包

時是不能作出判斷的,而是照常傳回,當用戶端接收到該封包後發現異常,才會發送

RST 封包

,重置連接配接。

關于

RST 封包

,我一開始也很疑惑,直到看到

rfc793 原文

rfc793-page33

确實說明了

TCP B

不能檢測這個舊的

SYN 封包

是否正确,是以正常傳回。而用戶端收到會進行檢測,發現是舊的封包,就會傳回

RST 封包

4.第三次握手失敗了怎麼辦?

這個問題在網上找到的答案品質參差不齊,翻閱了

rfc793

,仔細研究後,最終整理出以下答案:

首先考慮失敗的情況:

ACK封包丢失導緻第三次握手失敗

當用戶端收到服務端的

SYNACK

應答後,其狀态變為

ESTABLISHED

,并會發送

ACK

包給服務端,準備發送資料了。如果此時

ACK

在網絡中丢失(如上圖所示),過了逾時計時器後,那麼服務端會重新發送

SYNACK

包,重傳次數根據

/proc/sys/net/ipv4/tcp_synack_retries

來指定,預設是

5

次。如果重傳指定次數到了後,仍然未收到

ACK

應答,那麼一段時間後,

Server

自動關閉這個連接配接。

問題就在這裡,用戶端已經認為連接配接建立,而服務端則可能處在

SYN-RCVD

或者

CLOSED

,接下來我們需要考慮這兩種情況下服務端的應答:

  • 服務端處于

    CLOSED

    ,當接收到連接配接已經關閉的請求時,服務端會傳回

    RST 封包

    ,用戶端接收到後就會關閉連接配接,如果需要的話則會重連,那麼那就是另一個三次握手了。
  • SYN-RCVD

    ,此時如果接收到正常的

    ACK 封包

    ,那麼很好,連接配接恢複,繼續傳輸資料;如果接收到寫入資料等請求呢?注意了,此時寫入資料等請求也是帶着

    ACK 封包

    的,實際上也能恢複連接配接,使伺服器恢複到

    ESTABLISHED

    狀态,繼續傳輸資料。

這個結論也可以在

STACKFLOW

上找到驗證:

What if a TCP handshake segment is lost?

上圖圈住的部分:

總的來說,如果一個

ACK 封包

丢失了,但它的下一個資料包沒有丢失,那麼連接配接正常,否則,連接配接會被重置。

5.知道SYN攻擊嗎?如何防範?

所謂

SYN 洪泛攻擊

,就是利用

SYNACK 封包

的時候,伺服器會為用戶端請求配置設定緩存,那麼黑客(攻擊者),就可以使用一批虛假的

ip

向伺服器大量地發建立

TCP 連接配接

的請求,伺服器為這些

虛假ip

配置設定了緩存後,處在

SYN_RCVD

狀态,存放在

半連接配接隊列

中;另外,伺服器發送的請求又不可能得到回複(ip都是假的,能回複就有鬼了),隻能不斷地

重發請求

,直到達到設定的時間/次數後,才會關閉。

伺服器不斷為這些

半開連接配接

配置設定資源(但從未使用),導緻伺服器的連接配接資源被消耗殆盡,不過所幸,我們可以使用

SYN Cookie

進行有效地防禦。

SYN Cookie

防禦系統,與前面接收到

SYN 封包

就配置設定緩存不同,此時暫不配置設定資源;同時利用

SYN 封包

目的地IP

端口

,以及伺服器存儲的一個

秘密數

,使用它們進行散列,得到

server_isn

,然後附着在

SYNACK 封包

中發送給用戶端,接下來就是對

ACK 封包

進行判斷,如果其傳回的

ack

字段正好等于

server_isn + 1

,說明這是一個合法的

ACK

,那麼伺服器才會為其生成一個具有套接字的全開的連接配接。

SYN Cookie 防禦

當然這種方案也有一定

缺點

,最明顯的就是

伺服器不儲存連接配接的半開狀态

,就

喪失

重發SYN-ACK消息

的能力,這一方面會降低正常使用者的連接配接成功率,另一方面會導緻某些情況下正常通信的雙方會對連接配接是否成功打開産生誤解,如用戶端發給服務端的第三次握手消息(

ACK

)半路遺失,用戶端認為連接配接成功了,服務端認為沒收到

ACK

,連接配接沒成功,這種情況就需要上層應用采取政策特别處理了。

6.(ISN)是固定的嗎?

不固定,

client_isn

是随機生成的,而

server_isn

則需要根據

SYN 封包

中的

源、ip和端口

,加上伺服器本身的

密碼數

進行相同的散列得到,顯然這也不是固定的。

7.三次握手過程中可以攜帶資料嗎?

講過程的時候其實已經講了,

第三次握手是可以攜帶資料的

,而前兩次不行。

8. 關于 https 的認證過程?

限于篇幅,此處暫時不講,留意後續文章。

四次揮手

握手

類似,每次

揮手

也代表一次封包的發出和接收。

因為自頂向上這本書裡面的圖比較簡略,這裡采用謝希仁版本的計算機網絡中的圖:

計算機網絡-謝希仁-四次揮手

首先,目前用戶端和伺服器的狀态都為

ESTABLISHED

,接下來我們詳細講解下上圖的過程:

  • 客戶主機發起連接配接釋放的請求,設定

    FIN

    1

    ,當然,序号

    seq

    也會帶上,這裡假設為

    u

    ;發送完畢後,用戶端進入

    FIN-WAIT-1

    狀态。
    ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8xODQ1MjUzNi1kMmYxZDE0YjgyOWRlZmY1?x-oss-process=image/format,png) 第一次揮手:FIN封包
               
  • 服務端接收到

    FIN 封包

    後,會傳回一個

    ACK 封包

    回去,此時設定

    ACK

    1

    确認号

    u + 1

    ;表明自己接受到了用戶端關閉連接配接的請求,但還沒有準備好關閉連接配接。發送完畢後,伺服器端進入

    CLOSE-WAIT

    狀态,用戶端接收到這個确認包之後,進入

    FIN-WAIT-2

    狀态,等待伺服器端關閉連接配接。
    ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8xODQ1MjUzNi0xMjEwZTM2MTA5YjYzNzcz?x-oss-process=image/format,png) 第二次揮手:ACK封包
               
  • 伺服器端準備好關閉連接配接時,向用戶端發送結束連接配接請求,

    FIN

    置為

    1

    ;發送完畢後,伺服器端進入

    LAST-ACK

    狀态,等待來自用戶端的最後一個

    ACK

    ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8xODQ1MjUzNi1jYTI2NDVmZjBmMjc2MGUz?x-oss-process=image/format,png) 第三次揮手:FIN封包
               
  • 用戶端接收到服務端傳來的

    FIN 封包

    後,知道伺服器已經準備好關閉了,發送一個确認包,并進入

    TIME-WAIT

    狀态,等待可能出現的要求重傳的

    ACK 封包

    ;伺服器端接收到這個确認包之後,關閉連接配接,進入

    CLOSED

    用戶端等待了某個固定時間(兩個最大段生命周期,`2MSL`,2 Maximum Segment Lifetime)之後,沒有收到伺服器端的 `ACK` ,認為伺服器端已經正常關閉連接配接,于是自己也關閉連接配接,進入 `CLOSED` 狀态。
    
    ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8xODQ1MjUzNi1kNGVlNTI1ZWMzY2JiZDY0?x-oss-process=image/format,png) 第四次揮手:ACK封包
               

這裡我們再來看下

rfc793

中關于四次揮手的簡單例子:

rfc793-正常的關閉例子

  • 四次揮手重要的是

    TIME-WAIT

    狀态,為什麼需要這個狀态呢?

要確定伺服器是否已經收到了我們的

ACK 封包

,如果沒有收到的話,伺服器會重新發

FIN 封包

給用戶端,那麼用戶端再次收到

FIN 封包

之後,就知道之前的

ACK 封包

丢失了,就會再次發送

ACK 封包

1.為什麼握手隻要三次,揮手卻要四次?

關鍵就在中間兩步。

  • 建立連接配接時,當伺服器收到用戶端的

    SYN 封包

    後,可以直接發送

    SYNACK 封包

    。其中

    ACK

    是用來應答的,

    SYN

    是用來同步的。
  • 但是關閉連接配接時,當伺服器收到

    FIN 封包

    時,很可能并不會立即關閉

    SOCKET

    ,是以隻能先回複一個

    ACK 封包

    ,告訴用戶端,“你發的

    FIN 封包

    我收到了”。隻有等到伺服器所有的封包都發送/接收完了,我才能發送

    FIN 封包

    ,是以不能一起發送,需要四次握手。

2.為什麼 TIME_WAIT 狀态需要經過 2MSL 才能轉換到 CLOSE 狀态?

  • 第一,為了保證用戶端發送的最後一個

    ACK 封包

    能夠到達伺服器。我們必須假設網絡是不可靠的,

    ACK 封包

    可能丢失。如果服務端發出

    FIN 封包

    後沒有收到

    ACK 封包

    ,就會重發

    FIN 封包

    ,此時處于

    TIME-WAIT

    狀态的用戶端就會重發

    ACK 封包

    。當然,用戶端也不能無限久的等待這個可能存在的

    FIN 封包

    ,因為如果服務端正常接收到了

    ACK 封包

    後是不會再發

    FIN 封包

    的。是以,用戶端需要設定一個計時器,那麼等待多久最合适呢?所謂的

    MSL

    (Maximum Segment Lifetime)指一個封包在網絡中最大的存活時間,2MSL就是一個發送和一個回複所需的最大時間。如果直到

    2MSL

    時間後,用戶端都沒有再次收到

    FIN 封包

    ,那麼用戶端推斷

    ACK 封包

    已經被伺服器成功接收,是以結束

    TCP 連接配接

  • 第二,防止已失效的連接配接請求封包段出現在新的連接配接中。用戶端在發送完最後一個

    ACK 封包

    後,再經過時間

    2MSL

    ,就可以使由于網絡不通暢産生的滞留封包段失效。這樣下一個新的連接配接中就不會出現舊的連接配接請求封包。

2020金三銀四提前備戰完整精編面試解析下載下傳位址: https://shimo.im/docs/3Tvytq686Yyv83KX

繼續閱讀