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