三次握手和四次揮手是各個公司常見的考點,也具備必定的水平區分度,也被一些面試官做爲熱身題。不少小夥伴說這個問題剛開始回答的挺好,可是後面越回答越冒冷汗,最後就歇菜了。面試
見過比較典型的面試場景是這樣的:編程
面試官:請介紹下三次握手
求職者:第一次握手就是客戶端給服務器端發送一個報文,第二次就是服務器收到報文以後,會應答一個報文給客戶端,第三次握手就是客戶端收到報文後再給服務器發送一個報文,三次握手就成功了。
面試官:而後呢?
求職者:這就是三次握手的過程,很簡單的。
面試官:。。。。。。
(番外篇:一首涼涼送給你)服務器
記住猿人谷一句話:面試時越簡單的問題,通常就是隱藏着比較大的坑,通常都是須要將問題擴展的。上面求職者的回答不對嗎?固然對,但距離面試官的指望可能還有點距離。cookie
但願你們能帶着以下問題進行閱讀,收穫會更大。網絡
三次握手(Three-way Handshake)其實就是指創建一個TCP鏈接時,須要客戶端和服務器總共發送3個包。進行三次握手的主要做用就是爲了確認雙方的接收能力和發送能力是否正常、指定本身的初始化序列號爲後面的可靠性傳送作準備。實質上其實就是鏈接服務器指定端口,創建TCP鏈接,並同步鏈接雙方的序列號和確認號,交換TCP窗口大小
信息。併發
剛開始客戶端處於 Closed 的狀態,服務端處於 Listen 狀態。
進行三次握手:socket
第一次握手:客戶端給服務端發一個 SYN 報文,並指明客戶端的初始化序列號 ISN(c)。此時客戶端處於 SYN_SEND
狀態。3d
第二次握手:服務器收到客戶端的 SYN 報文以後,會以本身的 SYN 報文做爲應答,而且也是指定了本身的初始化序列號 ISN(s)。同時會把客戶端的 ISN + 1 做爲ACK 的值,表示本身已經收到了客戶端的 SYN,此時服務器處於 SYN_REVD
的狀態。code
第三次握手:客戶端收到 SYN 報文以後,會發送一個 ACK 報文,固然,也是同樣把服務器的 ISN + 1 做爲 ACK 的值,表示已經收到了服務端的 SYN 報文,此時客戶端處於 ESTABLISHED
狀態。服務器收到 ACK 報文以後,也處於 ESTABLISHED
狀態,此時,雙方已創建起了鏈接。blog
確認報文段ACK=1,確認號ack=y+1,序號seq=x+1(初始爲seq=x,第二個報文段因此要+1),ACK報文段能夠攜帶數據,不攜帶數據則不消耗序號。
發送第一個SYN的一端將執行主動打開(active open),接收這個SYN併發回下一個SYN的另外一端執行被動打開(passive open)。
在socket編程中,客戶端執行connect()時,將觸發三次握手。
弄清這個問題,咱們須要先弄明白三次握手的目的是什麼,能不能只用兩次握手來達到一樣的目的。
所以,須要三次握手才能確認雙方的接收與發送能力是否正常。
試想若是是用兩次握手,則會出現下面這種狀況:
如客戶端發出鏈接請求,但因鏈接請求報文丟失而未收到確認,因而客戶端再重傳一次鏈接請求。後來收到了確認,創建了鏈接。數據傳輸完畢後,就釋放了鏈接,客戶端共發出了兩個鏈接請求報文段,其中第一個丟失,第二個到達了服務端,可是第一個丟失的報文段只是在某些網絡結點長時間滯留了,延誤到鏈接釋放之後的某個時間纔到達服務端,此時服務端誤認爲客戶端又發出一次新的鏈接請求,因而就向客戶端發出確認報文段,贊成創建鏈接,不採用三次握手,只要服務端發出確認,就創建新的鏈接了,此時客戶端忽略服務端發來的確認,也不發送數據,則服務端一致等待客戶端發送數據,浪費資源。
服務器第一次收到客戶端的 SYN 以後,就會處於 SYN_RCVD 狀態,此時雙方尚未徹底創建其鏈接,服務器會把此種狀態下請求鏈接放在一個隊列裏,咱們把這種隊列稱之爲半鏈接隊列。
固然還有一個全鏈接隊列,就是已經完成三次握手,創建起鏈接的就會放在全鏈接隊列中。若是隊列滿了就有可能會出現丟包現象。
這裏在補充一點關於SYN-ACK 重傳次數的問題:
服務器發送完SYN-ACK包,若是未收到客戶確認包,服務器進行首次重傳,等待一段時間仍未收到客戶確認包,進行第二次重傳。若是重傳次數超過系統規定的最大重傳次數,系統將該鏈接信息從半鏈接隊列中刪除。
注意,每次重傳等待的時間不必定相同,通常會是指數增加,例如間隔時間爲 1s,2s,4s,8s......
當一端爲創建鏈接而發送它的SYN時,它爲鏈接選擇一個初始序號。ISN隨時間而變化,所以每一個鏈接都將具備不一樣的ISN。ISN能夠看做是一個32比特的計數器,每4ms加1 。這樣選擇序號的目的在於防止在網絡中被延遲的分組在之後又被傳送,而致使某個鏈接的一方對它作錯誤的解釋。
三次握手的其中一個重要功能是客戶端和服務端交換 ISN(Initial Sequence Number),以便讓對方知道接下來接收數據的時候如何按序列號組裝數據。若是 ISN 是固定的,攻擊者很容易猜出後續的確認號,所以 ISN 是動態生成的。
其實第三次握手的時候,是能夠攜帶數據的。可是,第一次、第二次握手不能夠攜帶數據
爲何這樣呢?你們能夠想一個問題,假如第一次握手能夠攜帶數據的話,若是有人要惡意攻擊服務器,那他每次都在第一次握手中的 SYN 報文中放入大量的數據。由於攻擊者根本就不理服務器的接收、發送能力是否正常,而後瘋狂着重複發 SYN 報文的話,這會讓服務器花費不少時間、內存空間來接收這些報文。
也就是說,第一次握手不能夠放數據,其中一個簡單的緣由就是會讓服務器更加容易受到攻擊了。而對於第三次的話,此時客戶端已經處於 ESTABLISHED 狀態。對於客戶端來講,他已經創建起鏈接了,而且也已經知道服務器的接收、發送能力是正常的了,因此能攜帶數據也沒啥毛病。
服務器端的資源分配是在二次握手時分配的,而客戶端的資源是在完成三次握手時分配的,因此服務器容易受到SYN洪泛攻擊。SYN攻擊就是Client在短期內僞造大量不存在的IP地址,並向Server不斷地發送SYN包,Server則回覆確認包,並等待Client確認,因爲源地址不存在,所以Server須要不斷重發直至超時,這些僞造的SYN包將長時間佔用未鏈接隊列,致使正常的SYN請求由於隊列滿而被丟棄,從而引發網絡擁塞甚至系統癱瘓。SYN 攻擊是一種典型的 DoS/DDoS 攻擊。
檢測 SYN 攻擊很是的方便,當你在服務器上看到大量的半鏈接狀態時,特別是源IP地址是隨機的,基本上能夠判定這是一次SYN攻擊。在 Linux/Unix 上可使用系統自帶的 netstats 命令來檢測 SYN 攻擊。
netstat -n -p TCP | grep SYN_RECV
常見的防護 SYN 攻擊的方法有以下幾種:
創建一個鏈接須要三次握手,而終止一個鏈接要通過四次揮手(也有將四次揮手叫作四次握手的)。這由TCP的半關閉(half-close)形成的。所謂的半關閉,其實就是TCP提供了鏈接的一端在結束它的發送後還能接收來自另外一端數據的能力。
TCP 的鏈接的拆除須要發送四個包,所以稱爲四次揮手(Four-way handshake),客戶端或服務器都可主動發起揮手動做。
剛開始雙方都處於 ESTABLISHED 狀態,假如是客戶端先發起關閉請求。四次揮手的過程以下:
FIN_WAIT1
狀態。CLOSE_WAIT
狀態。LAST_ACK
的狀態。TIME_WAIT
狀態。須要過一陣子以確保服務端收到本身的 ACK 報文以後纔會進入 CLOSED 狀態,服務端收到 ACK 報文以後,就處於關閉鏈接了,處於 CLOSED
狀態。收到一個FIN只意味着在這一方向上沒有數據流動。客戶端執行主動關閉並進入TIME_WAIT是正常的,服務端一般執行被動關閉,不會進入TIME_WAIT狀態。
在socket編程中,任何一方執行close()操做便可產生揮手操做。
由於當服務端收到客戶端的SYN鏈接請求報文後,能夠直接發送SYN+ACK報文。其中ACK報文是用來應答的,SYN報文是用來同步的。可是關閉鏈接時,當服務端收到FIN報文時,極可能並不會當即關閉SOCKET,因此只能先回復一個ACK報文,告訴客戶端,"你發的FIN報文我收到了"。只有等到我服務端全部的報文都發送完了,我才能發送FIN報文,所以不能一塊兒發送。故須要四次揮手。
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結束後才能再被使用。
MSL是Maximum Segment Lifetime的英文縮寫,可譯爲「最長報文段壽命」,它是任何報文在網絡上存在的最長時間,超過這個時間報文將被丟棄。
爲了保證客戶端發送的最後一個ACK報文段可以到達服務器。由於這個ACK有可能丟失,從而致使處在LAST-ACK狀態的服務器收不到對FIN-ACK的確認報文。服務器會超時重傳這個FIN-ACK,接着客戶端再重傳一次確認,從新啓動時間等待計時器。最後客戶端和服務器都能正常的關閉。假設客戶端不等待2MSL,而是在發送完ACK以後直接釋放關閉,一但這個ACK丟失的話,服務器就沒法正常的進入關閉鏈接狀態。
理論上,四個報文都發送完畢,就能夠直接進入CLOSE狀態了,可是可能網絡是不可靠的,有可能最後一個ACK丟失。因此TIME_WAIT狀態就是用來重發可能丟失的ACK報文。
《TCP/IP詳解 卷1:協議》有一張TCP狀態變遷圖,很具備表明性,有助於你們理解三次握手和四次揮手的狀態變化。以下圖所示,粗的實線箭頭表示正常的客戶端狀態變遷,粗的虛線箭頭表示正常的服務器狀態變遷。
之後面試官再問你三次握手和四次揮手,直接把這一篇文章丟給他就能夠了,他想問的都在這裏。
參考:《TCP/IP詳解 卷1:協議》