TCP協議報文構成

TCP/IP協議是個協議族,包含各種協議這裡不再敘述,主要分為鏈路層、網路層、傳輸層、應用層。這裡主要講一下面向連接過程的傳輸層TCP協議。它的報文結構如下圖:

  • 序列號seq:佔4個位元組,用來標記數據段的順序,TCP把連接中發送的所有數據位元組都編上一個序號,第一個位元組的編號由本地隨機產生;給位元組編上序號後,就給每一個報文段指派一個序號;序列號seq就是這個報文段中的第一個位元組的數據編號。
  • 確認號ack:佔4個位元組,期待收到對方下一個報文段的第一個數據位元組的序號;序列號表示報文段攜帶數據的第一個位元組的編號;而確認號指的是期望接收到下一個位元組的編號;因此當前報文段最後一個位元組的編號+1即為確認號。
  • 確認ACK:佔1位,僅當ACK=1時,確認號欄位才有效。ACK=0時,確認號無效
  • 同步SYN(synchronous):連接建立時用於同步序號。當SYN=1,ACK=0時表示:這是一個連接請求報文段。若同意連接,則在響應報文段中使得SYN=1,ACK=1。因此,SYN=1表示這是一個連接請求,或連接接受報文。SYN這個標誌位只有在TCP建產連接時才會被置1,握手完成後SYN標誌位被置0。
  • 終止FIN(finish):用來釋放一個連接。FIN=1表示:此報文段的發送方的數據已經發送完畢,並要求釋放運輸連接
  • URG (urgent) 緊急指針是否有效。為1,表示某一位需要被優先處理
  • ACK (acknowledgement) 確認號是否有效,一般置為1。
  • PSH (push) 提示接收端應用程序立即從TCP緩衝區把數據讀走
  • RST (reset) 對方要求重新建立連接,複位。
  • ACK、SYN和FIN這些大寫的單詞表示標誌位,其值要麼是1,要麼是0;ack、seq小寫的單詞表示序號。

粗體關鍵欄位比較重要

三次握手的過程 #

下圖為客戶端和服務端三次握手的過程:

  • 第一次握手:建立連接時,客戶端發送syn包(syn=j)到伺服器,並進入SYN_SENT狀態,等待伺服器確認;SYN:同步序列編號(Synchronize Sequence Numbers)。
  • 第二次握手:伺服器收到syn包,必須確認客戶的SYN(ack=j+1),同時自己也發送一個SYN包(syn=k),即SYN+ACK包,此時伺服器進入SYN_RECV狀態;
  • 第三次握手:客戶端收到伺服器的SYN+ACK包,向伺服器發送確認包ACK(ack=k+1),此包發送完畢,客戶端和伺服器進入ESTABLISHED(TCP連接成功)狀態,完成三次握手。

關於伯克利套接字的說明

我們在Linux類操作系統環境下實現網路通信通常會使用伯克利套接字。伯克利套接字Berkeley sockets),也稱為BSD Socket。伯克利套接字的應用編程介面(API)是採用C語言的進程間通信的庫,經常用在計算機網路間的通信。 BSD Socket的應用編程介面已經是網路套接字的事實上的抽象標準。大多數其他程序語言使用一種相似的編程介面。BSD Socket作為一種API,允許不同主機或者同一個計算機上的不同進程之間的通信。它支持多種I/O設備和驅動,但是具體的實現是依賴操作系統的。這種介面對於TCP/IP是必不可少的,所以是互聯網的基礎技術之一。它最初是由加州伯克利大學為Unix系統開發出來的。所有現代的操作系統都都實現了伯克利套接字介面,因為它已經是連接互聯網的標準介面了。

和連接建立相關的API有:connect, listen, accept3個,connect用在客戶端,另外2個用在服務端。對於TCP/IP protocol stack來說,我們這裡只討論這3個應用層的API幹了什麼事情。

connect:

發送了一個SYN,收到Server的SYN+ACK後,代表連接完成。發送最後一個ACK是protocol stack,tcp_out完成的。

listen

在Server這端,準備了一個未完成的連接隊列,保存只收到SYN_C的socket結構;還準備了已完成的連接隊列,保存了收到了最後一個ACK的socket結構。

accept

應用進程調用accept的時候,就是去檢查上面說的已完成的連接隊列,如果隊列里有連接,就返回這個連接;空的,blocking方試調用,就睡眠等待,nonblocking方式調用,就直接返回,一般一"EWOULDBLOCK「 errno告訴調用者,連接隊列是空的。

