TCP三次握手和四次揮手不論是在開發仍是面試中都是一個很是重要的知識點,它是咱們優化web程序性能的基礎。可是大部分教材都對這部分解釋的比較抽象,本文咱們就利用wireshark來抓包以真正體會整個流程的細節。html
根據下面這幅圖咱們來看一下TCP三次握手。p.s: 每一個箭頭表明一次握手。web
client發送一個SYN(J)包給server,而後等待server的ACK回覆,進入SYN-SENT
狀態。p.s: SYN爲synchronize的縮寫,ACK爲acknowledgment的縮寫。面試
server接收到SYN(seq=J)包後就返回一個ACK(J+1)包以及一個本身的**SYN(K)**包,而後等待client的ACK回覆,server進入SYN-RECIVED
狀態。算法
client接收到server發回的ACK(J+1)包後,進入ESTABLISHED
狀態。而後根據server發來的SYN(K)包,返回給等待中的server一個ACK(K+1)包。等待中的server收到ACK回覆,也把本身的狀態設置爲ESTABLISHED
。到此TCP三次握手完成,client與server能夠正常進行通訊了。shell
咱們來看一下爲何須要進行三次握手,兩次握手難道不行麼?這裏咱們用一個生活中的具體例子來解釋就很好理解了。咱們能夠將三次握手中的客戶端和服務器之間的握手過程比喻成A和B通訊的過程:緩存
上面分析還不夠形象,很容易忘記,下面咱們利用wireshark來證實一下上面的分析過程。從下面的的輸出就能夠很容易看出來,必需要通過前面的三次tcp請求才會有起一次http請求。服務器
第一次請求客戶端發送一個SYN包,序列號是0。tcp
第二次請求服務器會發送一個SYN和一個ACK包,序列號是0,ack號是1。ide
第三次本地客戶端請求會發送一個ACK包,序列號是1,ack號是1來回復服務器。性能
如下面這張圖爲例,咱們來分析一下TCP四次揮手的過程。
client發送一個FIN(M)包,此時client進入FIN-WAIT-1
狀態,這代表client已經沒有數據要發送了。
server收到了client發來的FIN(M)包後,向client發回一個ACK(M+1)包,此時server進入CLOSE-WAIT
狀態,client進入FIN-WAIT-2
狀態。
server向client發送FIN(N)包,請求關閉鏈接,同時server進入LAST-ACK
狀態。
client收到server發送的FIN(N)包,進入TIME-WAIT
狀態。向server發送**ACK(N+1)**包,server收到client的ACK(N+1)包之後,進入CLOSE
狀態;client等待一段時間尚未獲得回覆後判斷server已正式關閉,進入CLOSE
狀態。
通常在提到TCP三次握手的時候,一樣會涉及到TCP滑窗,下面咱們補充一下什麼是TCP滑窗。若是採用PAR的形式來傳遞的話,每一次發送方發送完包後必須獲得接收方的確認回覆,改進一下這個流程,發送端一次能夠發送多個包,沒必要每次等到接收方的ack回覆,同時接收端也要告訴發送端本身能接收多少。固然還須要保證順序性,對於亂序的情況,咱們能夠容許等待必定時間的亂序,好比先緩存提早到達的數據,而後等待須要的數據,若是必定時間沒有達到就drop掉。
TCP滑動窗口能夠解決咱們上面提到的概念,滑動窗口中的數據主要分爲下面幾類:
對於接收端也是有一個接收窗口,相似發送端,接收端的數據有三個分類(注意接收端並不要等待ACK):
下面這部份內容參考自coolshell,我認爲寫的比較好,因此轉過來分享一下,感興趣的朋友能夠閱讀一下 :)
TCP要保證全部的數據包均可以到達,因此,必須要有重傳機制。
注意,接收端給發送端的Ack確認只會確認最後一個連續的包,好比,發送端發了1,2,3,4,5一共五份數據,接收端收到了1,2,因而回ack 3,而後收到了4(注意此時3沒收到),此時的TCP會怎麼辦?咱們要知道,由於正如前面所說的,SeqNum和Ack是以字節數爲單位,因此ack的時候,不能跳着確認,只能確認最大的連續收到的包,否則,發送端就覺得以前的都收到了。
一種是不回ack,死等3,當發送方發現收不到3的ack超時後,會重傳3。一旦接收方收到3後,會ack 回 4——意味着3和4都收到了。
可是,這種方式會有比較嚴重的問題,那就是由於要死等3,因此會致使4和5即使已經收到了,而發送方也徹底不知道發生了什麼事,由於沒有收到Ack,因此,發送方可能會悲觀地認爲也丟了,因此有可能也會致使4和5的重傳。
對此有兩種選擇:
這兩種方式有好也有很差。第一種會節省帶寬,可是慢,第二種會快一點,可是會浪費帶寬,也可能會有無用功。但整體來講都很差。由於都在等timeout,timeout可能會很長。
因而,TCP引入了一種叫Fast Retransmit 的算法,不以時間驅動,而以數據驅動重傳。也就是說,若是,包沒有連續到達,就ack最後那個可能被丟了的包,若是發送方連續收到3次相同的ack,就重傳。Fast Retransmit的好處是不用等timeout了再重傳。
好比:若是發送方發出了1,2,3,4,5份數據,第一份先到送了,因而就ack回2,結果2由於某些緣由沒收到,3到達了,因而仍是ack回2,後面的4和5都到了,可是仍是ack回2,由於2仍是沒有收到,因而發送端收到了三個ack=2的確認,知道了2尚未到,因而就立刻重轉2。而後,接收端收到了2,此時由於3,4,5都收到了,因而ack回6。
Fast Retransmit只解決了一個問題,就是timeout的問題,它依然面臨一個艱難的選擇,就是,是重傳以前的一個仍是重傳全部的問題。對於上面的示例來講,是重傳#2呢仍是重傳#2,#3,#4,#5呢?由於發送端並不清楚這連續的3個ack(2)是誰傳回來的?也許發送端發了20份數據,是#6,#10,#20傳來的呢。這樣,發送端頗有可能要重傳從2到20的這堆數據(這就是某些TCP的實際的實現)。可見,這是一把雙刃劍。