不要再問我三次握手和四次揮手

三次握手和四次揮手是各個公司常見的考點,也具有一定的水平區分度,也被一些面試官作為熱身題。很多小伙伴說這個問題剛開始回答的挺好,但是后面越回答越冒冷汗,最后就歇菜了。

見過比較典型的面試場景是這樣的:

面試官:請介紹下三次握手
求職者:第一次握手就是客戶端給服務器端發送一個報文,第二次就是服務器收到報文之后,會應答一個報文給客戶端,第三次握手就是客戶端收到報文后再給服務器發送一個報文,三次握手就成功了。
面試官:然后呢?
求職者:這就是三次握手的過程,很簡單的。
面試官:。。。。。。
番外篇:一首涼涼送給你

記住一句話:面試時越簡單的問題,一般就是隱藏著比較大的坑,一般都是需要將問題擴展的。上面求職者的回答不對嗎?當然對,但距離面試官的期望可能還有點距離。

希望大家能帶著如下問題進行閱讀,收獲會更大。

  1. 請畫出三次握手和四次揮手的示意圖
  2. 為什么連接的時候是三次握手?
  3. 什么是半連接隊列?
  4. ISN(Initial Sequence Number)是固定的嗎?
  5. 三次握手過程中可以攜帶數據嗎?
  6. 如果第三次握手丟失了,客戶端服務端會如何處理?
  7. SYN攻擊是什么?
  8. 揮手為什么需要四次?
  9. 四次揮手釋放連接時,等待2MSL的意義?

三次握手和四次揮手.png

1. 三次握手

三次握手(Three-way Handshake)其實就是指建立一個TCP連接時,需要客戶端和服務器總共發送3個包。進行三次握手的主要作用就是為了確認雙方的接收能力和發送能力是否正常、指定自己的初始化序列號為后面的可靠性傳送做準備。實質上其實就是連接服務器指定端口,建立TCP連接,并同步連接雙方的序列號和確認號,交換TCP窗口大小信息。

剛開始客戶端處于 Closed 的狀態,服務端處于 Listen 狀態。
進行三次握手:

  • 第一次握手:客戶端給服務端發一個 SYN 報文,并指明客戶端的初始化序列號 ISN。此時客戶端處于?SYN_SENT?狀態。

    首部的同步位SYN=1,初始序號seq=x,SYN=1的報文段不能攜帶數據,但要消耗掉一個序號。

  • 第二次握手:服務器收到客戶端的 SYN 報文之后,會以自己的 SYN 報文作為應答,并且也是指定了自己的初始化序列號 ISN(s)。同時會把客戶端的 ISN + 1 作為ACK 的值,表示自己已經收到了客戶端的 SYN,此時服務器處于?SYN_RCVD?的狀態。

    在確認報文段中SYN=1,ACK=1,確認號ack=x+1,初始序號seq=y。

  • 第三次握手:客戶端收到 SYN 報文之后,會發送一個 ACK 報文,當然,也是一樣把服務器的 ISN + 1 作為 ACK 的值,表示已經收到了服務端的 SYN 報文,此時客戶端處于?ESTABLISHED?狀態。服務器收到 ACK 報文之后,也處于?ESTABLISHED?狀態,此時,雙方已建立起了連接。

    確認報文段ACK=1,確認號ack=y+1,序號seq=x+1(初始為seq=x,第二個報文段所以要+1),ACK報文段可以攜帶數據,不攜帶數據則不消耗序號。

發送第一個SYN的一端將執行主動打開(active open),接收這個SYN并發回下一個SYN的另一端執行被動打開(passive open)。

在socket編程中,客戶端執行connect()時,將觸發三次握手。

三次握手.png

1.1 為什么需要三次握手,兩次不行嗎?

