簡述
TCP / UDP 協議都是傳輸層的協議。
UDP 是面向無連接的協議,就是說發送端不在乎消息數據是否傳輸到接收端了,所以會出現數據丟失的情況,所以可靠性也不高。
TCP 是面向連接的、可靠的、基于字節流的傳輸層協議。所謂面向連接的,就是必須發送端和接收端必須處于連接狀態才能發送數據。可靠的就是無論網路鏈路出現的什么變化,都可以保證一個報文能夠到達接收端。
字節流:用戶消息通過 TCP 協議傳輸時,消息可能會被操作系統分組成多個 TCP 報文,如果接收方的程序如果不知道消息的邊界,是無法讀出一個有效的用戶消息的。
TCP 建立連接
TCP 是面向連接的,所以傳輸數據前需要和接收端建立連接,通過三次握手來進行。過程如下圖:
圖片來源: 小林coding
簡單描述一下三次握手:
1. 客戶端首先初始化自己的序列號,發送帶有 SYN 標識的報文給服務端,表示向服務端發起連接。客戶端進入?SYN-SEND 狀態。
2. 服務端接收到報文之后,同樣初始化自己的序列號,同時將確認應答的序列號,也就是收到的客戶端的序列號 + 1,返回給客戶端發送帶有 SYN + ACK 標識的報文,表示我接收到了你的連接請求,并同意連接。服務端進入?SYN-RCVD?狀態。
3. 客戶端接收到了服務端發來的應答,將收到的客戶端的序列號 + 1,再次向服務端發送帶有 ACK 表示的報文,表示我接收到你發的應答了,確認進入連接狀態。這次握手是可以攜帶客戶端到服務端的數據。客戶端進入?ESTABLISHED?狀態,在服務端收到客戶端的應答之后也進入?ESTABLISHED?狀態。
用一個例子來描述三次握手的情況。
為什么是三次握手
主要原因:三次握手是為了阻止重復歷史連接的初始化。
設想一個場景,當客戶端向服務端發送一個請求連接的報文之后宕機了,報文在到達服務器前被網絡阻塞了,此時服務器并沒有收到連接請求,狀態沒有變化。然后客戶端重啟之后,再次向服務端發送連接的報文。會出現下面的情況。
假設此時舊的請求報文比新的先到達服務端,然后向客戶端發送應答報文,都知道,報文里是包含了客戶端的序列號的,假設初始序列號是10,而服務端返回的是11(10+1),而客戶端在重啟之后發送的序列號是20,所以客戶端現在期望收到的報文序列號應該是21(20+1),但是此時收到的是11,那么就會向服務端發送 RST 請求報文, 表示出現了歷史連接,此次連接中止。(有內鬼,中止交易)。
而后一段時間之后,新發送的連接請求報文到達服務端,之后就會進行正常的 TCP 連接。用一個圖來簡單描述一下。
那么兩次握手能不能解決歷史連接的問題呢。
假設兩次握手,那么第一次握手服務端收到客戶端的SYN 之后,就進入到了 ESTABLISHED 狀態,此時服務端是可以向客戶端發送數據了,但是這個時候客戶端還沒有進入到?ESTABLISHED 狀態。服務端向客戶端發送 SYN+ACK 的報文之后,客戶端如果判斷此次是歷史連接,那么會回復 RST 中斷連接。但在這個期間,如果服務端發送了數據,那么發送的數據可能就丟失了,還浪費了資源。
三次握手之所以能解決歷史連接的問題,就是因為如果這是歷史連接,在第二次握手時服務端并不會進入到?ESTABLISHED 狀態,也就不能發送數據給客戶端,不會白白浪費資源。而是在客戶端確認了不是歷史連接的之后轉變狀態。
其他原因:同步初始序列號,避免浪費資源
1. 第一次握手,客戶端向服務端發送報文。
2. 第二次握手,服務端向客戶端發送報文,確認了客戶端發送正常,服務端接收正常。
3. 第三次握手,客戶端最后向服務端應答報文,確認了服務端發送正常,客戶端接收正常。
由此確認了客戶端和接收端連接正常。因為握手是會交換序列號的,也就是同步序列號,序列號在傳輸數據時去除重復的數據,可以根據序列號按序接收。
而兩次握手不能確保序列號同步,同時不能確認是否連接正常,假如只有兩次握手,少了第三次握手,那么服務端不知道客戶端的接收是否正常,如果不正常,那么服務端發送的數據就會丟失,浪費了資源。
握手過程中出現了報文丟失怎么辦
當第一次握手丟失了,當客戶端向服務端第一次握手,然后客戶端遲遲收不到服務端的 SYN+ACK 的報文,就會觸發超時重傳,重傳的報文序列號是一樣的。
而超時時間和重傳次數也有限制,不可能一直讓客戶端發送連接請求,浪費資源,在Linux 里,重傳次數是 5 ,而每次超時時間是上一次的兩倍,一般第一次是 1秒,第二次就是2秒,以此類推,5次重傳總耗時就是 1+2+4+8+16+31 = 63秒,大約一分鐘,如果5次都丟失,那么就不會再進行連接了。
第二次握手丟失。第二次握手包含了給客戶端的回應報文 ACK,和服務端發起建立連接的請求報文 SYN ,如果丟失了,客戶端會以為第一次握手丟失了,那么會觸發超時重傳。正常情況下,客戶端接收到第二次握手之后會發送 ACK 響應報文給服務端,但是服務端遲遲都收不到,那么服務端也會觸發超時重傳機制。
第三次握手丟失。第三次握手是客戶端發送給服務端的響應報文,如果丟失了,服務端會認為第二次握手丟失了,所以服務端會觸發超時重傳機制。
TCP 斷開連接的四次揮手
TCP 建立連接時需要發送報文,斷開連接時同樣需要發送報文。
1. 首先客戶端想要斷開連接,會發送一個帶有 FIN 標識的請求報文給服務端。客戶端進入 FIN_WAIT_1 狀態。
2. 服務端接收到報文之后會回復給客戶端一個帶有 ACK 標識的應答報文。服務端進入 CLOSE_WAIT 狀態。客戶端接收到 ACK 報文之后進入 FIN_WAIT_2 狀態。
3. 因為想要斷開連接時服務端可能還有數據沒有處理完,所以需要等待處理完成,處理完成之后會發送帶有 FIN 標識的請求報文給客戶端。服務端進入 LAST_ACK 狀態。
4. 客戶端接收到報文之后最后返回帶有 ACK 標識的應答報文給服務端,客戶端進入 TIME_WAIT? 狀態。服務端接收到ACK 報文之后進入到 CLOSE 狀態。一段時間之后,客戶端會進入 CLOSE 狀態,雙方都關閉連接。
圖片來源 小林coding
可以用打電話的方式來打個比方。
為什么要揮手四次
從每次揮手發送帶有標識的報文可以看出來,關閉連接時,客戶端向服務端發送 FIN 報文,只是代表客戶端已經發送數據完畢了,但是還能接收數據,同樣的服務端也是這樣。
服務端收到 FIN 報文時,回復一個 ACK 應答報文,表示我收到了你的請求,但是你先等等,我還有數據沒處理完,等到我數據處理完了,我再發送客戶端一個 FIN 報文表示我也沒數據需要處理和發送了。
假設少了一次揮手,比如說服務端只發送了 ACK 報文,客戶端就關閉連接的話,會導致還未處理和發送的數據出現錯誤或者丟失。
不過,在特定情況下,可以將四次揮手合并成三次揮手。比如服務端也沒有數據處理和發送時,TCP 存在延遲確認機制且是默認開啟的,那么第二次和第三次就會合并,變成三次揮手。
揮手丟失的話會發生什么
和握手相同,如果握手丟失了,發送方遲遲收不到回應,就會認為剛剛的報丟失了,就會觸發超時重傳機制。同樣的是 ACK 報文是不會重傳的,假如第二次揮手丟失了,那么客戶端收不到 ACK 應答報文,那么就會認為FIN 報文丟失了,那么就會重傳 FIN 報文。
簡單來說,當前最近的 FIN 報文發送方會經常重傳 FIN 報文。
為什么第四次揮手客戶端需要等待 2*MSL(報文最長壽命)時間后才進入 CLOSE?狀態
在第三次揮手時,服務端會發送 FIN 報文給客戶端,關閉連接,但是如果出現了第三次或者第四次揮手丟失的情況,服務端都會觸發超時重傳機制來重新發送 FIN 報文。
MSL 一個片段在網絡中最大的存活時間,2MSL 就是一個發送和一個回復所需的最大時間,如果這個大于這個時間客戶端還沒有收到服務端重新發送的 FIN 報文,那么客戶端就認為服務端已經接收到了自己發送的 ACK 應答報文,然后進行 CLOSE 狀態,代表 TCP 連接完成中斷。