1. 程式人生 > >Linux網路程式設計---詳解TCP的三次握手和四次揮手

Linux網路程式設計---詳解TCP的三次握手和四次揮手

我們知道,在TCP/IP協議中,TCP協議提供可靠的連線服務,是因為它有許多保證可靠連線的機制。可以分為3個方面:
1.確認應答機制:指的是不管哪一端傳送資料都需要確認回覆一下。
2.超時重傳機制,傳送後等待一段時間,不管是傳送失敗或者是還沒有收到回覆,那麼就認為資料傳輸失敗了;此時將會資料重傳。這個超時是遞增變化,但次數有限制,超過了重傳次數就認為網路斷開了。
3.序號/確認序號 :序號不一定從0開始,在一開始連線時,兩端會協商好,然後根據傳送的資料大小按位元組進行排序,之後確認序號在序號的基礎上加上資料的大小,傳送給對端表明資料已經接收到。
例如A端從1開始傳送資料,(這個序號1在進行資料傳輸前,A端和B端會協商好),傳送了1000個位元組資料,那麼B端如果收到給A端回覆時,確認序號就是1001,表示前1000個位元組資料接受到),資料傳到服務端之後,會根據序號將資料存放到B端緩衝區對應的位置上。
TCP因為要保證可靠傳輸,因此效能有很大的消耗,為了提高TCP傳輸效能,又需要有其他的一些機制。

這些機制包括:
1.滑動視窗
2.流量控制
3.擁塞控制
我們今天主要講述第1個很重要的機制:確認應答機制。因為這個機制當中設計到許多知識 ,需要我們深刻理解。我們一步一步來看。

1.TCP分段的格式

TCP的協議資料單元被稱為分段(Segment),TCP通過分段是互動來建立連線,傳輸資料。發出確認,進行差錯控制,流量控制及關閉連線。分段分為分段頭和資料兩部分。分段頭就是TCP為了實現端到端可靠傳輸所加上的控制資訊,而資料則是指由高層即應用層來的資料。下面我們來介紹TCP協議的分段各部分的內容。

在這裡插入圖片描述

源埠(Source Port): 16位的源埠其中包含初始化通訊的埠。源埠和源IP地址的作用是標示報文的返回地址。
目的埠(Destination port):

16位的目的埠域定義傳輸的目的。這個埠指明報文接收計算機上的應用程式地址介面。
序列號(序列碼,Sequence Number): 32位的序列號表示該分段的資料在傳送方資料流中的位置,用來保證到達資料順序的編號。當SYN出現,序列碼實際上是初始序列碼(ISN),而第一個資料位元組是ISN+1。在 SYN 標誌位置0時,該欄位指示了使用者資料區中第一個位元組的序號;在 SYN 標誌位置1時,該欄位指示的是初始傳送的序列號。
解釋: 如果傳送方給接收方傳送了一個連線請求(SYN)的分段,此時這分段的初始序列號假如是X,這個分段中資料佔用的位元組是1000個位元組,那麼傳送給接收方的這個序列號就是X+1000。這個序列號就告訴了接收方這個資料在總的資料流中的所佔的位置。即使傳送方的資料不按順序傳送,因為有了這個序列號,那麼接受方在存放資料時,也會將資料按順序存放。

確認號(Acknowledgment Number): 目的主機給源主機返回確認號,使源主機知道某個或幾個報文段已被接收。如果 ACK 控制位被設定為 1,則該欄位有效。確認號等於順序接收到的最後一個報文段的序號加 1,這也是目的主機希望下次接收的報文段的序號值。返回確認號後,計算機認為已接收到小於該確認號的所有資料。

資料偏移量(HLEN): 佔4個位元位,表示該TCP頭部有多少個32位bit(有多少個4位元組),最大時是15;所以TCP頭部最⼤⻓度是15 * 4 =60個位元組。即TCP 報文段的資料起始處距離 TCP 報文段的起始處有多遠,它指示何處資料開始。
保留(Reserved): 6位值域,這些位必須是0。為了將來定義新的用途所保留。