弄清這個問題,我們需要先弄明白三次握手的目的是什么,能不能只用兩次握手來達到同樣的目的。

  • 第一次握手:客戶端發送網絡包,服務端收到了。
    這樣服務端就能得出結論:客戶端的發送能力、服務端的接收能力是正常的。
  • 第二次握手:服務端發包,客戶端收到了。
    這樣客戶端就能得出結論:服務端的接收、發送能力,客戶端的接收、發送能力是正常的。不過此時服務器并不能確認客戶端的接收能力是否正常。
  • 第三次握手:客戶端發包,服務端收到了。
    這樣服務端就能得出結論:客戶端的接收、發送能力正常,服務器自己的發送、接收能力也正常。

因此,需要三次握手才能確認雙方的接收與發送能力是否正常。

試想如果是用兩次握手,則會出現下面這種情況:

如客戶端發出連接請求,但因連接請求報文丟失而未收到確認,于是客戶端再重傳一次連接請求。后來收到了確認,建立了連接。數據傳輸完畢后,就釋放了連接,客戶端共發出了兩個連接請求報文段,其中第一個丟失,第二個到達了服務端,但是第一個丟失的報文段只是在某些網絡結點長時間滯留了,延誤到連接釋放以后的某個時間才到達服務端,此時服務端誤認為客戶端又發出一次新的連接請求,于是就向客戶端發出確認報文段,同意建立連接,不采用三次握手,只要服務端發出確認,就建立新的連接了,此時客戶端忽略服務端發來的確認,也不發送數據,則服務端一致等待客戶端發送數據,浪費資源。

1.2 什么是半連接隊列?

服務器第一次收到客戶端的 SYN 之后,就會處于 SYN_RCVD 狀態,此時雙方還沒有完全建立其連接,服務器會把此種狀態下請求連接放在一個隊列里,我們把這種隊列稱之為半連接隊列

當然還有一個全連接隊列,就是已經完成三次握手,建立起連接的就會放在全連接隊列中。如果隊列滿了就有可能會出現丟包現象。

這里在補充一點關于SYN-ACK 重傳次數的問題:
服務器發送完SYN-ACK包,如果未收到客戶確認包,服務器進行首次重傳,等待一段時間仍未收到客戶確認包,進行第二次重傳。如果重傳次數超過系統規定的最大重傳次數,系統將該連接信息從半連接隊列中刪除。
注意,每次重傳等待的時間不一定相同,一般會是指數增長,例如間隔時間為 1s,2s,4s,8s…

1.3 ISN(Initial Sequence Number)是固定的嗎?

當一端為建立連接而發送它的SYN時,它為連接選擇一個初始序號。ISN隨時間而變化,因此每個連接都將具有不同的ISN。ISN可以看作是一個32比特的計數器,每4ms加1 。這樣選擇序號的目的在于防止在網絡中被延遲的分組在以后又被傳送,而導致某個連接的一方對它做錯誤的解釋。

三次握手的其中一個重要功能是客戶端和服務端交換 ISN(Initial Sequence Number),以便讓對方知道接下來接收數據的時候如何按序列號組裝數據。如果 ISN 是固定的,攻擊者很容易猜出后續的確認號,因此 ISN 是動態生成的。

1.4 三次握手過程中可以攜帶數據嗎?

其實第三次握手的時候,是可以攜帶數據的。但是,第一次、第二次握手不可以攜帶數據

為什么這樣呢?大家可以想一個問題,假如第一次握手可以攜帶數據的話,如果有人要惡意攻擊服務器,那他每次都在第一次握手中的 SYN 報文中放入大量的數據。因為攻擊者根本就不理服務器的接收、發送能力是否正常,然后瘋狂著重復發 SYN 報文的話,這會讓服務器花費很多時間、內存空間來接收這些報文。

也就是說,第一次握手不可以放數據,其中一個簡單的原因就是會讓服務器更加容易受到攻擊了。而對于第三次的話,此時客戶端已經處于 ESTABLISHED 狀態。對于客戶端來說,他已經建立起連接了,并且也已經知道服務器的接收、發送能力是正常的了,所以能攜帶數據也沒啥毛病。

