本文經過兩個圖來梳理TCP-IP協議相關知識。TCP通訊過程包括三個步驟:創建TCP鏈接通道,傳輸數據,斷開TCP鏈接通道。如圖1所示,給出了TCP通訊過程的示意圖。html
圖1 TCP 三次握手四次揮手算法
圖1主要包括三部分:創建鏈接、傳輸數據、斷開鏈接。服務器
1)創建TCP鏈接很簡單,經過三次握手即可創建鏈接。網絡
2)創建好鏈接後,開始傳輸數據。TCP數據傳輸牽涉到的概念不少:超時重傳、快速重傳、流量控制、擁塞控制等等。ssh
3)斷開鏈接的過程也很簡單,經過四次握手完成斷開鏈接的過程。socket
三次握手創建鏈接:函數
第一次握手:客戶端發送syn包(seq=x)到服務器,並進入SYN_SEND狀態,等待服務器確認;網站
第二次握手:服務器收到syn包,必須確認客戶的SYN(ack=x+1),同時本身也發送一個SYN包(seq=y),即SYN+ACK包,此時服務器進入SYN_RECV狀態;spa
第三次握手:客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=y+1),此包發送完畢,客戶端和服務器進入ESTABLISHED狀態,完成三次握手。orm
握手過程當中傳送的包裏不包含數據,三次握手完畢後,客戶端與服務器才正式開始傳送數據。理想狀態下,TCP鏈接一旦創建,在通訊雙方中的任何一方主動關閉鏈接以前,TCP 鏈接都將被一直保持下去。
傳輸數據過程:
a.超時重傳
超時重傳機制用來保證TCP傳輸的可靠性。每次發送數據包時,發送的數據報都有seq號,接收端收到數據後,會回覆ack進行確認,表示某一seq號數據已經收到。發送方在發送了某個seq包後,等待一段時間,若是沒有收到對應的ack回覆,就會認爲報文丟失,會重傳這個數據包。
b.快速重傳
接受數據一方發現有數據包丟掉了。就會發送ack報文告訴發送端重傳丟失的報文。若是發送端連續收到標號相同的ack包,則會觸發客戶端的快速重傳。比較超時重傳和快速重傳,能夠發現超時重傳是發送端在傻等超時,而後觸發重傳;而快速重傳則是接收端主動告訴發送端數據沒收到,而後觸發發送端重傳。
c.流量控制
這裏主要說TCP滑動窗流量控制。TCP頭裏有一個字段叫Window,又叫Advertised-Window,這個字段是接收端告訴發送端本身還有多少緩衝區能夠接收數據。因而發送端就能夠根據這個接收端的處理能力來發送數據,而不會致使接收端處理不過來。 滑動窗能夠是提升TCP傳輸效率的一種機制。
d.擁塞控制
滑動窗用來作流量控制。流量控制只關注發送端和接受端自身的情況,而沒有考慮整個網絡的通訊狀況。擁塞控制,則是基於整個網絡來考慮的。考慮一下這樣的場景:某一時刻網絡上的延時忽然增長,那麼,TCP對這個事作出的應對只有重傳數據,可是,重傳會致使網絡的負擔更重,因而會致使更大的延遲以及更多的丟包,因而,這個狀況就會進入惡性循環被不斷地放大。試想一下,若是一個網絡內有成千上萬的TCP鏈接都這麼行事,那麼立刻就會造成「網絡風暴」,TCP這個協議就會拖垮整個網絡。爲此,TCP引入了擁塞控制策略。擁塞策略算法主要包括:慢啓動,擁塞避免,擁塞發生,快速恢復。
四次握手斷開鏈接:
第一次揮手:主動關閉方發送一個FIN,用來關閉主動方到被動關閉方的數據傳送,也就是主動關閉方告訴被動關閉方:我已經不會再給你發數據了(固然,在fin包以前發送出去的數據,若是沒有收到對應的ack確認報文,主動關閉方依然會重發這些數據),但此時主動關閉方還能夠接受數據。
第二次揮手:被動關閉方收到FIN包後,發送一個ACK給對方,確認序號爲收到序號+1(與SYN相同,一個FIN佔用一個序號)。
第三次揮手:被動關閉方發送一個FIN,用來關閉被動關閉方到主動關閉方的數據傳送,也就是告訴主動關閉方,個人數據也發送完了,不會再給你發數據了。
第四次揮手:主動關閉方收到FIN後,發送一個ACK給被動關閉方,確認序號爲收到序號+1,至此,完成四次揮手。
圖2給出了TCP通訊過程當中的狀態轉移圖,理解此圖是咱們理解TCP-IP協議的關鍵。
圖2 TCP狀態轉移圖
狀態圖詳細解讀:
1.CLOSED:起始點,在超時或者鏈接關閉時候進入此狀態。
2.LISTEN:服務端在等待鏈接過來時候的狀態,服務端爲此要調用socket,bind,listen函數,就能進入此狀態。此稱爲應用程序被動打開(等待客戶端來鏈接)。
3.SYN_SENT:客戶端發起鏈接,發送SYN給服務器端。若是服務器端不能鏈接,則直接進入CLOSED狀態。
4.SYN_RCVD:跟3對應,服務器端接受客戶端的SYN請求,服務器端由LISTEN狀態進入SYN_RCVD狀態。同時服務器端要回應一個ACK,同時發送一個SYN給客戶端;另一種狀況,客戶端在發起SYN的同時接收到服務器端得SYN請求,客戶端就會由SYN_SENT到SYN_RCVD狀態。
5.ESTABLISHED:服務器端和客戶端在完成3次握手進入狀態,說明已經能夠開始傳輸數據了。
以上是創建鏈接時服務器端和客戶端產生的狀態轉移說明。相對來講比較簡單明瞭,若是你對三次握手比較熟悉,創建鏈接時的狀態轉移仍是很容易理解。
下面,咱們來看看鏈接關閉時候的狀態轉移說明,關閉須要進行4次雙方的交互,還包括要處理一些善後工做(TIME_WAIT狀態),注意,這裏主動關閉的一方或被動關閉的一方不是指特指服務器端或者客戶端,是相對於誰先發起關閉請求來講的:
6.FIN_WAIT_1:主動關閉的一方,由狀態5進入此狀態。具體的動做是發送FIN給對方。
7.FIN_WAIT_2:主動關閉的一方,接收到對方的FIN-ACK(即fin包的迴應包),進入此狀態。
8.CLOSE_WAIT:接收到FIN之後,被動關閉的一方進入此狀態。具體動做是接收到FIN,同時發送ACK。(之因此叫close_wait能夠理解爲被動關閉方此時正在等待上層應用發出關閉鏈接指令)
9.LAST_ACK:被動關閉的一方,發起關閉請求,由狀態8進入此狀態。具體動做是發送FIN給對方,同時在接收到ACK時進入CLOSED狀態。
10.CLOSING:兩邊同時發起關閉請求時,會由FIN_WAIT_1進入此狀態。具體動做是接收到FIN請求,同時響應一個ACK。
11.TIME_WAIT:最糾結的狀態來了。從狀態圖上能夠看出,有3個狀態能夠轉化成它,咱們一一來分析:
a.由FIN_WAIT_2進入此狀態:在雙方不一樣時發起FIN的狀況下,主動關閉的一方在完成自身發起的關閉請求後,接收到被動關閉一方的FIN後進入的狀態。
b.由CLOSING狀態進入:雙方同時發起關閉,都作了發起FIN的請求,同時接收到了FIN並作了ACK的狀況下,由CLOSING狀態進入。
c.由FIN_WAIT_1狀態進入:同時接受到FIN(對方發起),ACK(自己發起的FIN迴應),與b的區別在於自己發起的FIN迴應的ACK先於對方的FIN請求到達,而b是FIN先到達。這種狀況機率最小。
關閉的4次鏈接最難理解的狀態是TIME_WAIT,存在TIME_WAIT的2個理由:
1.可靠地實現TCP全雙工鏈接的終止。
2.容許老的重複分節在網絡中消逝。
附:
慢熱啓動算法 – Slow Start
首先,咱們來看一下TCP的慢熱啓動。慢啓動的意思是,剛剛加入網絡的鏈接,一點一點地提速,不要一上來就像那些特權車同樣霸道地把路佔滿。新同窗上高速仍是要慢一點,不要把已經在高速上的秩序給搞亂了。
慢啓動的算法以下(cwnd全稱Congestion Window):
1)鏈接建好的開始先初始化cwnd = 1,代表能夠傳一個MSS大小的數據。
2)每當收到一個ACK,cwnd++; 呈線性上升
3)每當過了一個RTT,cwnd = cwnd*2; 呈指數讓升
4)還有一個ssthresh(slow start threshold),是一個上限,當cwnd >= ssthresh時,就會進入「擁塞避免算法」(後面會說這個算法)
因此,咱們能夠看到,若是網速很快的話,ACK也會返回得快,RTT也會短,那麼,這個慢啓動就一點也不慢。
擁塞避免算法 – Congestion Avoidance前面說過,還有一個ssthresh(slow start threshold),是一個上限,當cwnd >= ssthresh時,就會進入「擁塞避免算法」。通常來講ssthresh的值是65535,單位是字節,當cwnd達到這個值時後,算法以下:
1)收到一個ACK時,cwnd = cwnd + 1/cwnd
2)當每過一個RTT時,cwnd = cwnd + 1
這樣就能夠避免增加過快致使網絡擁塞,慢慢的增長調整到網絡的最佳值。很明顯,是一個線性上升的算法。
擁塞狀態時的算法前面咱們說過,當丟包的時候,會有兩種狀況:
1)等到RTO超時,重傳數據包。TCP認爲這種狀況太糟糕,反應也很強烈。
sshthresh = cwnd /2
cwnd 重置爲 1
進入慢啓動過程
2)Fast Retransmit算法,也就是在收到3個duplicate ACK時就開啓重傳,而不用等到RTO超時。
TCP Tahoe的實現和RTO超時同樣。
TCP Reno的實現是:
cwnd = cwnd /2
sshthresh = cwnd
進入快速恢復算法——Fast Recovery
上面咱們能夠看到RTO超時後,sshthresh會變成cwnd的一半,這意味着,若是cwnd<=sshthresh時出現的丟包,那麼TCP的sshthresh就會減了一半,而後等cwnd又很快地以指數級增漲爬到這個地方時,就會成慢慢的線性增漲。咱們能夠看到,TCP是怎麼經過這種強烈地震盪快速而當心得找到網站流量的平衡點的。
快速恢復算法 – Fast RecoveryTCP Reno
這個算法定義在RFC5681。快速重傳和快速恢復算法通常同時使用。快速恢復算法是認爲,你還有3個Duplicated Acks說明網絡也不那麼糟糕,因此沒有必要像RTO超時那麼強烈。 注意,正如前面所說,進入Fast Recovery以前,cwnd 和 sshthresh已被更新:
cwnd = cwnd /2
sshthresh = cwnd
而後,真正的Fast Recovery算法以下:
cwnd = sshthresh + 3 * MSS (3的意思是確認有3個數據包被收到了)
重傳Duplicated ACKs指定的數據包
若是再收到 duplicated Acks,那麼cwnd = cwnd +1
若是收到了新的Ack,那麼,cwnd = sshthresh ,而後就進入了擁塞避免的算法了。
若是你仔細思考一下上面的這個算法,你就會知道,上面這個算法也有問題,那就是——它依賴於3個重複的Acks。注意,3個重複的Acks並不表明只丟了一個數據包,頗有多是丟了好多包。但這個算法只會重傳一個,而剩下的那些包只能等到RTO超時,因而,進入了惡夢模式——超時一個窗口就減半一下,多個超時會超成TCP的傳輸速度呈級數降低,並且也不會觸發Fast Recovery算法了。
TCP New Reno
因而,1995年,TCP New Reno(參見 RFC 6582 )算法提出來,主要就是在沒有SACK的支持下改進Fast Recovery算法的——
當sender這邊收到了3個Duplicated Acks,進入Fast Retransimit模式,開發重傳重複Acks指示的那個包。若是隻有這一個包丟了,那麼,重傳這個包後回來的Ack會把整個已經被sender傳輸出去的數據ack回來。若是沒有的話,說明有多個包丟了。咱們叫這個ACK爲Partial ACK。
一旦Sender這邊發現了Partial ACK出現,那麼,sender就能夠推理出來有多個包被丟了,因而乎繼續重傳sliding window裏未被ack的第一個包。直到再也收不到了Partial Ack,才真正結束Fast Recovery這個過程
咱們能夠看到,這個「Fast Recovery的變動」是一個很是激進的玩法,他同時延長了Fast Retransmit和Fast Recovery的過程。