網絡基礎:傳輸層(tcp協議:連接管理機制)

tcp協議(傳輸控制協議):對數據的傳輸進行一個詳細的控制。

tcp協議段格式
這裏寫圖片描述

源/目的端口號:表示數據來自於哪個進程,要去到哪個進程。
32位序號/32位確認序號:後面詳細講解
4位tcp報頭長度:表示該TCP頭部有多少個32位bit(有多少個4字節),所以tcp頭部最大長度是15*4 = 60BYTE。
16位窗口大小:後面詳細講解
16位校驗和:發送端填充,crc校驗,接收端校驗不通過則認爲數據有問題,此處的校驗和不光包含TCP首部,也包含TCP數據部分。
16位緊急指針:表示哪部分數據是緊急數據

6位標誌位:

字段 說明
URG 緊急指針是否有效
ACK 確認號是否有效
PSH 提示接收端應用程序立刻從tcp緩衝區把數據讀走
RST 對方要求重新建立連接;我們把攜帶RST表示的稱爲復位報文段
SYN 請求建立連接;我們把攜帶SYN標識的稱爲同步報文段
FIN 通知對方本端要關閉連接我們稱攜帶FIN標識的爲結束報文段

下面我們將從可靠性和性能兩方面來深入的理解TCP協議,來看一看TCP是如何來保證可靠性並且提高性能的。

可靠性方面

一:連接管理機制
正常的情況下,TCP要經過3次握手建立連接,經過4次揮手斷開鏈接。
下面我們就來看一看到底是如何3次握手4次揮手的。
3次握手:
這裏寫圖片描述

1、 當客戶端有需求時,會首先發起連接請求(希望建立連接,攜帶SYN(請求建立連接)字段)。請求發送出去以後就等待對方(服務器)的應答(看對方是否同意建立連接)。第一次握手
2、服務器端是一直處於監聽狀態,等待是否有需要建立連接的人,如果收到一個希望建立連接的請求,此時服務器端就會進行應答(同意連接還是不同意)。(攜帶ACK字段(確認)和SYN字段(表示我也想建立連接))。第二次握手
3、客戶端收到服務器端的應答,發現服務器端同意建立連接,此時就給服務器端應答(攜帶ACK字段),表示我知道了那麼連接就建立好了。第三次握手
我們可以想的更加生活化一點幫助我們理解這樣一個3次握手的過程。就像我們有時候約人出去吃飯,首先我們給他打電話問他:
我們一起去吃飯吧?(SYN)第一次握手
他說:好啊(ACK),什麼時候(SYN)第二次握手
此時你當然會繼續給對方迴應:現在就去吧(ACK)第三次握手
這樣一看,3次握手的過程就比較簡單易懂了。

下面我們來討論一下,爲什麼偏偏就要進行3次握手呢???
可以假設一下如果進行一次握手的後果是怎樣的?
如果進行一次握手,也就是說,只有客戶端發起連接。如果是這樣那麼請想象一下當你找人吃飯時你就跟他說我們去吃飯吧,然後你們之間就沒有下文了,那麼你怎麼知道他到底是同意還是不同意,你們之間吃飯的時間和地點統統都沒有協商好,這個事情又如何能進行下去呢?所以說,如果我們只進行一次握手,我們是不知道服務器端是否同意跟我們建立連接的,如果連接根本就沒有建立,那麼一切都沒有任何意義啊。
好了,既然一次握手行不通,就在加一次,2次握手到底是否可以呢?我們可以來分析一下。如果進行兩次握手,也就是說,客戶端發送連接請求以後是可以收到服務器端的回信的,就像你約人吃飯你能夠知道對方是同意還是不同意跟你去吃飯,然後2次握手完畢沒有下文了,想一想,對方還問你了什麼時候去哪兒吃,但是你卻並沒有回答人家,此時對方就會疑惑,他到底知道不知道我已經同意跟他去吃飯了或者會想這貨到底是不是要跟我去吃飯啊,既然沒有回信可能對方就不管你又去幹別的事情了。另外一種情況就是,你昨天剛好有時間所以你約人出去吃飯,但是呢由於一些原因對方就沒有看到你的消息,因此一直沒有給你回信,但是今天他看到消息了並且回覆了你說一起去吃飯,但是你今天就沒有時間,如果此時採用2次握手(你不給他回消息),他就以爲你們已經約好了一起去吃飯,然後就精心打扮等待着你……所以說,如果客戶端在收到服務器端的應答以後如果不給對方回信,那麼服務器端就以爲連接建立好了,然後就等着客戶端給他傳送數據,這無疑就浪費了很多的資源。所以說就像你今天收到對方的回信說一起去吃飯時應該給對方回覆我今天沒有時間。對方就會知道了。對應於3次握手,如果你最終應答一個確認就說明連接建立好,將要使用這個連接發送數據,如果最終沒有回覆一個應答就說明該連接已經不需要了

