前言
這段時間面試官都挺忙的,頻頻出現在部落格文章标題,雖然我不是特别想蹭熱度,但是實在想不到好的标題了-。-,蹭蹭就蹭蹭 :)
事實上我在阿裡面試的時候确實被問到了這個問題,HTTP、HTTPS、TCP/IP、Socket通信、三次握手四次揮手過程?當時雖然思路正确,可惜最終也并不算完全答對
結束後花了一段時間整理了下思路,參考和查閱了一下資料,整理如下:
來源:程式設計充電寶
作者:在所不辭
問題描述
你能否講解一下
TCP
的三次握手與四次揮手呢?
面試官如果從整體到局部入手,那我們就先講講整個三次握手和四次揮手的過程,但不要忘記,講的同時應該适當展現你對該知識點掌握的深度和廣度,具體怎麼說,我們後面慢慢道來。
三次握手
所謂的
握手
即一次發包到接收的過程,可能從用戶端發送到服務端,也可能從服務端發送到用戶端。
過程描述
先上一張
TCP封包結構
圖,待會我們會回來看這張圖:
TCP封包結構
先上三次握手的流程圖:
三次握手
接下來我們來詳細講解下上圖的過程:
- 客戶主機發起連接配接請求,設定
标志位為SYN
,同時用戶端1
選擇了一個初始序号随機
,并且存放在client_isn
字段的TCP封包
中,如下圖:序号
 第一次握手:SYN封包
- 接下來,當服務端接收到該封包後,會為其配置設定
(這使得TCP容易受到被稱為TCP 緩存和變量
的拒絕服務攻擊)緊接着,服務端會傳回一個SYN 洪泛攻擊
到用戶端,其中SYNACK 封包
SYN
,1
設定為确認号
,并且選一個自己的初始序号client_isn + 1
,并放置在server_isn
字段中,如下圖:序号
 第二次握手:SYNACK封包
- 當收到伺服器發來的
封包段後,用戶端也需要給該連接配接配置設定緩存和變量,然後再次發送一個确認封包給服務端,其中,SYNACK
标志位設定為 ,将SYN
确認号
,另外,此次封包可以攜帶負載資料:server_isn + 1
 第三次握手:ACK封包
細節拓展
- 三次握手的狀态轉換圖(建議達到能默寫下來的熟練程度)
 三次握手狀态圖
- 伺服器為什麼要使用特殊的初始序号
?這為什麼是必要的呢?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
 第一次揮手:FIN封包
- 服務端接收到
後,會傳回一個FIN 封包
回去,此時設定ACK 封包
ACK
1
确認号
;表明自己接受到了用戶端關閉連接配接的請求,但還沒有準備好關閉連接配接。發送完畢後,伺服器端進入u + 1
狀态,用戶端接收到這個确認包之後,進入CLOSE-WAIT
狀态,等待伺服器端關閉連接配接。FIN-WAIT-2
 第二次揮手:ACK封包
- 伺服器端準備好關閉連接配接時,向用戶端發送結束連接配接請求,
置為FIN
;發送完畢後,伺服器端進入1
狀态,等待來自用戶端的最後一個LAST-ACK
ACK
 第三次揮手:FIN封包
- 用戶端接收到服務端傳來的
後,知道伺服器已經準備好關閉了,發送一個确認包,并進入FIN 封包
狀态,等待可能出現的要求重傳的TIME-WAIT
;伺服器端接收到這個确認包之後,關閉連接配接,進入ACK 封包
CLOSED
用戶端等待了某個固定時間(兩個最大段生命周期,`2MSL`,2 Maximum Segment Lifetime)之後,沒有收到伺服器端的 `ACK` ,認為伺服器端已經正常關閉連接配接,于是自己也關閉連接配接,進入 `CLOSED` 狀态。  第四次揮手: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 封包
(Maximum Segment Lifetime)指一個封包在網絡中最大的存活時間,2MSL就是一個發送和一個回複所需的最大時間。如果直到MSL
時間後,用戶端都沒有再次收到2MSL
,那麼用戶端推斷FIN 封包
已經被伺服器成功接收,是以結束ACK 封包
TCP 連接配接
- 第二,防止已失效的連接配接請求封包段出現在新的連接配接中。用戶端在發送完最後一個
後,再經過時間ACK 封包
,就可以使由于網絡不通暢産生的滞留封包段失效。這樣下一個新的連接配接中就不會出現舊的連接配接請求封包。2MSL