1.5 SYN攻擊是什么?

服務器端的資源分配是在二次握手時分配的,而客戶端的資源是在完成三次握手時分配的,所以服務器容易受到SYN洪泛攻擊。SYN攻擊就是Client在短時間內偽造大量不存在的IP地址,并向Server不斷地發送SYN包,Server則回復確認包,并等待Client確認,由于源地址不存在,因此Server需要不斷重發直至超時,這些偽造的SYN包將長時間占用未連接隊列,導致正常的SYN請求因為隊列滿而被丟棄,從而引起網絡擁塞甚至系統癱瘓。SYN 攻擊是一種典型的 DoS/DDoS 攻擊。

檢測 SYN 攻擊非常的方便,當你在服務器上看到大量的半連接狀態時,特別是源IP地址是隨機的,基本上可以斷定這是一次SYN攻擊。在 Linux/Unix 上可以使用系統自帶的 netstat 命令來檢測 SYN 攻擊。

netstat -n -p TCP | grep SYN_RECV
  • 1

常見的防御 SYN 攻擊的方法有如下幾種:

  • 縮短超時(SYN Timeout)時間
  • 增加最大半連接數
  • 過濾網關防護
  • SYN cookies技術

2. 四次揮手

建立一個連接需要三次握手,而終止一個連接要經過四次揮手(也有將四次揮手叫做四次握手的)。這由TCP的半關閉(half-close)造成的。所謂的半關閉,其實就是TCP提供了連接的一端在結束它的發送后還能接收來自另一端數據的能力。

TCP 連接的拆除需要發送四個包,因此稱為四次揮手(Four-way handshake),客戶端或服務端均可主動發起揮手動作。

剛開始雙方都處于ESTABLISHED?狀態,假如是客戶端先發起關閉請求。四次揮手的過程如下:

  • 第一次揮手:客戶端發送一個 FIN 報文,報文中會指定一個序列號。此時客戶端處于?FIN_WAIT1?狀態。
    即發出連接釋放報文段(FIN=1,序號seq=u),并停止再發送數據,主動關閉TCP連接,進入FIN_WAIT1(終止等待1)狀態,等待服務端的確認。
  • 第二次揮手:服務端收到 FIN 之后,會發送 ACK 報文,且把客戶端的序列號值 +1 作為 ACK 報文的序列號值,表明已經收到客戶端的報文了,此時服務端處于?CLOSE_WAIT?狀態。
    即服務端收到連接釋放報文段后即發出確認報文段(ACK=1,確認號ack=u+1,序號seq=v),服務端進入CLOSE_WAIT(關閉等待)狀態,此時的TCP處于半關閉狀態,客戶端到服務端的連接釋放。客戶端收到服務端的確認后,進入FIN_WAIT2(終止等待2)狀態,等待服務端發出的連接釋放報文段。
  • 第三次揮手:如果服務端也想斷開連接了,和客戶端的第一次揮手一樣,發給 FIN 報文,且指定一個序列號。此時服務端處于?LAST_ACK?的狀態。
    即服務端沒有要向客戶端發出的數據,服務端發出連接釋放報文段(FIN=1,ACK=1,序號seq=w,確認號ack=u+1),服務端進入LAST_ACK(最后確認)狀態,等待客戶端的確認。
  • 第四次揮手:客戶端收到 FIN 之后,一樣發送一個 ACK 報文作為應答,且把服務端的序列號值 +1 作為自己 ACK 報文的序列號值,此時客戶端處于?TIME_WAIT?狀態。需要過一陣子以確保服務端收到自己的 ACK 報文之后才會進入 CLOSED 狀態,服務端收到 ACK 報文之后,就處于關閉連接了,處于?CLOSED狀態。
    即客戶端收到服務端的連接釋放報文段后,對此發出確認報文段(ACK=1,seq=u+1,ack=w+1),客戶端進入TIME_WAIT(時間等待)狀態。此時TCP未釋放掉,需要經過時間等待計時器設置的時間2MSL后,客戶端才進入CLOSED狀態。

