TCP 可靠傳輸的實現(一)TCP的三次握手和四次揮手

1、介紹TCP

TCP(Transmission Control Protocol 傳輸控制協議)是一種面向連接、可靠的、基于字節流的傳輸層通信協議。

TCP將通過以下方式來提供可靠性傳輸:

TCP 可靠性傳輸實現(一)TCP的三次握手和四次揮手;

TCP 可靠性傳輸實現(二)TCP的重傳機制;

http://www.lxweimin.com/p/f7f75a0f6384

TCP 可靠性傳輸實現(三)TCP的流量控制和擁塞機制;

TCP 可靠性傳輸實現(四)TCP的保活機制;

TCP面向連接意味著兩個使用TCP的應用(通常是一個客戶端和一個服務器)在彼此交換數據之前必須先建立一個TCP連接。這一過程和打電話相似,先撥號振鈴,等待對方接通后說“喂”,然后才說明是誰。在本文將介紹TCP是如何建立連接的,以及當一方通信結束后如何斷開連接。

2、TCP的首部

TCP數據被封裝在一個IP數據報中,如圖:

TCP數據在IP數據報中的封裝

下圖顯示TCP首部的數據格式。如果不計任選字段,它通常是20個字節。

TCP包首部

每個TCP段都包含源端和目的端的端口號,用于尋找發端和收端應用程序。這兩個值加上IP首部中的源端IP地址和目的端IP地址唯一確定一個TCP連接

一個IP地址和一個端口號也稱為一個插口(socket)插口對(socket pair)(包含客戶IP地址、客戶端端口號、服務器IP地址和服務器端口號的四元組)可唯一確定互聯網絡中每個TCP連接的雙方。

下面介紹下TCP首部字段

序列號:用來標識從TCP發端向TCP收端發送的數據字節流,它表示在這個報文段中的第一個數據字節。如果把字節流看做在兩個應用程序間的單向流動,則TCP用序號對每個字節進行計數。序號是32bit的無符號數,序號到達2^32-1后又從0開始。序號用來解決網絡包亂序問題。接收端根據這個編號進行確認

確認號:是接收確認端期望收到的下一個序列號。確認號應當是上次已成功收到的數據字節序號加1,只有當標志位中的ACK標志為1時該確認號的字段才有效。主要用來解決丟包問題。

若確認號=N,則表明:到序號N-1為止的所有數據都已正確收到。

首部長度

TCP 首部總長度由該字段決定。該字段占 4bit,取最大值1111時,也就是十進制的 15,TCP 首部的偏移單位是 4 byte, 那么TCP 首部最長是 15 * 4 = 60 字節。?TCP 首部總長度有20個固定字節,所以該字段最短是 20byte / 4byte =?5,即 0101。首部長度也叫做數據偏移,因為首部長度實際上指示了數據區在報文段的起始偏移值。

保留位

為將來定義新的用途保留,現在一般置 0。

控制位:TCP首部中有6個標志比特,它們中的多個可同時被設置為1,主要是用于操控TCP的狀態機的,依次為URG 、 ACK、? PSH、 RST、 SYN、 FIN。

\bullet ?URG:緊急指針有效,是發送端向接收端發送緊急數據的一種方式。

\bullet ?ACK:該位為1時表示【確認應答號】有效,TCP規定除了最初建立連接時的 SYN 號之外,該位必須設置為1。

\bullet ?PSH:接收方應該盡快將這個報文段交給應用層。

\bullet ?SYN:該位為1時表示希望建立連接,同步序號用來發起一個連接。

\bullet ?RST:該位為1時表示TCP連接出現異常,必須強制斷開連接。

\bullet ?FIN:該位為1時表示希望斷開連接。

窗口大小

TCP的流量控制由連接的每一端通過聲明的窗口大小來提供。

校驗和

是一個強制性的字段,一定是由發送端計算和存儲,并由接收端進行驗證。

緊急指針

當URG標志置1時緊急指針才有效。緊急指針是一個正的偏移量,和序號字段中的值相加表示緊急數據最后一個字節的序號。

選項字段

