天天看點

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協定的分段各部分的内容。

Linux網絡程式設計---詳解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協定的分段,下來我們看看三次握手的過程。

三次握手的基本過程

Linux網絡程式設計---詳解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并沒有要求建立連接配接。

四次揮手的基本過程

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

第一次揮手: 用戶端發起一個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的話就能保證在兩個傳輸⽅向上的尚未被接收或遲到的報⽂段都已經消失(否則伺服器⽴刻重新開機, 可能會收到來⾃上⼀個程序的遲到的資料, 但是這種資料很可能是錯誤的)。

繼續閱讀