收到一個FIN只意味著在這一方向上沒有數據流動。客戶端執行主動關閉并進入TIME_WAIT是正常的,服務端通常執行被動關閉,不會進入TIME_WAIT狀態。

在socket編程中,任何一方執行close()操作即可產生揮手操作。
image.png

2.1 揮手為什么需要四次?

因為當服務端收到客戶端的SYN連接請求報文后,可以直接發送SYN+ACK報文。其中ACK報文是用來應答的,SYN報文是用來同步的。但是關閉連接時,當服務端收到FIN報文時,很可能并不會立即關閉SOCKET,所以只能先回復一個ACK報文,告訴客戶端,“你發的FIN報文我收到了”。只有等到我服務端所有的報文都發送完了,我才能發送FIN報文,因此不能一起發送。故需要四次揮手。

2.2 2MSL等待狀態

TIME_WAIT狀態也成為2MSL等待狀態。每個具體TCP實現必須選擇一個報文段最大生存時間MSL(Maximum Segment Lifetime),它是任何報文段被丟棄前在網絡內的最長時間。這個時間是有限的,因為TCP報文段以IP數據報在網絡內傳輸,而IP數據報則有限制其生存時間的TTL字段。

對一個具體實現所給定的MSL值,處理的原則是:當TCP執行一個主動關閉,并發回最后一個ACK,該連接必須在TIME_WAIT狀態停留的時間為2倍的MSL。這樣可讓TCP再次發送最后的ACK以防這個ACK丟失(另一端超時并重發最后的FIN)。

這種2MSL等待的另一個結果是這個TCP連接在2MSL等待期間,定義這個連接的插口(客戶的IP地址和端口號,服務器的IP地址和端口號)不能再被使用。這個連接只能在2MSL結束后才能再被使用。

2.3 四次揮手釋放連接時,等待2MSL的意義?

MSL是Maximum Segment Lifetime的英文縮寫,可譯為“最長報文段壽命”,它是任何報文在網絡上存在的最長時間,超過這個時間報文將被丟棄。

為了保證客戶端發送的最后一個ACK報文段能夠到達服務器。因為這個ACK有可能丟失,從而導致處在LAST-ACK狀態的服務器收不到對FIN-ACK的確認報文。服務器會超時重傳這個FIN-ACK,接著客戶端再重傳一次確認,重新啟動時間等待計時器。最后客戶端和服務器都能正常的關閉。假設客戶端不等待2MSL,而是在發送完ACK之后直接釋放關閉,一但這個ACK丟失的話,服務器就無法正常的進入關閉連接狀態。

兩個理由:

  1. 保證客戶端發送的最后一個ACK報文段能夠到達服務端

    這個ACK報文段有可能丟失,使得處于LAST-ACK狀態的B收不到對已發送的FIN+ACK報文段的確認,服務端超時重傳FIN+ACK報文段,而客戶端能在2MSL時間內收到這個重傳的FIN+ACK報文段,接著客戶端重傳一次確認,重新啟動2MSL計時器,最后客戶端和服務端都進入到CLOSED狀態,若客戶端在TIME-WAIT狀態不等待一段時間,而是發送完ACK報文段后立即釋放連接,則無法收到服務端重傳的FIN+ACK報文段,所以不會再發送一次確認報文段,則服務端無法正常進入到CLOSED狀態。

  2. 防止“已失效的連接請求報文段”出現在本連接中

    客戶端在發送完最后一個ACK報文段后,再經過2MSL,就可以使本連接持續的時間內所產生的所有報文段都從網絡中消失,使下一個新的連接中不會出現這種舊的連接請求報文段。