選項字段的長度 = TCP 首部總長度 - 20 字節固定長度。 由于 TCP?首部總長度最大為 60字節, 那么選項字段的長度最大為 40 字節;

一個選項字段占 4個字節


最常見的可選字段是最長報文大小,又稱為MSS(Maximum Segment Size)。每個連接方通常都在通信的第一個報文段(為建立連接而設置SYN標志的那個段)中指明這個選項。它表明本端所能接收的最大長度的報文段。

數據

數據部分是可選的,在連接建立和終止時,雙方交換的報文段僅有TCP首部,如果一方沒有數據要發送,也使用沒有任何數據的首部來確認收到的數據。

3、TCP連接的建立

TCP連接的建立與關閉

第一次握手:客戶端發送請求報文將 SYN = J 的初始序列號發送給服務端,發送完之后客戶端處于SYN_SENT狀態。

第二次握手:服務端收到SYN請求報文后,如果同意連接,會以自己的SYN(服務端) = K? 的初始序列號和 ack = SYN(客戶端) + 1 報文作為應答,服務端處于SYN_RECEIVE狀態。

第三次握手:客戶端接收到服務端的SYN+ack, 發送 ack = SYN(服務端) + 1確認包作為應答,客戶端轉為ESTABLISHED狀態。

為什么是三次握手?不是兩次、四次?

這個問題需要回到TCP的概念上:

TCP連接是為了得到 保證連接可靠性和流量控制所需維護的某些狀態信息,這些信息的組合包括 Socket、序列號、窗口大小。 所以問題就是,為什么三次握手才可以初始化Socket、序列號、窗口大小并建立TCP連接。原因有三:

客戶端連續發送多次SYN建立連接的報文,在網絡擁堵等情況下出現:

\bullet ?一個【舊的SYN報文】比【最新的SYN】報文更早到了服務端,那么此時,服務端就會回一個SYN + ACK 報文給客戶端;

\bullet ?客戶端收到后根據自身的上下文,判斷這是一個歷史連接(序列號過期或超時),那么客戶端就會發送 RST 報文給服務端,表示終止這一次的連接;

如果是兩次握手連接,就不能判斷此次連接是否是歷史連接,三次握手可以讓客戶端準備發送第三次報文時,客戶端有足夠的上下文來判斷當前的連接是否是歷史連接。

(2)三次握手才可以同步雙方的初始序列號;

TCP的通信雙方都必須維護一個【序列號】,序列號是可靠傳輸的一個關鍵因素,它的作用:

\bullet ?接收方可以丟棄重復的數據;

\bullet ?接收方可以根據數據包的序列號按序接收;

\bullet ?發送方可以標識哪些數據包是已被對方接收到的;

因此在三次握手中,當客戶端方法攜帶【初始序列號】的 SYN 報文的時候,需要服務端回一個 ACK 應答報文,表示客戶端的 SYN報文已被服務端成功接收, 那當服務端發送【初始序列號】給客戶端的時候,也需要得到客戶端的應答回應,這樣才能保證雙方的初始序列號能被可靠的同步。

四次握手是把 服務端回應客戶端的 ACK 請求和服務端發送給服務端的初始序列號(第二步、第三步)合并成了一步,因此就成了【三次握手】。

(3)三次握手才可以避免資源的浪費;

如果只有【兩次握手】,當客戶端的 SYN 請求連接在網絡中阻塞,客戶端超時沒有收到 ACK 應答報文就會重新發送 SYN,由于沒有三次握手,服務端直接就分配資源并建立連接會導致:

建立了多個冗余的無效連接,造成不必要的浪費。

兩次握手會導致資源浪費

4、TCP連接的斷開

四次揮手

第一次揮手:首先進行關閉的一方(即發送第一個 FIN)將執行主動關閉,而另一方(收到這個 FIN)執行被動關閉。客戶端發送完 FIN 報文后,就進入了 FIN_WAIT_1 狀態;

第二次揮手:服務器收到這個 FIN ,它發回一個確認應答 ACK,ack = M + 1 (即,收到的序號+1)? 表示收到了,接著服務端進入CLOSE_WAIT 狀態。客戶端收到服務端的 ACK 應答報文后,進入 FIN_WAIT_2 狀態。

