天天看點

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

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次握手:

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

1、 當用戶端有需求時,會首先發起連接配接請求(希望建立連接配接,攜帶SYN(請求建立連接配接)字段)。請求發送出去以後就等待對方(伺服器)的應答(看對方是否同意建立連接配接)。第一次握手

2、伺服器端是一直處于監聽狀态,等待是否有需要建立連接配接的人,如果收到一個希望建立連接配接的請求,此時伺服器端就會進行應答(同意連接配接還是不同意)。(攜帶ACK字段(确認)和SYN字段(表示我也想建立連接配接))。第二次握手

3、用戶端收到伺服器端的應答,發現伺服器端同意建立連接配接,此時就給伺服器端應答(攜帶ACK字段),表示我知道了那麼連接配接就建立好了。第三次握手

我們可以想的更加生活化一點幫助我們了解這樣一個3次握手的過程。就像我們有時候約人出去吃飯,首先我們給他打電話問他:

我們一起去吃飯吧?(SYN)第一次握手

他說:好啊(ACK),什麼時候(SYN)第二次握手

此時你當然會繼續給對方回應:現在就去吧(ACK)第三次握手

這樣一看,3次握手的過程就比較簡單易懂了。

下面我們來讨論一下,為什麼偏偏就要進行3次握手呢???

可以假設一下如果進行一次握手的後果是怎樣的?

如果進行一次握手,也就是說,隻有用戶端發起連接配接。如果是這樣那麼請想象一下當你找人吃飯時你就跟他說我們去吃飯吧,然後你們之間就沒有下文了,那麼你怎麼知道他到底是同意還是不同意,你們之間吃飯的時間和地點統統都沒有協商好,這個事情又如何能進行下去呢?是以說,如果我們隻進行一次握手,我們是不知道伺服器端是否同意跟我們建立連接配接的,如果連接配接根本就沒有建立,那麼一切都沒有任何意義啊。

好了,既然一次握手行不通,就在加一次,2次握手到底是否可以呢?我們可以來分析一下。如果進行兩次握手,也就是說,用戶端發送連接配接請求以後是可以收到伺服器端的回信的,就像你約人吃飯你能夠知道對方是同意還是不同意跟你去吃飯,然後2次握手完畢沒有下文了,想一想,對方還問你了什麼時候去哪兒吃,但是你卻并沒有回答人家,此時對方就會疑惑,他到底知道不知道我已經同意跟他去吃飯了或者會想這貨到底是不是要跟我去吃飯啊,既然沒有回信可能對方就不管你又去幹别的事情了。另外一種情況就是,你昨天剛好有時間是以你約人出去吃飯,但是呢由于一些原因對方就沒有看到你的消息,是以一直沒有給你回信,但是今天他看到消息了并且回複了你說一起去吃飯,但是你今天就沒有時間,如果此時采用2次握手(你不給他回消息),他就以為你們已經約好了一起去吃飯,然後就精心打扮等待着你……是以說,如果用戶端在收到伺服器端的應答以後如果不給對方回信,那麼伺服器端就以為連接配接建立好了,然後就等着用戶端給他傳送資料,這無疑就浪費了很多的資源。是以說就像你今天收到對方的回信說一起去吃飯時應該給對方回複我今天沒有時間。對方就會知道了。對應于3次握手,如果你最終應答一個确認就說明連接配接建立好,将要使用這個連接配接發送資料,如果最終沒有回複一個應答就說明該連接配接已經不需要了

由此看來3次握手是必不可少的,但是我們也應該清楚的知道3次握手不保證一定成功,也是有可能會失敗的,但失敗對伺服器端并沒有影響,對用戶端是有影響的,但是可以彌補,就是在我們的用戶端通過連接配接發送資料時,發現連接配接并沒有建立好,此時就會進行RST(reset,複位封包段),重建立立連接配接。

4次揮手

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

伺服器端一旦接收到用戶端的确認封包就會進入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 = ;
setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
           

後續的TCP協定的資料傳輸的可靠性保證請移步下一篇文章:TCP協定資料傳輸的可靠性保證(續)

繼續閱讀