2.4 為什么TIME_WAIT狀態需要經過2MSL才能返回到CLOSE狀態?

理論上,四個報文都發送完畢,就可以直接進入CLOSE狀態了,但是可能網絡是不可靠的,有可能最后一個ACK丟失。所以TIME_WAIT狀態就是用來重發可能丟失的ACK報文

3. 總結

《TCP/IP詳解 卷1:協議》有一張TCP狀態變遷圖,很具有代表性,有助于大家理解三次握手和四次揮手的狀態變化。如下圖所示,粗的實線箭頭表示正常的客戶端狀態變遷,粗的虛線箭頭表示正常的服務器狀態變遷。

TCP狀態變遷圖.jpg

?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:
http://www.pswp.cn/news/247366.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/247366.shtml
英文地址,請注明出處:http://en.pswp.cn/news/247366.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Apache ServiceComb

Apache ServiceComb 開源,全棧微服務解決方案。開箱即用,高性能,兼容流行的生態,多語言支持 ServiceComb是一個微服務框架,提供服務注冊,發現,配置和管理實用程序。 下載 :http://se…

VScode PowerShell運行腳本報錯禁止運行腳本解決方式圖文

今天在新Windows電腦上用VScode的終端PowerShell運行一個腳本的時候, 錯誤 在vscode終端運行vue -V查看版本失敗 PS C:\Users\11388> vue -V vue : 無法加載文件 E:\NodeJs\node_global\vue.ps1,因為在此系統上禁止運行腳本。有關詳細信息&#xf…

多線程的創建方式---繼承Thread和實現Runnable