第三次揮手:等待服務端處理完數據后,也向客戶端發送 FIN 報文,之后服務端進入 LAST_ACK 狀態。

第四次揮手:服務端收到了 ACK 應答報文后,就進入了 CLOSE 狀態,至此服務端完成了連接的關閉。

為什么揮手需要四次?

\bullet ?關閉連接時,客戶端向服務端發送 FIN 時,僅僅表示客戶端不再發送數據了,但還是能接收數據。

\bullet ?服務端收到客戶端的 FIN 報文時,先回一個 ACK 應答報文,而服務端可能還有數據需要處理和發送,等服務端不再發送數據時,才發送 FIN 報文給客戶端表示同意關閉連接。

服務端的 ACK 和 FIN 一般都會分開發送,從而比三次握手多了一次。

5、Socket編程

應用在使用 TCP 或 UDP 時,會用到操作系統提供的類庫。這種類庫一般被稱為API(Application Programming Interface 應用編程接口),使用 TCP 或 UDP 通信時,又會廣泛使用到套接字(Socket)的API。

Socket

那么TCP如何利用Socket編程

基于TCP的Socket通信

\bullet ?服務端和客戶端初始化 socket,得到文件描述符;

\bullet ?服務端調用 bind ,將綁定 IP 地址和端口號;

\bullet ?服務端調用 listen ,進行監聽;

\bullet ?服務端調用 accept,等待客戶端連接;

\bullet ?客戶端調用 connect,向服務端的地址和端口發起連接請求;

\bullet ?服務端 accept 返回用于傳輸的 socket 的文件描述符;

\bullet ?客戶端調用 write 寫入數據; 服務端調用 read 讀取數據;

\bullet ?客戶端斷開連接時,會調用 close , 服務端 read 讀取數據的時候,就會讀取到 EOF,待處理完數據后,服務端調用 close 表示連接關閉。

注意:服務端調用 accept 時,連接成功了會返回一個已完成連接的socket,后續用來傳輸數據。所以,監聽的 socket 和 用來傳輸數據的 socket,是兩個 socket,一個是 監聽 socket,一個是 已完成連接 socket。

重點講下三次握手連接請求的實現:

通過隊列實現

處于 listen 狀態的 TCP socket,有兩個獨立的隊列:

\bullet ?SYN 隊列(SYN Queue)

存儲了收到了 SYN 包的連接,它的職責是回復 SYN + ACK 包,并且在客戶端沒有收到 ACK包時執行重傳。 發送完 SYN + ACK 之后, SYN 隊列等待從客戶端發出的 Ack 包(三次握手的最后一個包)。當收到 ACK 包時,首先找到對應的 SYN 隊列,再在對應的 SYN隊列中檢查相關的數據是否匹配,如果匹配,則將連接相關的數據從 SYN 隊列中移除,創建一個完整的連接(用于傳輸數據的),并將這個連接加入到 Accept 隊列。

\bullet ?Accept 隊列(Accept Queue)

存放的是已建立好的連接,等待被應用程序取走。 當進程調用 accept() 時, 將 socket 從隊列中取出,傳遞給上層應用程序。


參考文檔:
1、TCP/IP 詳解

2、圖解tcpip

3、https://mp.weixin.qq.com/s/tH8RFmjrveOmgLvk9hmrkw

4、https://mp.weixin.qq.com/s/5VXhL0dTFcWNyfQ7-7NBEg

5、https://mp.weixin.qq.com/s?src=11&timestamp=1592656050&ver=2412&signature=8YnNG5TR2QrxJK4CPHAknP8I4ujYG2voPwedWyH1EJXVSQUKDS5IaHvbMd3S7kpL7GrCaZkQbvgbh5OoX9IZ777abi2e3Ze-Cu1DCoLk9cexKOng3oXRfgt60fOU2gNF&new=1

6、https://blog.csdn.net/mary19920410/article/details/58030147

7、https://blog.csdn.net/Mary19920410/article/details/72857764

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。