TCP的整個連接過程
如果沒有基礎的話,直接看這張圖或者網絡上各種文字描述,十分生澀,所以先看懂接下來的握手揮手的圖,理解之后,再看這個有限狀態機就感覺原來如此簡單。
三次握手
握手過程
第一次握手:主機A發送位碼為syn=1,隨機產生seq number=x的數據包到服務器,客戶端進入SYN_SEND狀態,等待服務器的確認;主機B由SYN=1知道A要求建立連接。
第二次握手:主機B收到請求后要確認連接信息,向A發送ack number(主機A的seq+1)、syn=1、ack=1,隨機產生seq=y的包,此時服務器進入SYN_RECV狀態。
第三次握手:主機A收到后檢查ack number是否正確,即第一次發送的seq number+1,以及位碼ack是否為1,若正確,主機A會再發送ack number(主機B的seq+1)、ack=1,主機B收到后確認seq值與ack=1則連接建立成功。客戶端和服務器端都進入ESTABLISHED狀態。
三次握手的必要性
- 第一次握手:客戶端發送網絡包,服務端收到了。這樣服務端就能得出結論:客戶端的發送能力、服務端的接收能力是正常的。
- 第二次握手:服務端發包,客戶端收到了。這樣客戶端就能得出結論:服務端的接收、發送能力,客戶端的接收、發送能力是正常的。不過此時服務器並不能確認客戶端的接收能力是否正常。
- 第三次握手:客戶端發包,服務端收到了。這樣服務端就能得出結論:客戶端的接收能力,服務器自己的發送能力也正常。
至此,客戶端和服務端可以確定雙方的接收和發送能力均正常。
第三次握手的必要性
這主要是為了防止已失效的連接請求報文段突然又傳送到了服務器端,從而減少服務端的開銷。
如果只有兩次握手就建立連接會出現這種情況:客戶端發出的連接請求報文段在某些網絡節點長時間滯留了,以致延誤到連接釋放以后的某個時間才能到達服務端。本來這是一個早已失效的報文段,但服務端收到此失效的連接請求報文段后,就誤認為客戶端又發出了一次新的連接請求。於是向客戶端發出確認報文段,同意建立連接。由於現在客戶端並沒有發出建立連接的請求,因此不會處理服務端的確認,也不會向服務端發送數據。但服務端卻以為新的連接已經建立了,並一直等待客戶端發來數據。 服務端會因此浪費很多了。
如果第三次握手丟失了,客戶端服務端會如何處理?
服務端:
該TCP連接的狀態為SYN_RECV,並且會根據TCP的超時重傳機制,會等待3秒、6秒、12秒后重新發送SYN+ACK包,以便Client重新發送ACK包。而Server重發SYN+ACK包的次數,可以通過設置/proc/sys/net/ipv4/tcp_synack_retries修改,默認值為5。如果重發指定次數之后,仍然未收到客戶端的ACK應答,那么一段時間后,服務端自動關閉這個連接。
客戶端:
客戶端在接收到SYN+ACK包,它的TCP連接狀態就為ESTABLISHED(已連接),表示該連接已經建立。那么如果第三次握手中的ACK包丟失的情況下,客戶端向服務端發送數據,服務端將以RST包(reset重置)響應,才能感知到服務端的錯誤。
什么是syn flood攻擊
syn flood是一種經典的ddos攻擊手段,這里面用到了TCP三次握手存在的漏洞。當服務端接收到SYN后進入SYN-RECV狀態,此時的連接稱為半連接,同時會被服務端寫入一個半連接隊列。如果攻擊者在短時間內不斷的向服務端發送大量的SYN包而不響應,那么服務器的半連接隊列很快會被寫滿,從而導致無法工作。 實現syn flood 的手段,可以通過偽造源IP的方式,這樣服務器的響應就永遠到達不了客戶端(握手無法完成);當然,通過設定客戶端防火牆規則也可以達到同樣的目的。對syn flood實現攔截是比較困難的,可以通過啟用 syn_cookies 的方式實現緩解,但這通常不是最佳方案。最好的辦法是通過專業的防火牆來解決,基本上所有的雲計算大 都具備這個能力。
四次揮手
揮手過程
第一次揮手:主機A(可以是客戶端,也可以是服務器端),設置Sequence Number和Acknowledgment Number,向主機B發送一個FIN報文段;此時,主機A進入FIN_WAIT_1狀態;這表示主機A沒有數據要發送給主機B了。
第二次揮手:主機B收到了主機A發送的FIN報文段,向主機A回一個ACK報文段,Acknowledgment Number為Sequence Number加1,主機A進入FIN_WAIT_2狀態;主機B告訴主機A,我也沒有數據要發送了,可以進行關閉連接了。
第三次揮手:主機B向主機A發送FIN報文段,請求關閉連接,同時主機B進入CLOSE_WAIT狀態。
第四次揮手:主機A收到主機B發送的FIN報文段,向主機B發送ACK報文段,然后主機A進入TIME_WAIT狀態;主機B收到主機A的ACK報文段以后,就關閉連接;此時,主機A等待2MSL后依然沒有收到回復,則證明主機B已正常關閉,那好,主機A也可以關閉連接了。
主機B發送了FIN-ACK之后,會立即啟動超時重傳計時器
主機A在發送最后一個ACK之后,會立即啟動時間等待計時器
揮手為什么需要四次?
因為當服務端收到客戶端的SYN連接請求報文后,可以直接發送SYN+ACK報文。其中ACK報文是用來應答的,SYN報文是用來同步的。但是關閉連接時,當服務端收到FIN報文時,很可能並不會立即關閉SOCKET,所以只能先回復一個ACK報文,告訴客戶端,"你發的FIN報文我收到了"。只有等到我服務端所有的報文都發送完了,我才能發送FIN報文,因此不能一起發送。故需要四次揮手。
RST是什么,為什么會出現
RST 是一個特殊的標記,用來表示當前應該立即終止連接。以下這些情況都會產生RST:
- 向一個未被監聽的端口發送數據
- 對方已經調用 close 關閉連接
- 存在一些數據未處理(接收緩沖區),請求關閉連接時,會發送RST強制關閉
- 某些請求發生了超時
為什么服務器會有大量closewait
半關閉的狀態下的服務器連接會處於closewait狀態,直到服務器發送了FIN。 那么在應用層則是調用socket.close()會執行FIN的發送,如果服務器出現大量CLOSE_WAIT狀態的連接,那么有可能的原因:
- 服務器壓力過大,根本來不及調用close
- 存在連接泄露問題(Bug),服務器未及時關閉連接
四次揮手釋放連接時,等待2MSL的意義
為了保證客戶端發送的最后一個ACK報文段能夠到達服務器。因為這個ACK有可能丟失,從而導致處在LAST-ACK狀態的服務器收不到對FIN-ACK的確認報文。服務器會超時重傳這個FIN-ACK,接着客戶端再重傳一次確認,重新啟動時間等待計時器。最后客戶端和服務器都能正常的關閉。假設客戶端不等待2MSL,而是在發送完ACK之后直接釋放關閉,一但這個ACK丟失的話,服務器就無法正常的進入關閉連接狀態。
MSL是Maximum Segment Lifetime的英文縮寫,可譯為“最長報文段壽命”,它是任何報文在網絡上存在的最長時間,超過這個時間報文將被丟棄。我們都知道IP頭部中有個TTL字段,TTL是time to live的縮寫,可譯為“生存時間”,這個生存時間是由源主機設置初始值代表一個IP數據包可以經過的最大路由數,每經過一個路由器,它的值就減1,當此值為0則數據報被丟棄,同時發送ICMP報文通知源主機。RFC793中規定MSL為2分鍾,但這完全是從工程上來考慮,對於現在的網絡,常用值30秒或1分鍾。因此TCP允許不同的實現可根據具體情況使用更小的MSL值。
最后,限於筆者經驗水平有限,歡迎讀者就文中的觀點提出寶貴的建議和意見。如果想獲得更多的學習資源或者想和更多的是技術愛好者一起交流,可以關注我的公眾號『全菜工程師小輝』后台回復關鍵詞領取學習資料、進入前后端技術交流群和程序員副業群。同時也可以加入程序員副業群Q群:735764906 一起交流。