四次揮手的過程

  • 1)客戶端進程發出連接釋放報文,並且停止發送數據。釋放數據報文首部,FIN=1,其序列號為seq=u(等於前面已經傳送過來的數據的最後一個位元組的序號加1),此時,客戶端進入FIN-WAIT-1(終止等待1)狀態。 TCP規定,FIN報文段即使不攜帶數據,也要消耗一個序號。
  • 2)伺服器收到連接釋放報文,發出確認報文,ACK=1,ack=u+1,並且帶上自己的序列號seq=v,此時,服務端就進入了CLOSE-WAIT(關閉等待)狀態。TCP伺服器通知高層的應用進程,客戶端向伺服器的方向就釋放了,這時候處於半關閉狀態,即客戶端已經沒有數據要發送了,但是伺服器若發送數據,客戶端依然要接受。這個狀態還要持續一段時間,也就是整個CLOSE-WAIT狀態持續的時間。
  • 3)客戶端收到伺服器的確認請求後,此時,客戶端就進入FIN-WAIT-2(終止等待2)狀態,等待伺服器發送連接釋放報文(在這之前還需要接受伺服器發送的最後的數據)。
  • 4)伺服器將最後的數據發送完畢後,就向客戶端發送連接釋放報文,FIN=1,ack=u+1,由於在半關閉狀態,伺服器很可能又發送了一些數據,假定此時的序列號為seq=w,此時,伺服器就進入了LAST-ACK(最後確認)狀態,等待客戶端的確認。
  • 5)客戶端收到伺服器的連接釋放報文後,必須發出確認,ACK=1,ack=w+1,而自己的序列號是seq=u+1,此時,客戶端就進入了TIME-WAIT(時間等待)狀態。注意此時TCP連接還沒有釋放,必須經過2??MSL(最長報文段壽命)的時間後,當客戶端撤銷相應的TCB後,才進入CLOSED狀態。

  • 6)伺服器只要收到了客戶端發出的確認,立即進入CLOSED狀態。同樣,撤銷TCB後,就結束了這次的TCP連接。可以看到,伺服器結束TCP連接的時間要比客戶端早一些。

而四次揮手對應的socket api為:

主要為close(fd),剩下的確認等待等操作會交給內核。

Socket三次握手四次揮手通信模型:

為什麼要三次握手

我們要知道網路傳輸是有延遲的,可能丟失的,不是說A發一個包給B,B保證能立刻收到,甚至B可能一直收不到。

? 第一次。A跟B說,我要建立連接了。

? 第二次。B跟A說,OK,那我也建立連接。

? 第三次。A跟B說,嗯,我知道了。

第二次和第三次都是為了保證連接是可靠的。

? 假設只有一次握手,而A的包無法發到B那裡去,那A就是自顧自的建立了連接,傻傻的發信息,卻不知道對方其實根本收不到。所以第二次握手是為了告訴A,B收到了你的信息。假設只有兩次握手,那麼對B來說,B是不知道A是否收到了自己的信息的,第三次握手是為了告訴B,A收到了B的信息了,並且可以互發信息了。 B真的需要知道 A是否收到了自己發出的第二次握手的信息嗎?是的,如果A的第一次握手因為某些原因延遲很久才到B,而其實現在A和B已經聊完天,關閉連接了,這時B發出第二次握手,而A已經沒什麼想跟B說的,就不會發出第三次握手,這樣B就不會建立連接消耗資源。若是只有二次握手的這種情況,B會直接建立連接消耗資源。

為什麼要四次揮手

第一次。A跟B說,我要斷開連接了。

第二次。B跟A說,好的,我知道了,我不再接收你的信息了。

第三次。B跟A說,我傳給你的信息傳完了,你可以關閉連接了。

第四次。A跟B說,好的,我關閉連接了。

第二次揮手是為了告訴A,B知道你不再發送信息了,且B不再接收信息。但B仍可以向A發送信息。因為A是主動關閉的一方,但B可能仍然有信息未發送完。第三次揮手是為了告訴A,B的信息發完了,A你可以關閉連接了。 第四次揮手是為了告訴B,A我知道可以關閉連接了,你也可以關閉了。對B來說,第四次揮手B才知道A成功關閉連接了,不關閉很耗費資源,所以要保證關閉了。若B接收不到第四次揮手信息,將會繼續第三次揮手,直到收到確認信息為止。注意:對A來說,第四次揮手後2MSL內未接收到B的第三次揮手信息才會關閉連接,否則會繼續第四次揮手。防止B接收不到第四次揮手信息。(若B接收不到第四次揮手信息,將重複發送第三次揮手信息,這個2MSL就是如果真的有重複發送的第三次揮手信息,在這個時間內肯定到達A了(路由不出問題的話),就是為了保證B收到第四次揮手信息)。

狀態圖:

關於一個很出名的狀態圖附上:

客戶端的狀態變遷:CLOSED-->SYN_SENT-->ESTABLISHED-->FIN_WAIT_1-->FIN_WAIT_2-->TIME_WAIT-->CLOSED

伺服器的狀態變遷:CLOSED-->LISTEN-->SYN_RCVD-->ESTABLISHED-->CLOSE_WAIT-->LAST_ACK--->CLOSED

CLOSED:這個狀態不是一個真正的狀態,是圖中假想的一個起點或者是終點

LISTEN: 伺服器等待連接過來的狀態

SYN_SENT: 客戶端發起連接(主動打開),變成此狀態,如果SYN超時,或者伺服器不存在直接CLOSED

SYN_RCVD:伺服器收到SYN包的時候,就變成此狀態,

ESTABLISHED:完成三次握手,進入連接建立狀態,說明此時可以進行數據傳輸了

FIN_WAIT_1:客戶端執行主動關閉,發送完FIN包之後便進入FIN_WAIT_1狀態

FIN_WAIT_2:客戶端發送FIN包之後,收到ACK,即進入此狀態,其實就是半關閉的狀態

TIME_WAIT:這個狀態從圖上看,有3中情況,從FIN_WAIT_2進入,客戶端收到伺服器發送過來的FIN包之後進入TIME_WAIT狀態,有CLOSING狀態進入,這是同時關閉的狀態,同時發起FIN請求,同時接收並做了ACK的回復,從FIN_WAIT_1進入,收到對端的FIN,ACK,並回復ACK,這個地方感覺是,FIN和ACK是一塊來的.

CLOSE_WAIT:接收到FIN之後,被動的一方進入此狀態,並回復ACK

LAST_ACK:被動的一端發送FIN包之後 處於LAST_ACK狀態


推薦閱讀:
相关文章