緊急位元URG: 此位置位為 1時,表明啟用了緊急指標欄位,它告訴系統此報文段中有緊急資料,應儘快傳送。

確認位元ACK: 僅當 ACK = 1 時,確認號欄位才有效,TCP 規定,在連線建立後所有傳達的報文段都必須把 ACK 置 1。

推送位元PSH: 請求急迫操作,即分段一到馬上傳送應用程式而不等到緩衝區滿時才傳送應用程式。

解釋:當兩個應用程序進行互動式的通訊時,有時在一端的應用程序希望在鍵入一個命令後立即就能夠收到對方的響應。在這種情況下,TCP 就可以使用推送(push)操作,這時,傳送方 TCP 把 PSH 置 1 ,並立即建立一個報文段傳送出去,接收方收到PSH = 1 的報文段,就儘快地(即“推送”向前)交付給接收應用程序,而不再等到整個快取都填滿後再向上交付。

復位位元RST: 復位連線,置1時重建連線。如果接收到RST位時候,通常發生了某些錯誤。如主機崩潰,也可用於拒絕非法的分段或拒絕連線請求。

同步位元SYN: 與ACK合用以建立TCP連線。僅在三次握手建立 TCP 連線時有效。當 SYN = 1而 ACK = 0時,表明這是一個連線請求報文段,對方若同意建立連線,則應在相應的報文段中使用 SYN = 1和ACK = 1。因此SYN置1就表示這是一個連線請求和連線接受報文。

終止位元FIN: 用來釋放一個連線。當 FIN = 1時表示傳送方已無資料要傳送,從而釋放連線。但接收方仍可繼續接收發送方此前傳送的資料。
視窗(Window): 16位,這個值是傳送方期望一次接收的位元組數,此欄位使用可變大小的滑動視窗協議用來進行流量控制。
校驗位(Checksum): 16位,用於對分段首部和資料進行校驗。通過將所有16個位元位以補碼的形式相加,然後再對和取補碼,正常情況下結果為0。源機器基於資料內容計算一個數值,收資訊機要與源機器數值結果完全一樣,從而證明資料的有效性。

緊急指標(緊急,Urgent Pointer): 佔16位,僅在 URG = 1 時才有意義,它指出本報文段中的緊急資料的位元組數(緊急資料結束後就是普通資料),即指出了緊急資料的末尾在報文中的位置,注意:即使視窗為零時也可傳送緊急資料。如果 URG 為 1 ,則緊急指標標誌著緊急資料的結束。其值是緊急資料最後 1 位元組的序號,表示報文段序號的偏移量。例如,如果報文段的序號是 1000,前 8 個位元組都是緊急資料,那麼緊急指標就是 8 。緊急指標一般用途是使使用者可中止程序。
選項(Option): 長度不定,但長度必須是一個位元組。如果沒有選項就表示這一個位元組的域等於0。
介紹了TCP協議的分段,下來我們看看三次握手的過程。

三次握手的基本過程

在這裡插入圖片描述

準備階段:剛開始客戶端和服務端處於CLOSED狀態,表示可以建立連線。當要建立連線時,此時服務端開始監聽,並處於LISTEN狀態。
在此之前,需要注意ACK、SYN、Seq(序號)、Ack(確認序號)的所表示的含義。
第一次握手:客戶端將SYN設定為1,表示要建立一個新的連線,並隨機產生一個序列值Seq=M(100),並將該資料包發給伺服器客戶端,進入SYN_SEND狀態;

第二次握手:伺服器收到資料包後,由標誌位SYN=1知道客戶端要建立一個連線,伺服器將確認ACK置為1,並且將Ack=M+1(101)表示確認序號,之後,再將SYN置為1,並隨機產生一個Seq=N(200),表示服務端也想和客戶端建立連線,之後將該資料包發給客戶端,伺服器進入SYN_RECV狀態;

第三次握手:客戶端收到確認後,檢查ACK是否為1,Ack是否為M+1(101),伺服器有時候不同意建立一個連線(有可能達到了伺服器建立客戶端的上限),那麼這裡的ACK=0,表示服務端不同意建立連線。如果ACK=1,則服務端同意建立連線;並且在第二次握手時,服務端也向客戶端傳送了一個SYN請求,此時客戶端收到在收到服務端的應答請求ACK的同時也收到了服務端想和客戶端建立連線的SYN請求,此時客戶端會也會給服務端回覆請求。因此客戶端將ACK置為1,Ack置為N+1(201),之後將資料包傳送給服務端。當服務端收到資料包之後,,客戶端和伺服器都進入ESTABLISHED狀態,建立連線完成,隨後伺服器和客戶端之間就開始傳輸資料了!這就是大名鼎鼎的三次握手!!!!

有人肯定覺得這樣的連線會很煩,建立連線一次,兩次應該也可以搞定啊,為什麼要偏偏是三次?或者又有人腦洞比較特別,會問咋不是四次呢?

在此之前,我們要知道TCP是要保證可靠資料傳輸的,也就是TCP是一個全雙共通訊協議。
我們來一一解釋;
我們不妨舉個打電話例子來幫助理解。
假如A給B打電話。
1.一次握手的情況: A問B,“你能聽到嗎?”。就這一句,也就表明是一次握手,此時如果B沒有收到,那麼A 接下來說的話 (也就是要傳送的資料) B根本就收不到。可是,A並不知道B能不能收到,因為B沒有給A回覆,所以接下來A只管自己嗨(直接給B傳送資料),而B一臉茫然,這樣資料就傳送不到B,就不能保證A和B對話的可靠性。

2.兩次握手的情況: A問B,“你能聽到嗎?”,此時B收到,然後給A回覆了一句“可以”,因為要保證B說的話A也要能收到,所以B也問A:你能聽到我嗎?”。好了,連線此時就已經結束。我們分析下,首先站在A的角度,A說的話是能保證B收到,因為B給A回覆了;那麼站在B的角度,B自己說的話不能保證A一定能收到,因為A並沒有給B回覆。所以如果只是兩次握手,然後就進行資料傳輸,很明顯,B傳送的資料A是很有可能收不到的。

3.三次握手的情況: 在兩次的基礎上,已經能保證A說的話B能收到。如果再進行一次握手,即B所說的話能給被A回覆,那麼B也就知道自己說的話A是能收到的。所以三次握手是保證可靠連線的最小次數。

總結一下:就是前兩次握手是保證A端資料的可靠傳輸,而後兩次握手是保證B端資料的可靠傳輸。

上面的例子是一個三次握手的原因,是為了幫助理解。

我們下來解釋如果不是三次握手而是兩次握手,就進行資料傳輸,會造成什麼影響?

關於為什麼A還要傳送一次確認呢?這主要是為了防止已失效的連線請求報文段突然又傳送到了B,因而產生錯誤。

所謂“已失效的連線請求報文段”是這樣產生的。考慮一種正常情況,A發出連線請求,但因連線請求報文丟失而未收到確認,於是A再重傳一次連線請求,後來收到了確認,建立了連線,資料傳輸完畢後,就釋放了連線。A共傳送了兩個連線請求報文段,其中第一個丟失,第二個到達了B.沒有“已失效的連線請求報文段。

現假定出現一種異常情況,即A發出的第一個連線請求報文段並沒有丟失,而是在某些網路結點長時間滯留了,以致延誤到連線釋放以後的某個時間才到達B.本來這是一個早已失效的報文段,但B收到此失效的連線請求報文段後,就誤認為是A又發出一次新的連線請求,於是就向A發出確認報文段,同意建立連線,假定不採用三次握手,那麼只要B發出確認,新的連線就建立了。
由於現在A並沒有發出建立連線的請求,因此不會理睬B的確認,也不會向B傳送資料,但B卻以為新的運輸連線已經建立了,並一直等待A發來資料,B的許多資源就這樣白自浪費了, 採用三次握手的辦法可以防止上述現象的發生,例如在剛才的情況下,A不會向B的確認發出確認,B由於收不到確認,就知道A並沒有要求建立連線。

