TCP(Transmission Control Protocol,傳輸控制協議)是一種面向鏈接的、可靠的、基於字節流的通訊協議,數據在傳輸前要創建鏈接,傳輸完畢後還要斷開鏈接。它是個超級麻煩的協議,是互聯網的基礎,也是每一個程序員必備的基本功。首先來看看OSI的七層模型:程序員
咱們須要知道TCP工做在網絡OSI的七層模型中的第四層——Transport層,IP在第三層——Network層,ARP在第二層——Data Link層;在第二層上的數據,咱們把它叫Frame,在第三層上的數據叫Packet,第四層的數據叫Segment。 同時,咱們須要簡單的知道,數據從應用層發下來,會在每一層都會加上頭部信息,進行封裝,而後再發送到數據接收端。這個基本的流程你須要知道,就是每一個數據都會通過數據的封裝和解封裝的過程。 在OSI七層模型中,每一層的做用和對應的協議以下: 算法
TCP是一個協議,那這個協議是如何定義的,它的數據格式是什麼樣子的呢?要進行更深層次的剖析,就須要瞭解,甚至是熟記TCP協議中每一個字段的含義。 服務器
上面就是TCP協議頭部的格式,因爲它過重要了,是理解其它內容的基礎,下面就將每一個字段的信息都詳細的說明一下。網絡
URG
,ACK
,PSH
,RST
,SYN
,FIN
。每一個標誌位的意思以下:
SYN
標誌位和ACK
標誌位搭配使用,當鏈接請求的時候,SYN
=1,ACK
=0;鏈接被響應的時候,SYN
=1,ACK
=1;FIN
標誌位的TCP數據包後,鏈接將被斷開。好了,基本知識都已經準備好了,下面來看看TCP創建鏈接、發送數據及斷開鏈接。併發
1.創建鏈接
使用 connect() 創建鏈接時,客戶端和服務器端會相互發送三個數據包,俗稱三次握手。能夠形象的比喻爲下面的對話:socket
客戶端調用 socket() 函數建立套接字後,由於沒有創建鏈接,因此套接字處於CLOSED
狀態;服務器端調用 listen() 函數後,套接字進入LISTEN
狀態,開始監聽客戶端請求。
這個時候,客戶端開始發起請求:
1).當客戶端調用 connect() 函數後,TCP協議會組建一個數據包,並設置 SYN 標誌位,表示該數據包是用來創建同步鏈接的。同時生成一個隨機數字 1000,填充「序號(Seq)」字段,表示該數據包的序號。完成這些工做,開始向服務器端發送數據包,客戶端就進入了SYN-SEND
狀態。
2).服務器端收到數據包,檢測到已經設置了 SYN 標誌位,就知道這是客戶端發來的創建鏈接的「請求包」。服務器端也會組建一個數據包,並設置 SYN 和 ACK 標誌位,SYN 表示該數據包用來創建鏈接,ACK 用來確認收到了剛纔客戶端發送的數據包。
服務器生成一個隨機數 2000(2000 和客戶端數據包沒有關係),填充「序號(Seq)」字段。服務器將客戶端數據包序號(1000)加1,獲得1001,並用這個數字填充「確認號(Ack)」字段。服務器將數據包發出,進入SYN-RECV
狀態。
3).客戶端收到數據包,檢測到已經設置了 SYN 和 ACK 標誌位,就知道這是服務器發來的「確認包」。客戶端會檢測「確認號(Ack)」字段,看它的值是否爲 1000+1,若是是就說明鏈接創建成功。
接下來,客戶端會繼續組建數據包,並設置 ACK 標誌位,表示客戶端正確接收了服務器發來的「確認包」。同時,將剛纔服務器發來的數據包序號(2000)加1,獲得 2001,並用這個數字來填充「確認號(Ack)」字段。客戶端將數據包發出,進入ESTABLISED
狀態,表示鏈接已經成功創建。
4).服務器端收到數據包,檢測到已經設置了 ACK 標誌位,就知道這是客戶端發來的「確認包」。服務器會檢測「確認號(Ack)」字段,看它的值是否爲 2000+1,若是是就說明鏈接創建成功,服務器進入ESTABLISED
狀態。
至此,客戶端和服務器都進入了ESTABLISED
狀態,鏈接創建成功,接下來就能夠收發數據了。函數
那爲何非要三次呢?怎麼以爲兩次就能夠完成了。spa
在謝希仁的《計算機網絡》中是這樣說的:爲了防止已失效的鏈接請求報文段忽然又傳送到了服務端,於是產生錯誤。計算機網絡
在書中同時舉了一個例子,以下:指針
已失效的鏈接請求報文段」的產生在這樣一種狀況下:client發出的第一個鏈接請求報文段並無丟失,而是在某個網絡結點長時間的滯留了,以至延誤到鏈接釋放之後的某個時間纔到達server。原本這是一個早已失效的報文段。但server收到此失效的鏈接請求報文段後,就誤認爲是client再次發出的一個新的鏈接請求。因而就向client發出確認報文段,贊成創建鏈接。假設不採用「三次握手」,那麼只要server發出確認,新的鏈接就創建了。因爲如今client並無發出創建鏈接的請求,所以不會理睬server的確認,也不會向server發送數據。但server卻覺得新的運輸鏈接已經創建,並一直等待client發來數據。這樣,server的不少資源就白白浪費掉了。採用「三次握手」的辦法能夠防止上述現象發生。例如剛纔那種狀況,client不會向server的確認發出確認。server因爲收不到確認,就知道client並無要求創建鏈接。
2.數據傳輸
創建鏈接後,兩臺主機就能夠相互傳輸數據了。以下圖所示:
上圖給出了主機A分2次(分2個數據包)向主機B傳遞200字節的過程。首先,主機A經過1個數據包發送100個字節的數據,數據包的 Seq 號設置爲 1200。主機B爲了確認這一點,向主機A發送 ACK 包,並將 Ack 號設置爲 1301。
爲了保證數據準確到達,目標機器在收到數據包(包括SYN包、FIN包、普通數據包等)包後必須當即回傳ACK包,這樣發送方纔能確認數據傳輸成功。
此時 Ack 號爲 1301 而不是 1201,緣由在於 Ack 號的增量爲傳輸的數據字節數。假設每次 Ack 號不加傳輸的字節數,這樣雖然能夠確認數據包的傳輸,但沒法明確100字節所有正確傳遞仍是丟失了一部分,好比只傳遞了80字節。所以按以下的公式確認 Ack 號:Ack號 = Seq號 + 傳遞的字節數 + 1
與三次握手協議相同,最後加 1 是爲了告訴對方要傳遞的 Seq 號。
下面分析傳輸過程當中數據包丟失的狀況,以下圖所示:
上圖表示經過 Seq 1301 數據包向主機B傳遞100字節的數據,但中間發生了錯誤,主機B未收到。通過一段時間後,主機A仍未收到對於 Seq 1301 的ACK確認,所以嘗試重傳數據。爲了完成數據包的重傳,TCP套接字每次發送數據包時都會啓動定時器,若是在必定時間內沒有收到目標機器傳回的 ACK 包,那麼定時器超時,數據包會重傳。
上圖演示的是數據包丟失的狀況,也會有 ACK 包丟失的狀況,同樣會重傳。
這個值太大了會致使沒必要要的等待,過小會致使沒必要要的重傳,理論上最好是網絡 RTT 時間,但又受制於網絡距離與瞬態時延變化,因此實際上使用自適應的動態算法(例如 Jacobson 算法和 Karn 算法等)來肯定超時時間。
往返時間(RTT,Round-Trip Time)表示從發送端發送數據開始,到發送端收到來自接收端的 ACK 確認包(接收端收到數據後便當即確認),總共經歷的時延。
TCP數據包重傳次數根據系統設置的不一樣而有所區別。有些系統,一個數據包只會被重傳3次,若是重傳3次後還未收到該數據包的 ACK 確認,就再也不嘗試重傳。但有些要求很高的業務系統,會不斷地重傳丟失的數據包,以盡最大可能保證業務數據的正常交互。
最後須要說明的是,發送端只有在收到對方的 ACK 確認包後,纔會清空輸出緩衝區中的數據。
3.斷開鏈接
創建鏈接很是重要,它是數據正確傳輸的前提;斷開鏈接一樣重要,它讓計算機釋放再也不使用的資源。若是鏈接不能正常斷開,不只會形成數據傳輸錯誤,還會致使套接字不能關閉,持續佔用資源,若是併發量高,服務器壓力堪憂。
創建鏈接須要三次握手,斷開鏈接須要四次握手,下圖演示了客戶端主動斷開鏈接的場景,能夠形象的比喻爲下面的對話:
創建鏈接後,客戶端和服務器都處於ESTABLISED
狀態。這時,客戶端發起斷開鏈接的請求:
1).客戶端調用 close() 函數後,向服務器發送 FIN 數據包,進入FIN_WAIT_1
狀態。FIN 是 Finish 的縮寫,表示完成任務須要斷開鏈接。
2).服務器收到數據包後,檢測到設置了 FIN 標誌位,知道要斷開鏈接,因而向客戶端發送「確認包」,進入CLOSE_WAIT
狀態。
注意:服務器收到請求後並非當即斷開鏈接,而是先向客戶端發送「確認包」,告訴它我知道了,我須要準備一下才能斷開鏈接。
3) 客戶端收到「確認包」後進入FIN_WAIT_2
狀態,等待服務器準備完畢後再次發送數據包。
4) 等待片刻後,服務器準備完畢,能夠斷開鏈接,因而再主動向客戶端發送 FIN 包,告訴它我準備好了,斷開鏈接吧。而後進入LAST_ACK
狀態。
5) 客戶端收到服務器的 FIN 包後,再向服務器發送 ACK 包,告訴它你斷開鏈接吧。而後進入TIME_WAIT
狀態。
6) 服務器收到客戶端的 ACK 包後,就斷開鏈接,關閉套接字,進入CLOSED
狀態。
爲何客戶端最後一次發送 ACK包後進入 TIME_WAIT 狀態,而不是直接進入 CLOSED 狀態關閉鏈接?
TCP 是面向鏈接的傳輸方式,必須保證數據可以正確到達目標機器,不能丟失或出錯,而網絡是不穩定的,隨時可能會毀壞數據,因此機器A每次向機器B發送數據包後,都要求機器B」確認「,回傳ACK包,告訴機器A我收到了,這樣機器A才能知道數據傳送成功了。若是機器B沒有回傳ACK包,機器A會從新發送,直到機器B回傳ACK包。
客戶端最後一次向服務器回傳ACK包時,有可能會由於網絡問題致使服務器收不到,服務器會再次發送 FIN 包,若是這時客戶端徹底關閉了鏈接,那麼服務器不管如何也收不到ACK包了,因此客戶端須要等待片刻、確認對方收到ACK包後才能進入CLOSED狀態。那麼,要等待多久呢?
TIME_WAIT 狀態的說明
數據包在網絡中是有生存時間的,超過這個時間還未到達目標主機就會被丟棄,並通知源主機。這稱爲報文最大生存時間(MSL,Maximum Segment Lifetime)。TIME_WAIT 要等待 2MSL 纔會進入 CLOSED 狀態。ACK 包到達服務器須要 MSL 時間,服務器重傳 FIN 包也須要 MSL 時間,2MSL 是數據包往返的最大時間,若是 2MSL 後還未收到服務器重傳的 FIN 包,就說明服務器已經收到了 ACK 包。