上次說了「性本善」的 UDP 協議,這哥們秉承「網之初,性本善,不丟包,不亂序」的原則,徜徉在網絡世界中。html
與之相對應的,TCP 就像是老大哥同樣,瞭解了社會的殘酷,變得複雜而成熟,秉承「性惡論」。它認爲網絡環境是惡劣的,丟包、亂序、重傳、擁塞都是常有的事兒,一言不合可能就會丟包,送達不了,因此從算法層面來保證可靠性。算法
老規矩,我們先來看看 TCP 頭的格式。網絡
從上面這個圖能夠看出,它比 UDP 要複雜的多。而複雜的地方,也正是它爲了解決 UDP 存在的問題所必需的字段。tcp
首先,源端口號和目標端口號是二者都有,不可缺乏的字段。spa
接下來是包的序號。給包編號就是爲了解決亂序的問題。老大哥作事,穩重爲主,一件件來,面臨再複雜的狀況,也臨危不亂。設計
除了發送端須要給包編號外,接收方也會回覆確認序號。作事靠譜,答應了就要作到,暫時作不到也要給個回覆。htm
這裏要注意的是,TCP 是個老大哥沒錯,但不能說他必定會保證傳輸準確無誤的完成。從 IP 層面來說,若是網絡的確那麼差,是沒有任何可靠性保證的,即便 TCP 老大哥再穩,他也管不了 IP 層丟包,他只能儘量的保證在他的層面上的可靠性。blog
而後是一些狀態位。有如下常見狀態位:路由
從這些狀態位就能夠看出,TCP 基於「性惡論」,警覺性就很高,不像 UDP 和小朋友似的,隨便一個不認識的小朋友都能玩到一塊兒,他與別人的信任要通過屢次交互才能創建。rem
還有一個窗口大小。這個是 TCP 用來進行流量控制的。通訊雙方各聲明一個窗口,標識本身當前的處理能力,讓發送端別發送的太快,要否則撐死接收端。也不能發送的太慢,要否則就餓死接收端了。
根據上述對 TCP 頭的分析,咱們知道對於 TCP 協議要重點關注如下幾個問題:
瞭解完 TCP 頭,咱們就來看下 TCP 創建鏈接的過程,這就是著名的「三次握手」。
三次握手,過程是這樣子的:
着重記憶上述過程,後續不少分析都是基於這個過程來的。
記得剛接觸三次握手的時候,就一直很納悶,爲啥必定要三次?兩次不行嗎?四次不行嗎?而後不少人就解釋,若是是兩次,就怎樣怎樣,四次,又怎樣怎樣?但這其實都是從結果推緣由,沒有說明本質。
咱們應該知道,握手是爲了創建穩定的鏈接,這個是最終目的。而要達到這個目的,就要通訊雙方的交互造成一個確認的閉環。
拿上述 A、B 通訊的例子來看,A 給 B 發信息,B 要告訴 A 他收到信息了。這時候,算是一個確認閉環嗎?明顯不是,由於 B 沒有收到來自 A 的確認信息。
因此,要達到咱們上述的目標,還要 A 給 B 一個確認信息,這樣就造成了一個確認閉環。
A 給 B 的確認信息發出後,遇到網絡很差的狀況,也會出現丟包的狀況。按理來講,還應該有個迴應,可是,咱們發現,好像這樣下去就沒玩沒了啦。
因此,咱們說,只要通訊雙方造成一個確認閉環後,就認爲鏈接已創建。一旦鏈接創建,A 會立刻發送數據,而 A 發送數據,後續的不少問題都獲得瞭解決。
例如 A 發給 B 的確認消息丟了,當 A 後續發送的數據到達的時候,B 能夠認爲這個鏈接已經創建。若是 B 直接掛了,A 發送的數據就會報錯,說 B 不可達,這樣,A 也知道 B 出事情了。
三次握手除了通訊雙方創建鏈接外,主要仍是爲了溝通 TCP 包的序號問題。
A 要告訴 B,我發起的包的序號起始是從哪一個號開始的,B 也要告訴 A,B 發起的包的序號的起始號。
TCP 包的序號是會隨時間變化的,能夠當作一個 32 位的計數器,每 4ms 加一。計算一下,這樣到出現重複號,須要 4 個多小時。可是,4 個小時後,還沒到達目的地的包早就死翹翹了。這是由於 IP 包頭裏的 TTL(生存時間)。
爲何序號不能從 1 開始呢?由於這樣會很容易出現衝突。
例如,A 連上 B 以後,發送了 一、二、3 三個包,可是發送 3 的時候,中間丟了,或者繞路了,因而從新發送,後來 A 掉線了,從新連上 B 後,序號又從 1 開始,而後發送 2,可是壓根沒想發送 3,而若是上次繞路的那個 3 恰好又回來了,發給了 B ,B 天然就認爲,這就是下一包,因而發生了錯誤。
就這樣,雙方歷經千辛萬苦,終於創建了鏈接。前面也說過,爲了維護這個鏈接,雙方都要維護一個狀態機,在鏈接創建的過程當中,雙方的狀態變化時序圖就像下面這樣:
總體過程是:
說完了鏈接,接下來就來了解下 TCP 的「再見模式」。這也常被稱爲四次揮手。
還拿 A 和 B 舉例,揮手過程:
這樣這個鏈接就關閉了。看起來過程很順利,是的,這是通訊雙方「和平分手」的場面。
A 開始說「不玩了」,B 說「知道了」,這個回合,是沒什麼問題的,由於在此以前,雙方還處於合做的狀態。
若是 A 說「不玩了」,沒有收到回覆,那麼 A 會從新發送「不玩了」。可是這個回合結束以後,就極可能出現異常狀況了,由於有一方率先撕破臉。這種撕破臉有兩種狀況。
一種狀況是,A 說完「不玩了」以後,A 直接跑路,這是會有問題的,由於 B 尚未發起結束,而若是 A 直接跑路,B 就算髮起結束,也得不到回答,B 就就不知道該怎麼辦了。
另外一種狀況是,A 說完「不玩了」,B 直接跑路。這樣也是有問題的,由於 A 不知道 B 是還有事情要處理,仍是過一會發送結束。
爲了解決這些問題,TCP 專門設計了幾個狀態來處理這些問題。接下來,咱們就來看看斷開鏈接時的狀態時序圖。
總體過程是:
最後一個步驟裏,若是 A 直接跑路了,也會出現問題。由於 A 的最後一個回覆,B 若是沒有收到的話就會重複第 4 步,可是由於 A 已經跑路了,因此 B 會一直重複第 4 步。
所以,TCP 協議要求 A 最後要等待一段時間,這個等待時間是 TIME_WAIT,這個時間要足夠長,長到若是 B 沒收到 A 的回覆,B 重發給 A,A 的回覆要有足夠時間到達 B。
A 直接跑路還有一個問題是,A 的端口就空出來了,可是 B 不知道,B 原來發過的不少包可能還在路上,若是 A 的端口被新的應用佔用了,這個新的應用會受到上個鏈接中 B 發過來的包,雖然序列號是從新生成的,可是這裏會有一個雙保險,防止產生混亂。所以也須要 A 等待足夠長的時間,等到 B 發送的全部未到的包都「死翹翹」,再空出端口。
這個等待的時間設爲 2MSL,MSL 是 Maximum Segment Lifetime,即報文最大生存時間。它是任何報文再網絡上存在的最長時間,超過這個時間的報文就會被丟棄。
由於 TCP 報文基於 IP 協議,而 IP 頭中有一個 TTL 域,是 IP 數據報能夠通過的最大路有數,每通過一個處理他的路由器,此值就減 1,當此值爲 0 時,數據報就被丟棄,同時發送 ICMP 報文通知源主機。協議規定 MSL 爲 2 分鐘,實際應用中經常使用的是 30 秒、1分鐘和 2 分鐘等。
還有一種異常狀況,B 超過了 2MS 的時間,依然沒有收到它發的 FIN 的 ACK。按照 TCP 的原理,B 固然還會重發 FIN,這個時候 A 再收到這個包以後,就表示,我已經等你這麼久,算是仁至義盡了,再來的數據包我就不認了,因而直接發送 RST,這樣 B 就知道 A 跑路了。
將鏈接創建和鏈接斷開的兩個時序狀態圖綜合起來,就是著名的 TCP 狀態機。咱們能夠將這個狀態機和時序狀態機對照看,就會更加明瞭。
圖中加黑加粗部分,是上面說到的主要流程,相關說明:
參考: