滴滴工程師帶你深入理解 TCP 握手分手全過程
阿新 • • 發佈:2019-02-05
after ets c51 proxy 還需要 2.3 afa raft 五個
TCP可以看成是一種字節流,它會處理IP層或以下的層的丟包、重復以及錯誤問題。在連接的建立過程中,雙方需要交換一些連接的參數。這些參數可以放在TCP頭部。
TCP提供了一種可靠、面向連接、字節流、傳輸層的服務,采用三次握手建立一個連接。采用4次揮手來關閉一個連接。
TCP服務模型
在了解了建立連接、關閉連接的“三次握手和四次揮手”後,我們再來看下TCP相關的東西。
一個TCP連接由一個4元組構成,分別是兩個IP地址和兩個端口號。一個TCP連接通常分為三個階段:啟動、數據傳輸、退出(關閉)。
當TCP接收到另一端的數據時,它會發送一個確認,但這個確認不會立即發送,一般會延遲一會兒。ACK是累積的,一個確認字節號N的ACK表示所有直到N的字節(不包括N)已經成功被接收了。這樣的好處是如果一個ACK丟失,很可能後續的ACK就足以確認前面的報文段了。
一個完整的TCP連接是雙向和對稱的,數據可以在兩個方向上平等地流動。給上層應用程序提供一種雙工服務。一旦建立了一個連接,這個連接的一個方向上的每個TCP報文段都包含了相反方向上的報文段的一個ACK。
序列號的作用是使得一個TCP接收端可丟棄重復的報文段,記錄以雜亂次序到達的報文段。因為TCP使用IP來傳輸報文段,而IP不提供重復消除或者保證次序正確的功能。另一方面,TCP是一個字節流協議,絕不會以雜亂的次序給上層程序發送數據。因此TCP接收端會被迫先保持大序列號的數據不交給應用程序,直到缺失的小序列號的報文段被填滿。
TCP頭部
源端口和目的端口在TCP層確定雙方進程,序列號表示的是報文段數據中的第一個字節號,ACK表示確認號,該確認號的發送方期待接收的下一個序列號,即最後被成功接收的數據字節序列號加1,這個字段只有在ACK位被啟用的時候才有效。
當新建一個連接時,從客戶端發送到服務端的第一個報文段的SYN位被啟用,這稱為SYN報文段,這時序列號字段包含了在本次連接的這個方向上要使用的第一個序列號,即初始序列號ISN,之後發送的數據是ISN加1,因此SYN位字段會消耗一個序列號,這意味著使用重傳進行可靠傳輸。而不消耗序列號的ACK則不是。
頭部長度(圖中的數據偏移)以32位字為單位,也就是以4bytes為單位,它只有4位,最大為15,因此頭部最大長度為60字節,而其最小為5,也就是頭部最小為20字節(可變選項為空)。
ACK —— 確認,使得確認號有效。 RST —— 重置連接(經常看到的reset by peer)就是此字段搞的鬼。 SYN —— 用於初如化一個連接的序列號。 FIN —— 該報文段的發送方已經結束向對方發送數據。
當一個連接被建立或被終止時,交換的報文段只包含TCP頭部,而沒有數據。
狀態轉換
三次握手和四次揮手的狀態轉換如下圖。
為什麽要“三次握手,四次揮手”
三次握手
換個易於理解的視角來看為什麽要3次握手。
客戶端和服務端通信前要進行連接,“3次握手”的作用就是雙方都能明確自己和對方的收、發能力是正常的。
第一次握手:客戶端發送網絡包,服務端收到了。這樣服務端就能得出結論:客戶端的發送能力、服務端的接收能力是正常的。
第二次握手:服務端發包,客戶端收到了。這樣客戶端就能得出結論:服務端的接收、發送能力,客戶端的接收、發送能力是正常的。 從客戶端的視角來看,我接到了服務端發送過來的響應數據包,說明服務端接收到了我在第一次握手時發送的網絡包,並且成功發送了響應數據包,這就說明,服務端的接收、發送能力正常。而另一方面,我收到了服務端的響應數據包,說明我第一次發送的網絡包成功到達服務端,這樣,我自己的發送和接收能力也是正常的。
第三次握手:客戶端發包,服務端收到了。這樣服務端就能得出結論:客戶端的接收、發送能力,服務端的發送、接收能力是正常的。 第一、二次握手後,服務端並不知道客戶端的接收能力以及自己的發送能力是否正常。而在第三次握手時,服務端收到了客戶端對第二次握手作的回應。從服務端的角度,我在第二次握手時的響應數據發送出去了,客戶端接收到了。所以,我的發送能力是正常的。而客戶端的接收能力也是正常的。
經歷了上面的三次握手過程,客戶端和服務端都確認了自己的接收、發送能力是正常的。之後就可以正常通信了。
每次都是接收到數據包的一方可以得到一些結論,發送的一方其實沒有任何頭緒。我雖然有發包的動作,但是我怎麽知道我有沒有發出去,而對方有沒有接收到呢?
而從上面的過程可以看到,最少是需要三次握手過程的。兩次達不到讓雙方都得出自己、對方的接收、發送能力都正常的結論。其實每次收到網絡包的一方至少是可以得到:對方的發送、我方的接收是正常的。而每一步都是有關聯的,下一次的“響應”是由於第一次的“請求”觸發,因此每次握手其實是可以得到額外的結論的。比如第三次握手時,服務端收到數據包,表明看服務端只能得到客戶端的發送能力、服務端的接收能力是正常的,但是結合第二次,說明服務端在第二次發送的響應包,客戶端接收到了,並且作出了響應,從而得到額外的結論:客戶端的接收、服務端的發送是正常的。
用表格總結一下:
四次揮手
TCP連接是雙向傳輸的對等的模式,就是說雙方都可以同時向對方發送或接收數據。當有一方要關閉連接時,會發送指令告知對方,我要關閉連接了。這時對方會回一個ACK,此時一個方向的連接關閉。但是另一個方向仍然可以繼續傳輸數據,等到發送完了所有的數據後,會發送一個FIN段來關閉此方向上的連接。接收方發送ACK確認關閉連接。註意,接收到FIN報文的一方只能回復一個ACK, 它是無法馬上返回對方一個FIN報文段的,因為結束數據傳輸的“指令”是上層應用層給出的,我只是一個“搬運工”,我無法了解“上層的意誌”。
“三次握手,四次揮手”怎麽完成?
其實3次握手的目的並不只是讓通信雙方都了解到一個連接正在建立,還在於利用數據包的選項來傳輸特殊的信息,交換初始序列號ISN。
3次握手是指發送了3個報文段,4次揮手是指發送了4個報文段。註意,SYN和FIN段都是會利用重傳進行可靠傳輸的。
三次握手
本文作者:饒全成,中科院計算所碩士,滴滴出行後端研發工程師。 個人主頁:https://zhihu.com/people/raoquancheng 記得剛畢業找工作面試的時候,經常會被問到:你知道“3次握手,4次揮手”嗎?這時候我會“胸有成竹”地“背誦”前期準備好的“答案”,第一次怎麽怎麽,第二次……答完就沒有下文了,面試官貌似也沒有深入下去的意思,深入下去我也不懂,皆大歡喜! 作為程序員,要有“刨根問底”的精神。知其然,更要知其所以然。這篇文章希望能抽絲剝繭,還原背後的原理。 什麽是“3次握手,4次揮手” TCP是一種面向連接的單播協議,在發送數據前,通信雙方必須在彼此間建立一條連接。所謂的“連接”,其實是客戶端和服務器的內存裏保存的一份關於對方的信息,如ip地址、端口號等。
- 客戶端發送一個SYN段,並指明客戶端的初始序列號,即ISN(c).
- 服務端發送自己的SYN段作為應答,同樣指明自己的ISN(s)。為了確認客戶端的SYN,將ISN(c)+1作為ACK數值。這樣,每發送一個SYN,序列號就會加1. 如果有丟失的情況,則會重傳。
- 為了確認服務器端的SYN,客戶端將ISN(s)+1作為返回的ACK數值。
目前,Linux下默認會進行5次重發SYN-ACK包,重試的間隔時間從1s開始,下次的重試間隔時間是前一次的雙倍,5次的重試時間間隔為1s, 2s, 4s, 8s, 16s, 總共31s, 稱為指數退避,第5次發出後還要等32s才知道第5次也超時了,所以,總共需要 1s + 2s + 4s+ 8s+ 16s + 32s = 63s, TCP才會把斷開這個連接。由於,SYN超時需要63秒,那麽就給攻擊者一個攻擊服務器的機會,攻擊者在短時間內發送大量的SYN包給Server(俗稱SYN flood攻擊),用於耗盡Server的SYN隊列。對於應對SYN 過多的問題,linux提供了幾個TCP參數:tcp_syncookies、tcp_synack_retries、tcp_max_syn_backlog、tcp_abort_on_overflow 來調整應對。全連接隊列滿 當第三次握手時,當server接收到ACK包之後,會進入一個新的叫 accept 的隊列。 當accept隊列滿了之後,即使client繼續向server發送ACK的包,也會不被響應,此時ListenOverflows+1,同時server通過tcp_abort_on_overflow來決定如何返回,0表示直接丟棄該ACK,1表示發送RST通知client;相應的,client則會分別返回read timeout 或者 connection reset by peer。另外,tcp_abort_on_overflow是0的話,server過一段時間再次發送syn+ack給client(也就是重新走握手的第二步),如果client超時等待比較短,就很容易異常了。而客戶端收到多個 SYN ACK 包,則會認為之前的 ACK 丟包了。於是促使客戶端再次發送 ACK ,在 accept隊列有空閑的時候最終完成連接。若 accept隊列始終滿員,則最終客戶端收到 RST 包(此時服務端發送syn+ack的次數超出了tcp_synack_retries)。 服務端僅僅只是創建一個定時器,以固定間隔重傳syn和ack到服務端 命令 netstat -s命令
[root@server ~]# netstat -s | egrep "listen|LISTEN" 667399 times the listen queue of a socket overflowed 667399 SYNs to LISTEN sockets ignored 比如上面看到的 667399 times ,表示全連接隊列溢出的次數,隔幾秒鐘執行下,如果這個數字一直在增加的話肯定全連接隊列偶爾滿了。 [root@server ~]# netstat -s | grep TCPBacklogDrop 查看 Accept queue 是否有溢出ss命令
[root@server ~]# ss -lnt State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 :6379 : LISTEN 0 128 :22 : 如果State是listen狀態,Send-Q 表示第三列的listen端口上的全連接隊列最大為50,第一列Recv-Q為全連接隊列當前使用了多少。 非 LISTEN 狀態中 Recv-Q 表示 receive queue 中的 bytes 數量;Send-Q 表示 send queue 中的 bytes 數值。小結 當外部連接請求到來時,TCP模塊會首先查看max_syn_backlog,如果處於SYN_RCVD狀態的連接數目超過這一閾值,進入的連接會被拒絕。根據tcp_abort_on_overflow字段來決定是直接丟棄,還是直接reset. 從服務端來說,三次握手中,第一步server接受到client的syn後,把相關信息放到半連接隊列中,同時回復syn+ack給client. 第三步當收到客戶端的ack, 將連接加入到全連接隊列。 一般,全連接隊列比較小,會先滿,此時半連接隊列還沒滿。如果這時收到syn報文,則會進入半連接隊列,沒有問題。但是如果收到了三次握手中的第3步(ACK),則會根據tcp_abort_on_overflow字段來決定是直接丟棄,還是直接reset.此時,客戶端發送了ACK, 那麽客戶端認為三次握手完成,它認為服務端已經準備好了接收數據的準備。但此時服務端可能因為全連接隊列滿了而無法將連接放入,會重新發送第2步的syn+ack, 如果這時有數據到來,服務器TCP模塊會將數據存入隊列中。一段時間後,client端沒收到回復,超時,連接異常,client會主動關閉連接。 “三次握手,四次揮手”redis實例分析
- 我在dev機器上部署redis服務,端口號為6379,
- 通過tcpdump工具獲取數據包,使用如下命令
- 在dev2機器上用redis-cli訪問dev:6379, 發送一個ping, 得到回復pong
- 停止抓包,用tcpdump讀取捕獲到的數據包
滴滴工程師帶你深入理解 TCP 握手分手全過程