由此看來3次握手是必不可少的,但是我們也應該清楚的知道3次握手不保證一定成功,也是有可能會失敗的,但失敗對服務器端並沒有影響,對客戶端是有影響的,但是可以彌補,就是在我們的客戶端通過連接發送數據時,發現連接並沒有建立好,此時就會進行RST(reset,復位報文段),重新建立連接。

4次揮手
這裏寫圖片描述
服務器端一旦接收到客戶端的確認報文就會進入established狀態,可以進行數據的讀寫。

1、當客戶端調用close主動要求關閉連接時,服務器就會接收到結束報文段(FIN)。客戶端進入fin_wait_1狀態。第一次揮手
2、服務器返回一個確認報文(ACK)並進入close_wait狀態。第二次揮手
3、客戶端收到確認報文以後,說明服務器端已經準備要關閉連接了(還需要處理完之前的數據),客戶端就進入fin_wait_2狀態,等待服務器端處理完數據。服務器端處理完數據以後向客戶端發送結束報文段(FIN),此時進入last_ack狀態,等待最後一個客戶端應答的ACK。第三次揮手
4、客戶端收到服務器端的結束報文段(FIN),就明白服務器已經將之前的數據處理完畢可以關閉連接了,於是客戶端就向服務器端迴應一個確認報文(ACK)確認自己收到了FIN。第四次揮手

TIME_WAIT狀態
TCP協議規定,主動請求想要斷開鏈接的一方最終要進入一個time_wait狀態。等待兩個MSL(報文最大生存時間)的時間之後才能回到closed狀態。這個time_wait的具體值在不同的操作系統中也有不同的規定,比如說在RFC1122中規定爲2分鐘,而在Centos7上規定的值又是1分鐘。下面我們來了解一下這個time_wait狀態。
顧名思義,time_wait,就是要等的意思,那麼爲什麼要等呢?之所以要等,是因爲我們必須保證連接已經完全斷開。那爲什麼又等的是2MSL呢?因爲這樣一來就能保證在兩個傳輸方向上的上未被接收或遲到的報文段都已經消失,在這個等的期間,我們就可以讓更多的報文消散(有可能某些報文在連接關閉以後纔到達,此時就有可能已經建立了一個新的連接了)。因此等待的久一點讓更多的報文得以消散。同時也保證了最後一個報文的可靠到達(假設最後一個ACK報文丟失,那麼服務器會在重發一個FIN,這時雖然客戶端的進程不在了,但是TCP連接依然存在,所以可以重發last_ack)。

解決TIME_WAIT狀態引起的bind失敗的方法
我們現在知道主動請求斷開連接的一方會進入一個time_wait狀態,但是這並不是對所有的情況來說都是合理的。假設說我們現在的服務器處理大量的客戶端的連接,但是某些客戶端不活躍,此時服務器就會主動的去斷開連接從而清理掉這些不活躍的客戶端,一旦清理的不活躍的客戶端多了,服務器端就會產生大量的time_wait狀態,那麼最終就會導致我們的服務端口不夠用就沒有辦法繼續去處理那些新的客戶端請求。
解決辦法:使用setsockopt()設置socket描述符的選項SO_REUSEADDR爲1,表示允許創建端口號相同但IP地址不同的多個socket描述符。

//在服務器端的代碼中的socket()和bind()之間加入以下代碼:
int opt = 1;
setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));

後續的TCP協議的數據傳輸的可靠性保證請移步下一篇文章:TCP協議數據傳輸的可靠性保證(續)

發佈了110 篇原創文章 · 獲贊 47 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章