四次揮手的基本過程

在這裡插入圖片描述

第一次揮手: 客戶端發起一個FIN和一個Seq=M,要求關閉客戶端到伺服器之間的資料傳遞,之後客戶端進入FIN_WAIT1狀態;

第二次揮手: 伺服器收到FIN後,傳送一個ACK=1,和ack=M+1表示知道了,進入CLOSE_WAIT狀態;時只是客戶端告訴服務端他沒有資料要傳送了,但並不代表服務端沒有資料可以傳送,也就是說此時服務端可能給另外的客戶端傳送資料。,這也就是為什麼是四次握手的原因,後面我們再介紹。

第三次揮手: 當伺服器的資料傳遞完後,再發送一個FIN和一個Seq=N來確定斷開連線,等待最後一個ACK的到來;此時服務端進入LAST_ACK狀態。

第四次揮手: 此時一直等待的客戶端接收到服務端FIN訊號後,表示伺服器也要斷開了,沒資料傳送了,便傳送一個ACK和Ack=N+1,之後就進入TIME_WAIT狀態,當服務端收到ACK後,服務端就徹底斷開連線,客戶端就被動斷開了。接著客戶端等待2個MSL(Max Segment Life, 報⽂最⼤⽣存時間時間) 後,才會進入CLOSED狀態。這就是大名鼎鼎的四次揮手!!!!!!

為什麼建立連線協議是三次握手,而關閉連線卻是四次握手呢?

這是因為服務端的 LISTEN 狀態下的 SOCKET 當收到 SYN 報文的建連請求後,它可以把 ACK 和 SYN(ACK 起應答作用,而 SYN 起同步作用)放在一個報文裡來發送。但關閉連線時,當收到對方的 FIN 報文通知時,它僅僅表示對方沒有資料傳送給你了,但是你還可以給對方傳送資料,也有這麼種可能,你還有一些資料在傳給對方的途中,所以你不能立馬關閉連線,也即你可能還需要把在傳輸途中的資料給對方之後,又或者,你還有一些資料需要傳輸給對方後,(再關閉連線)再發送FIN 報文給對方來表示你同意現在可以關閉連線了,所以它這裡的 ACK 報文和 FIN 報文多數情況下都是分開發送的。

為什麼 TIME_WAIT 狀態還需要等 2MSL後才能返回到 CLOSED 狀態?

1.為什麼要等?

這是因為雖然雙方都同意關閉連線了,而且握手的 4 個報文也都協調和傳送完畢,按理可以直接回到 CLOSED 狀態(就好比從 SYN_SEND 狀態到 ESTABLISH 狀態那樣);但是因為我們必須要假想網路是不可靠的,你無法保證你最後傳送的 ACK 報文會一定被對方收到, 因此對方處於 LAST_ACK 狀態下的 SOCKET 可能會因為超時未收到 ACK 報文而重發 FIN 報文,此時如果客戶端直接進入到ClLOSED狀態,那麼它是肯定不會收到第2個服務端傳送過來的FIN包,所以這個 TIME_WAIT 狀態的作用等待第2個FIN包,並且用來重發可能丟失的的最後一個 ACK 報文。

2.為什麼是2個MSL?

等待兩個msl時間,是為了讓網路上迷途的報文徹底的消失,防止對新的連線造成影響。TIME_WAIT持續存在2MSL的話就能保證在兩個傳輸⽅向上的尚未被接收或遲到的報⽂段都已經消失(否則伺服器⽴刻重啟, 可能會收到來⾃上⼀個程序的遲到的資料, 但是這種資料很可能是錯誤的)。