繼承Thread類創建多線程 1 package cn.ftf.thread;2 /**3 * 多線程實現方式一 繼承Thread實現多線程,繼承Thread,重寫run方法4 * author 房廷飛5 *6 */7 public class StartThread extends Thread{ //對象繼承Thread8 public static void mai…

添加右鍵用Sublime Text3 打開文件和文件夾

最近重新裝了一下系統,右鍵沒有用Sublime Text 3打開的選項了,于是查了一下解決方案 1、環境 Win10和Win7都可以Sublime Text 3最新版本以下為Win10系統下截圖 2、添加右鍵打開文件 Win R,輸入regedit,打開注冊表 找到HKEY_CLASSESS_ROOT…

Windows Mobile Widget Emulator

今天Vimpyboy 在codeplex發布了Windows Mobile Widget Emulator。這是一個用來調試Windows Mobile 6.5 Widget的工具,我在做Windows Mobile 6.5 新功能widget開發 的時候就發現調試Widget很麻煩。也有想法做一個Emulator,其實這個Emulator目標很明顯&…

JS 常用字符串數組遍歷函數方法整理

目錄 一、concat() 二、join() 三、push() 四、pop() 五、shift() 六、unshift() 七、slice() 九、substring() 和 substr() 十、sort 排序 十一、reverse() 十二、indexOf 和 lastIndexOf 十三、every 十四、some 十五、filter 十六、map ES6新增新操作數組的…

AttributeError: Can only use .str accessor with string values, which use np.object_ dtype in pandas

忘記網址了…… 問題: 分析思路與解決方法: 轉載于:https://www.cnblogs.com/bravesunforever/p/11247988.html

vue app掃PC端二維碼登錄

通過接口獲取二維碼唯一標識,例如:qrcodeId通過 qrcodejs2插件生成 二維碼(二維碼內容就是 qrcodeId,具體根據APP 需要)循環調用接口,查看掃碼狀態(app是否掃碼確認登錄) //下載插件…

第八章 方法

1. 概述 本章重點講述類型中的各種方法,包括實例構造器、類型構造器、操作符/類型轉換重載、擴展方法、分部方法。 2. 名詞解釋 ① 構造器:是允許將類型的實例初始化為良好狀態的一種特殊方法。 3. 主要內容 3.1 實例構造器和類(引用類型) ① 創建一個引…

Java生鮮電商平臺-促銷架構以及秒殺解決方案實戰

Java生鮮電商平臺-促銷架構以及秒殺解決方案實戰 背景:隨著這幾年的電商的大熱,我們經常看到一些商家為了促銷和快速收益,紛紛推出了秒殺活動.不管是日常的超市里面的促銷,明星演唱會門票售賣,還是春節訂閱火車票,等等我們都能看到秒殺活動的影子. 1. 構建秒殺活動架構 1.1 說明…

PHP---錯誤處理(error)

錯誤的級別 1. notice:提示2. warning:警告3. error:致命錯誤12345 注:notice和warning報錯后繼續執行,error報錯后停止 錯誤的提示方法 方法一:顯示在瀏覽器上 方法二:記錄在日志中執行 錯…

對url給后臺傳數據的時候特殊字符需要轉義

URL中的字符只能是ASCII字符,但是ASCII字符比較少,而URL則常常包含ASCII字符集以外的字符,如非英語字符,漢字,特殊符號等等,所以要對URL進行轉換。這個過程就叫做URL編碼,或者叫URL轉義&#xf…

PHP Cookie處理

Cookie 是什么? cookie是保存在客戶端的信息包(一個文件) cookie 常用于識別用戶。 cookie 是一種服務器留在用戶計算機上的小文件。每當同一臺計算機通過瀏覽器請求頁面時,這臺計算機將會發送 cookie。通過 PHP,您能…

python裝飾器補充

帶參裝飾器 msg """ 1.QQ 2.wechat """ avg input(驗證方式:)def auth(avg):def wrapper(f):def inner(*args,**kwargs):if avg QQ:user input(name)pwd input(password)if user alex and pwd 123456:f()else:print(輸入錯誤)…

PHP-連接數據庫

1.2 連接數據庫 通過PHP做MySQL的客戶端 1.2.1 開啟mysqli擴展 在php.ini中開啟mysqli擴展 extensionphp_mysqli.dll開啟擴展后重啟服務器,就可以使用mysqli_函數了,1.2.2 連接數據庫 創建news數據庫 -- 創建表 drop table if exists news; create …

python模塊初始與time、datetime及random

模塊初始與time、datetime及random 模塊初始 模塊的概念(本質為一個py文件) python模塊可以將代碼量較大的程序分割成多個有組織的、彼此獨立但又能互相交互的代碼片段,這些自我包含的有組織 的代碼段就是模塊,模塊在物理形式上表…

PHP-面向對象編程教程

1.2 面向對象介紹 1.2.1 介紹 面向對象是一個編程思想。編程思想有面向過程和面向對象 面向過程:編程思路集中的是過程上 面向對象:編程思路集中在參與的對象 以去飯館吃飯為例: ? 面向過程:點菜——做菜——上菜——吃飯—…

vue-property-decorator使用指南

在Vue中使用TypeScript時,非常好用的一個庫,使用裝飾器來簡化書寫。 一、安裝 npm i -S vue-property-decorator PropPropSyncProvideModelWatchInjectProvideEmitComponent (provided by vue-class-component)Mixins (the helper function named mix…

Java生鮮電商平臺-統一異常處理及架構實戰

Java生鮮電商平臺-統一異常處理及架構實戰 補充說明:本文講得比較細,所以篇幅較長。 請認真讀完,希望讀完后能對統一異常處理有一個清晰的認識。 背景 軟件開發過程中,不可避免的是需要處理各種異常,就我自己來說&…

VScode新建自定義模板快捷方式

VS新建vue文件的自定義模板 在使用vscode開發的時候,新建vue文件是不可或缺的,但是VSCode并沒有vue文件的初始化模板,這個需要自定義模板。 我們可以使用vscode的snippets在新建.vue 文件后輕松獲得一套模板。 具體步驟 打開VSCode -> …