01、簡介
TCP(Transmission Control Protocol 傳輸控制協議)是一種基于IP的傳輸層協議,TCP協議面向連接、正面確認與重傳、緩沖機制、流量控制、差錯控制、擁塞控制,可保證高可靠性(資料無丟失、資料無失序、資料無錯誤、資料無重復到達)傳輸層協議,

?
上圖形象展示了TCP協議是基于IP協議的傳輸層協議,對于IP協議的詳解,請看《IP協議詳解》,
02、TCP協議頭
TCP協議頭資料個數如下:

?
埠號[16bit]
我們知道,網路實作的是不同主機的行程間通信,在一個作業系統中,有很多行程,當資料到來時要提交給哪個行程進行處理呢?這就需要用到埠號,在TCP頭中,有源埠號(SourcePort)和目標埠號(DestinationPort),源埠號標識了發送主機的行程,目標埠號標識接受方主機的行程,埠是由互聯網分配號碼管理局(IANA)分配的,具體請看《UDP協議詳解》,
序號[32bit]
序號分為發送序號(SequenceNumber)和確認序號(AcknowledgmentNumber),
發送序號:用來標識從TCP源端向TCP目的端發送的資料位元組流,它表示在這個報文段中的第一個資料位元組的順序號,如果將位元組流看作在兩個應用程式間的單向流動,則 TCP用順序號對每個位元組進行計數,序號是32bit的無符號數,序號到達2∧32- 1后又從 0開始,當建立一個新的連接時,SYN標志變1,順序號欄位包含由這個主機選擇的該連接的初始順序號ISN(Initial Sequence Number),
確認序號:包含發送確認的一端所期望收到的下一個順序號,因此,確認序號應當是上次已成功收到資料位元組順序號加 1,只有ACK標志為1時確認序號欄位才有效,TCP為應用層提供全雙工服務,這意味資料能在兩個方向上獨立地進行傳輸,因此,連接的每一端必須保持每個方向上的傳輸資料順序號,
在wireshark的抓包檔案中,Seq表示發送序列號,Ack表示確認序列號,

?
偏移[4bit]
這里的偏移實際指的是TCP首部的長度,它用來表明TCP首部中32bit字的數目,通過它可以知道一個TCP包它的用戶資料是從哪里開始的,這個欄位占4bit,如4bit的值是0101,則說明TCP首部長度是5* 4 = 20位元組,所以TCP的首部長度最大為15* 4 = 60位元組,然而沒有可選欄位,正常長度為20位元組,

?
Reserved [3bit]
目前沒有使用,它的值都為0,注意:在比較舊的資料中顯示6bit的保留位元組,因為新的TCP協議使用了3個位作為標志,所以只剩下3個保留位,
標志[9bit]
上面說到增加了3位作標志位,增加的是:
NS: "nonce sum"簡寫,隨機和,該標簽用來保護不受發送者發送的突發的惡意隱藏報文的侵害,
CWR: "Congestion WindowReduced"簡寫,擁塞視窗減,發送方降低它的發送速率,發送者在接收到一個帶有ECEflag包時,將會使用CWRflag,
ECE: "ECN-Echo"簡寫,ECN表示ExplicitCongestion Notification(顯式擁塞通知),發送方接收到了一個更早的擁塞通告,表示TCPpeer有ECN能力,
其他6個標志位
URG: "urgent"簡寫,通知接收端處理在處理其他包前優先處理接收到的緊急報文(urgentpackets),緊急指標(urgentpointer)有效,
ACK: "Acknowledgment"簡寫,表示包已經被成功接收,確認序號有效,
PSH: "push"簡寫,通知接收端處理接收的報文,而不是將報文快取到buffer中,
RST:"reset"簡寫,重置連接標志,用于重置由于主機崩潰或其他原因而出現錯誤的連接,復位通訊請求,一般表示斷開一個連接,我們把含有RST標識的報文稱為復位報文段,
SYN:"Synchronisation"簡寫,表示三次握手建立連接的第一步,在建立連接時發送者發送的第一個包中設定flag值為SYN,我們把含有SYN標識的報文稱為同步報文段,
FIN: "finished"簡寫,表示發送者以及發送完資料,通常用在發送者通知對端,本端即將關閉,我們把含有FIN標識的報文稱為結束報文段
注意:他們中的多個可同時被置為1,

?
視窗大小(window)[16bit]
指的是接收視窗,視窗的大小,表示源方法最多能接受的位元組數,
校驗和[16bit]
校驗和覆寫了整個的TCP報文段:TCP首部和TCP資料,這是一個強制性的欄位,一定是由發端計算和存盤,并由收端進行驗證,
緊急指標[16bit]
只有當URG標志置為1時緊急指標才有效,緊急指標是一個正的偏移量,和序號欄位中的值相加表示緊急資料最后一個位元組的序號,TCP的緊急方式是發送端向另一端發送緊急資料的一種方式,
TCP選項
長度不定,但長度必須是32bits的整數倍,TCP頭部的最后一個選項欄位(options)是可變長的可選資訊,這部分最多包含40位元組,因為TCP頭部最長是60位元組(其中還包含前面討論的20位元組的固定部分),典型的TCP選項頭部結構如圖所示,

?
-
選項的第一個欄位kind說明選項的型別,有的TCP選項沒有后面兩個欄位,僅包含1位元組的kind欄位,
-
第二個欄位length(如果有的話)指定該選項的總長度,該長度包括kind欄位和length欄位占據的2位元組,
-
第三個欄位info(如果有的話)是選項的具體資訊,
常見的TCP選項有7種,如圖所示

?
1、kind=0,選項表結束(EOP)選項
一個報文段僅用一次,放在末尾用于填充,用途是說明:首部已經沒有更多的訊息,應用資料在下一個32位字開始處,
2、kind=1,空操作(NOP)選項
沒有特殊含義,一般用于將TCP選項的總長度填充為4位元組的整數倍,
3、kind=2,最大報文段長度(MSS)選項
TCP連接初始化時,通信雙方使用該選項來協商最大報文段長度,TCP模塊通常將MSS設定為(MTU-40)位元組(減掉的這40位元組包括20位元組的TCP頭部和20位元組的IP頭部),這樣攜帶TCP報文段的IP資料報的長度就不會超過MTU(假設TCP頭部和IP頭部都不包含選項欄位,并且這也是一般情況),從而避免本機發生IP分片,對以太網而言,MSS值是1460(1500-40)位元組,
4、kind=3,視窗擴大因子選項
TCP連接初始化時,通信雙方使用該選項來協商接收視窗的擴大因子,在TCP的頭部中,接收視窗大小是用16位表示的,故最大為65535位元組,但實際上TCP模塊允許的接收視窗大小遠不止這個數(為了提高TCP通信的吞吐量),視窗擴大因子解決了這個問題,
假設TCP頭部中的接收通告視窗大小是N,視窗擴大因子(移位數)是M,那么TCP報文段的實際接收通告視窗大小是N*(2^M),或者說N左移M位,注意,M的取值范圍是0~14,我們可以通過修改/proc/sys/net/ipv4/tcp_window_scaling內核變數來啟用或關閉視窗擴大因子選項,
和MSS選項一樣,視窗擴大因子選項只能出現在同步報文段中,否則將被忽略,但同步報文段本身不執行視窗擴大操作,即同步報文段頭部的接收視窗大小就是該TCP報文段的實際接收視窗大小,當連接建立好之后,每個資料傳輸方向的視窗擴大因子就固定不變了,
5、kind=4,選擇性確認(SelectiveAcknowledgment,SACK)選項
TCP通信時,如果某個TCP報文段丟失,則TCP會重傳最后被確認的TCP報文段后續的所有報文段,這樣原先已經正確傳輸的TCP報文段也可能重復發送,從而降低了TCP性能,SACK技術正是為改善這種情況而產生的,它使TCP只重新發送丟失的TCP報文段,而不用發送所有未被確認的TCP報文段,選擇性確認選項用在連接初始化時,表示是否支持SACK技術,我們可以通過修改/proc/sys/net/ipv4/tcp_sack 內核變數來啟用或關閉選擇性確認選項,
6、kind=5,SACK實際作業的選項
該選項的引數告訴發送方本端已經收到并快取的不連續的資料塊,從而讓發送端可以據此檢查并重發丟失的資料塊,每個塊邊沿(edgeofblock)引數包含一個4位元組的序號,其中塊左邊沿表示不連續塊的第一個資料的序號,而塊右邊沿則表示不連續塊的最后一個資料的序號的下一個序號,這樣一對引數(塊左邊沿和塊右邊沿)之間的資料是沒有收到的,因為一個塊資訊占用8位元組,所以TCP頭部選項中實際上最多可以包含4個這樣的不連續資料塊(考慮選項型別和長度占用的2位元組),
7、kind=8,時間戳選項
該選項提供了較為準確的計算通信雙方之間的回路時間(RoundTrip Time,RTT)的方法,從而為TCP流量控制提供重要資訊,我們可以通過修改/proc/sys/net/ipv4/tcp_timestamps內核變數來啟用或關閉時間戳選項,
以SYN的TCP選項的MSS為例的wireshark分析,其他的大家可以自行分析,

?
整個TCP協議頭部的wireshark決議,

?
03、TCP資料包的編號(SEQ)
一個包1400位元組,那么一次性發送大量資料,就必須分成多個包,比如,一個10MB的檔案,需要發送7100多個包,
發送的時候,TCP協議為每個包編號(sequencenumber,簡稱SEQ),以便接收的一方按照順序還原,萬一發生丟包,也可以知道丟失的是哪一個包,
第一個包的編號是一個亂數,為了便于理解,這里就把它稱為1號包,假定這個包的負載長度是100位元組,那么可以推算出下一個包的編號應該是101,這就是說,每個資料包都可以得到兩個編號:自身的編號,以及下一個包的編號,接收方由此知道,應該按照什么順序將它們還原成原始檔案,
這里的編號就是TCP頭中的確認號,wireshark顯示的Seq和Ack是wireshark重新編號的,

?
資料包1:發送序號:532420307(1),確認序號:2978637660(1),資料包長6
資料包2:發送序號:2978637660(1),確認序號:532420313(7),
備注:括號里是wireshark的編號,
可以發現:
資料包2的發送序號是資料包1的確認序號,
資料包2的確認序號是資料包1的發送序號+6,也就是加上資料包長,
符合上面的文字描述,
04、三次握手建立連接
三次握手建立連接程序:
a.請求端(通常稱為客戶)發送一個SYN段指明客戶打算連接的服務器的埠,以及初始序號(ISN,在這個例子中為1415531521),這個SYN段為報文段1,
b.服務器發回包含服務器的初始序號的SYN報文段(報文段2)作為應答,同時,將確認序號設定為客戶的ISN加1以對客戶的SYN報文段進行確認,一個SYN將占用一個序號,
c.客戶必須將確認序號設定為服務器的ISN加1以對服務器的SYN報文段進行確認(報文段3),
這三個報文段完成連接的建立,這個程序也稱為三次握手(three-wayhandshake),

?
用wirshark抓包如下:
![]()
?
可以看到三次握手確定了雙方間包的序號、最大接受資料的大小(window)以及MSS(MaximumSegment Size),
MSS = MTU - IP頭-TCP頭,MTU表示最大傳輸單元,我們在IP頭分析的時候會講到,它一般為1500個位元組,IP頭和TCP頭部帶可選選項的時候都是20個位元組,這樣的話MSS=1500- 20 -20 = 1460,
MSS限制了TCP包攜帶資料的大小,它的意思就是當應用層向傳輸層提交資料通過TCP協議進行傳輸時,如果應用層的資料大于MSS就必須分段,分成多個段,逐個的發過去,這部分內容是不是IP分片,不要和IP分片混淆了,IP分片是IP協議層的資料報分片,這是TCP的分片,IP協議分片詳細請看《IP協議詳解》,
我們wireshar抓包顯示MSS都是1460,這樣顯示不出來握手的協商機制,假設客戶端的MSS是4312,服務器的MSS是1460,那么握手程序中的協商可以下圖形象表示,

?
其中,第1 次和第2 次握手包的TCP 首部包含MSS 選項,互相通知對方網路介面能夠適應的MSS 的大小,然后雙方會使用較小的MSS 值進行傳輸,
前面講解TCP頭中flg中就有SYN標志,在wireshark抓包中也有顯示,

?
讀到這里,好像一切順理成章,決定既然互聯網“先驅”定義了三次握手建立,那么就是三次握手建立連接,可有些人會有疑問,為什么兩次握手不能,
比如A給B東西,
A說:我要和你建立,你準備好了嗎?
B說:好的,我準備好了,
A直接把東西給B,
這樣的邏輯在生活中好像一點毛病也沒有,但其實這樣是不行,3次握手完成兩個重要的功能,既要雙方做好發送資料的準備作業(雙方都知道彼此已準備好),也要允許雙方就初始序列號進行協商,這個序列號在握手程序中被發送和確認,
現在把三次握手改成僅需要兩次握手,死鎖是可能發生的,其實上面有個“坑”,那就是一開始我們限制了A給B東西,但實際的TCP通信中,連接建立了,可以客戶端主動和服務器通信,也可以服務器主動和客戶端通信,如果兩次握手,B收到A的握手申請,發送好的,我準備好了,這時候B在想,A如果收不到怎么辦,A到底有沒有收到啊,我(B)能不能向A發資料???
所以需要三次握手,
A說:我和你建立,你準備好了嗎?
B說:好的,我準備好了,
A說:我知道你準備好了(我也準備好了),
開始愉快的相互傳輸資料,

?
05、四次揮手斷開連接
四次揮手斷開連接程序:
a.現在的網路通信都是基于socket實作的,當客戶端將自己的socket進行關閉時,內核協議堆疊會向服務器自動發送一個FIN置位的包,請求斷開連接,我們稱首先發起斷開請求的一方稱為主動斷開方,
b.服務器端收到請客端的FIN斷開請求后,內核協議堆疊會立即發送一個ACK包作為應答,表示已經收到客戶端的請求,
c.服務器運行一段時間后,關閉了自己的socket,這個時候內核協議堆疊會向客戶端發送一個FIN置位的包,請求斷開連接,
d.客戶端收到服務端發來的FIN斷開請求后,會發送一個ACK做出應答,表示已經收到服務端的請求,

?
用wirshar抓包分析如下:
![]()
?
前面講解TCP頭中flg中就有FIN標志,在wireshark抓包中也有顯示,

?
下圖類比四次揮手程序:

?
這里有個問題,如果有同學自己wireshark抓包分析的話(我提供的wireshark檔案第一次通信也是這種情況),會發現下面情況:

?
怎么只有3次揮手,應用程式出問題了?wirshark自行”合并“了?為什么別人抓包就有四次揮手斷開?
這跟Wireshark沒有關系,跟實作有關,四次揮手,都知道是客戶端和服務器之間互動的四個報文,FIN、ACK、FIN、ACK,但抓包來看,卻不是每次如教科書說的那樣,首先要搞明白這個FIN報文的真正用途,FIN報文用在本端沒有資料發送給對方時,關閉從本端到對端的連接,但是并不影響從對方到本端的連接,也就是說本端仍然可以接收對方的資料,即發送通道關閉,接收通道正常,如果對方收到本端FIN報文時,對方的接收通道就會關閉,此時,如果對方也沒有資料發給本端,那么對方也會發送FIN給本端,用于關閉從對方到本端的連接,這時候就可能出現ACK和FIN合在一起的情況,當然,如果對方仍然有資料發送,那么就等資料發完,再發FIN來關閉連接,這時候就是四次揮手了,因此,四次揮手變成三次,跟wireshark沒關系,跟資料的收發雙方才有關系,從這也能看出tcp是雙工通信了,現在的很多的實作都是合并在一起,三個程序,主要是為了效率和安全,
TCP 連接必須經過時間2MSL 后才真正釋放掉(2MSL的時間的用意 --- 為了保證A 發送的最后一個ACK 報文段能夠到達B.防止“已失效的連接請求報文段”出現在本連接中.A在發送完最后一個ACK 報文段后,再經過時間2MSL,就可以使本連接持續的時間內所產生的所有報文段,都從網路中消失.這樣就可以使下一個新的連接中不會出現這種舊的連接請求報文段),
06、TCP可靠性的保證
TCP采用一種名為“帶重傳功能的肯定確認(positiveacknowledge withretransmission)”的技術作為提供可靠資料傳輸服務的基礎,這項技術要求接收方收到資料之后向源站回送確認資訊ACK,發送方對發出的每個分組都保存一份記錄,在發送下一個分組之前等待確認資訊,發送方還在送出分組的同時啟動一個定時器,并在定時器的定時期滿而確認資訊還沒有到達的情況下,重發剛才發出的分組,
下圖a表示帶重傳功能的肯定確認協議傳輸資料的情況,下圖a表示分組丟失引起超時和重傳,為了避免由于網路延遲引起遲到的確認和重復的確認,協議規定在確認資訊中稍帶一個分組的序號,使接收方能正確將分組與確認關聯起來,
下圖a可以看出,雖然網路具有同時進行雙向通信的能力,但由于在接到前一個分組的確認資訊之前必須推遲下一個分組的發送,簡單的肯定確認協議浪費了大量寶貴的網路帶寬,為此, TCP使用滑動視窗的機制來提高網路吞吐量,同時解決端到端的流量控制,

?
07、滑動視窗技術
TCP的滑動視窗主要有兩個作用,一是提供TCP的可靠性,二是提供TCP的流控特性,同時滑動視窗機制還體現了TCP面向位元組流的設計思路,
TCP的Window是一個16bit位欄位,它代表的是視窗的位元組容量,也就是TCP的標準視窗最大為2^16-1=65535個位元組,另外在TCP的選項欄位中還包含了一個TCP視窗擴大因子,option-kind為3,詳細請看上文,
滑動視窗技術是簡單的帶重傳的肯定確認機制的一個更復雜的變形,它允許發送方在等待一個確認資訊之前可以發送多個分組,
所以,TCP的滑動視窗的可靠性也是建立在“確認重傳”基礎上的,
TCP 滑動視窗分為:發送視窗和接收視窗,
發送方的發送快取內的資料都可以被分為4類:
-
已發送,已收到ACK
-
已發送,未收到ACK
-
未發送,但允許發送
-
未發送,但不允許發送
其中型別2和3都屬于發送視窗,
接收方的快取資料分為3類:
-
已接收
-
未接收但準備接收
-
未接收而且不準備接收
如下圖所示,發送方要發送一個分組序列,滑動視窗協議在分組序列中放置一個固定長度的視窗,然后將視窗內的所有分組都發送出去;當發送方收到對視窗內第一個分組的確認資訊時,它可以向后滑動并發送下一個分組;隨著確認的不斷到達,視窗也在不斷的向后滑動,

?
上面的解釋,對于不熟悉滑動視窗的同學,可能看不太明白,下面將詳細講述一下,
上面講解三次握手建立連接時說到,握手程序中商議了MSS,也就是每一包的資料長度,抓包中也顯示的確是1460位元組傳輸的,

?
但是1460位元組不是整數,不方便我們快速計算,下面講解將MSS假設為1000,這樣方面快速理解,
在進行資料傳輸時,如果傳輸的資料比較大(大于1000),就需要拆分為多個資料包進行發送,TCP協議需要對資料進行確認后,才可以發送下一個資料包,

?
從上圖中可以看到,發送端每發送一個資料包,都需要得到接收端的確認應答以后,才可以發送下一個資料包,這樣一來,就會在等待確認應答包環節浪費時間,為了避免這種情況,TCP引入了視窗概念,視窗大小指的是不需要等待確認應答包而可以繼續發送資料包的最大值,
例如,視窗大小為3,資料包的傳輸如圖所示,

?
從上圖中可以看到,發送端發送第一個資料包(1-1000),沒有等待對應的確認應答包,就繼續發送第二個資料包(1001-2000)和第三個包(2001-3000),當收到第3個資料包的確認應答包時,會連續發送3個資料包(3001-4000,4001-5000,5001-6000),當收到第6個資料包的確認應答包時,又會發送3個資料包(6001-7000,7001-8000,8001-9000),
以這種方式發送,就可以省去多個資料包(第1、2、4、5、7、8個)的確認應答包時間,從而避免了網路的吞吐量的降低,
這樣就引出了視窗的概念,視窗大小指的是可以發送資料包的最大數量,建議讀到這里,剛才對視窗不太理解的同學,向上翻翻,再理解一下滑動視窗的圖示,
那么,此時視窗就通過滑動的方式,向后移動,確保下一次發送仍然可以發送視窗大小的資料包,這樣的發送方式被稱為滑動視窗機制,設定視窗大小為3,滑動視窗機制原理如圖所示,

?
上圖中,每1000 個位元組表示一個資料包,發送端同時發送了3個資料包(2001-5000),接收端回應的確認應答包為“下一個發送4001”,表示接收端成功回應了前兩個資料包,沒有回應最后一個資料包,此時,最后一個資料包要保留在視窗中,
由于視窗大小為3,發送端除了最后一個包以外,還可以繼續發送下兩個資料包(5001-6000和6001-7000),視窗滑動到7001 處,
滑動視窗影片演示:點擊觀看
08、視窗滑動的資料重發
在進行資料包傳輸時,難免會出現資料丟失情況,這種情況一般分為兩種,
-
第一種,如果未使用滑動視窗機制,發送的資料包沒有收到確認應答包,那么資料都會被重發;如果使用了滑動視窗機制,即使確認應答包丟失,也不會導致資料包重發,
-
第二種,發送的資料包丟失,將導致資料包重發,
下面詳細介紹使用滑動視窗機制的兩種情況,
確認應答包丟失
這種情況指的是前面發送的資料包沒有收到對應的確認應答,當收到后面資料包的確認應答包,表示前面的資料包已經成功被接收端接收了,發送端不需要重新發送前面的資料包了,如圖所示,

?
下面分為5 部分對上圖進行講解,
1) 發送端第1 次發送資料包:這里設定的視窗大小為3,可以最大發送3 個資料包,發送端同時發送3 個資料包1-1000、1001-2000和2001-3000,
2) 接收端回傳確認應答包:接收端接收到這些資料,并給出確認應答包,資料包1-1000 和資料包2001-3000 的確認應答包沒有丟失,但是資料包1001-2000 的確認應答包丟失了,
3) 發送端第2 次發送資料包:發送端收到接收端發來的確認應答包,雖然沒有收到資料包1001-2000 的確認應答包,但是收到了資料包2001-3000 的確認應答包,判斷第一次發送的3 個資料包都成功到達了接收端,再次發送3 個資料包3001-4000、4001-5000和5001-6000,
4) 接收端回傳確認應答包:接收端接收到這些資料,并給出確認應答包,資料包3001-4000 和資料包4001-5000 的確認應答包丟失了,但是資料包5001-6000沒有丟失,
5) 發送端第3 次發送資料包:發送端收到接收端發來的確認應答包,查看到資料包5001-6000 收到了確認應答包,判斷第2 次發送的3 個資料包都成功到達了接收端,再次發送3 個資料包6001-7000、7001-8000和8001-9000,
發送資料包丟失
這種情況指的是發送端發送的部分資料包沒有達到接收端,那么,如果在接收端收到的資料包,不是本應該要接收的資料包,那么就會給發送端回傳訊息,告訴發送端自己應該接收的資料包,
如果發送端連續收到3 次這樣的資料包,就認為該資料包成功發送到接收端,這時就開始重發該資料包,如圖所示,

?
下面分為7 部分對上圖進行講解,
1) 發送端發送資料包:這里視窗大小為4,發送端發送4 個資料包,分別為1-1000、1001-2000、2001-3000和3001-4000,
2) 接收端回傳確認應答包:接收端接收到這些資料,并給出確認應答包,接收端收到了資料包1-1000,回傳了確認應答包;收到了資料包1001-2000,回傳了確認應答包;但是資料包2001-3000,在發送程序中丟失了,沒有成功到達接收端,資料包3001-4000 沒有丟失,成功到達了接收端,但是該資料包不是接收端應該接收的資料包,資料包2001-3000 才是真正應該接收的資料包,因此收到資料包3001-4000 以后,接收端第一次回傳下一個應該發送2001 的資料包的確認應答包,
3) 發送端發送資料包:發送端仍然繼續向接收端發送4 個資料包,分別為4001-5000、5001-6000、6001-7000和7001-8000,
4) 接收端回傳確認應答包:接收端接收到這些資料,并給出確認應答包,當接收端收到資料包4001-5000 時,發現不是自己應該接收的資料包2001-3000,第二次回傳下一個應該發送2001 的資料包的確認應答包,當接收端收到資料包5001-6000 時,仍然發現不是自己應該接收的資料包2001-3000,第三次回傳下一個應該發送2001的資料包的確認應答包,以此類推直到接收完所有資料包,接收端都回傳下一個應該發送2001 的資料包的確認應答包,
5) 發送端重發資料包:發送端連續3 次收到接收端發來的下一個應該發送2001 的資料包的確認應答包,認為資料包2001-3000 丟失了,就進行重發該資料包,
6) 接收端收到重發資料包:接收端收到重發資料包以后,查看這次是自己應該接收的資料包2001-3000,并回傳確認應答包,告訴發送端,下一個該接收8001 的資料包了,
7) 發送端發送資料包:發送端收到確認應答包后,繼續發送視窗大小為4 的資料包,分別為8001-9000、9001-10000、10001-11000和11001-12000,
09、TCP流控制
在使用滑動視窗機制進行資料傳輸時,發送方根據實際情況發送資料包,接收端接收資料包,但是,接收端處理資料包的能力是不同的,
1)如果視窗過小,發送端發送少量的資料包,接收端很快就處理了,并且還能處理更多的資料包,這樣,當傳輸比較大的資料時需要不停地等待發送方,造成很大的延遲,
2)如果視窗過大,發送端發送大量的資料包,而接收端處理不了這么多的資料包,這樣,就會堵塞鏈路,如果丟棄這些本應該接收的資料包,又會觸發重發機制,
3) 為了避免這種現象的發生,TCP提供了流控制,所謂的流控制就是使用不同的視窗大小發送資料包,發送端第一次以視窗大小(該視窗大小是根據鏈路帶寬的大小來決定的)發送資料包,接收端接收這些資料包,并回傳確認應答包,告訴發送端自己下次希望收到的資料包是多少(新的視窗大小),發送端收到確認應答包以后,將以該視窗大小進行發送資料包,
TCP 流控制程序如圖所示,

?
為了方便講解,將上圖以發送端發送資料包進行分隔,將其分為3 部分進行講解,
第一部分
發送端根據當前鏈路帶寬大小決定發送資料包的視窗大小,這里,視窗大小為3,表示可以發送3 個資料包,因此發送端發送了3 個資料包,分別為1-1000、1001-2000和2001-3000,
接收端接收這些資料包,但是只能處理2 個資料包,第3 個資料包2001-3000 沒有被處理,因此回傳確認應答包,設定視窗大小為2,告訴發送端自己現在只能處理2個資料包,下一次請發送2 個資料包,
第二部分
發送端接收到確認應答包,查看到接收端回傳視窗大小為2,知道接收端只處理了2個資料包,發過去的第3 個資料包2001-3000 沒有被處理,這說明此時接收端只能處理2 個資料包,第3 個資料包還需要重新發送,
因此發送端發送2 個資料包2001-3000 和3001-4000,接收端收到這兩個資料包并進行了處理,此時,還是只能處理2 個視窗,繼續向發送端發送確認應答包,設定視窗為2,告訴發送端,下一個應該接收4001 的資料包,
第三部分
發送端接收到確認應答包,查看到接收端回傳視窗大小為2,說明接收端接收了上次發送的2 個資料包,此時仍然可以處理2 個資料包,繼續發送資料包4001-5000 和5001-6000,
如果在接收端回傳的確認應答包中,視窗設定為0,則表示現在不能接收任何資料,這時,發送端將不會再發送資料包,只有等待接收端發送視窗更新通知才可以繼續發送資料包,
如果這個更新通知在傳輸中丟失了,那么就可能導致無法繼續通信,為了避免這樣的情況發生,發送端會時不時地發送視窗探測包,該包僅有1個位元組,用來獲取最新的視窗大小的資訊,
原理如圖所示,

?
下面介紹上圖所示的獲取視窗更新資料包的原理,
1) 發送端發送資料,發送端以視窗大小為2,發送了2 個資料包,分別為4001-5000和5001-6000,接收端接收到這些資料以后,緩沖區滿了,無法再處理資料,于是向發送端回傳確認應答包,告訴它下一個接收6001 的資料,但是現在處理不了資料,先暫停發送資料,設定視窗大小為0,
2) 發送端暫停發送資料,發送端收到確認應答包,查看到下一次發送的是6001 的資料,但視窗大小為0,得知接收端此時無法處理資料,此時,不進行發送資料,進入等待狀態,
3)接收端發送視窗大小更新包,當接收端處理完發送端之前發來的資料包以后,將會給發送端發送一個視窗大小更新包,告訴它,此時可以發送的資料包的數量,這里設定視窗大小為3,表示此時可以處理3 個資料包,但是該資料包丟失了,沒有發送到發送端,
4) 發送端發送視窗探測包,由于視窗大小更新包丟失,發送端的等待時間超過了重發超時時間,此時,發送端向接收端發送一個視窗探測包,大小為1 位元組,這里是6001,
5) 接收端再次發送視窗大小更新包,接收端收到發送端發來的探測包,再次發送視窗大小更新包,視窗大小為3,
6) 發送端發送資料,發送端接收到視窗大小更新包,查看到應該發的是6001 的資料包,視窗大小為3,可以發送3 個資料包,因此發送了資料包,分別為6001-7000、7001-8000和8001-9000,
10、網線“斷”了怎么辦
對于TCP鏈接來說,他們之間一旦建立了連接,那么可以一直沒有訊息通訊,TCP連接的雙方都沒有向對方發送資料,則在兩個TCP模塊之間不交換任何資訊,
只要兩端的主機沒有被重啟,則連接依然保持建立,不管中間路由器可以崩潰和重啟,還是電話線被掛斷再連通,這意味著我們可以啟動一個客戶與服務器建立一個連接,然后離去數小時、數天、數個星期或者數月,而連接依然保持,
這對于客戶端來說,倒還好一點,畢竟不會有那么多的連接被占用,對于服務器來說,就是一個很糟糕的事情,這種連接無疑是一種僵尸連接,平白無辜的占用著服務器的資源,一旦這種連接非常多,服務器往往會因為連接數量的限制,導致沒有辦法接入新的客戶端,
這個時候,其實就需要一種定時探測對端連接是否還存活的機制存在,如此以來彼此都能知道對方的狀態,是否還能繼續使用,
這種機制,對于TCP來說,就是TCP的保活機制,TCP還設有一個保活計時器,服務器每收到一次客戶端的請求后都會重新復位這個計時器,時間通常是設定為2小時,若兩小時還沒有收到客戶端的任何資料,服務器就會發送一個探測報文段,以后每隔75秒鐘發送一次,俗稱“心跳”,若一連發送10個探測報文仍然沒反應,服務器就認為客戶端出了故障,接著就關閉連接,
TCP具有保活器,但我建議在應用層最好還要設計一個“心跳”用來維持TCP連接,時間間隔可自行確定,再插一嘴,具有保活器的TCP就是長連接,
長連接:建立一個連接,多個請求復用這個連接,一直用同一個鏈接傳輸資料,最后再關閉連接,
短連接:建立一個連接,傳輸一個請求,發送完資料后就關閉連接,
TCP具有保活器優點:
1.在連接兩個端系統的網路出現臨時故障的時候,保活選項會引起一個 實際上很好的連接終止,例如,如果在一個中間路由器崩潰并重新啟動時發送保活探查,那么TCP會認為客戶的主機已經崩潰,而實際上所發生的并非如此,
2.保活功能主要是為服務器應用程式提供的,服務器應用程式希望知道客戶主機是否崩潰,從而可以代表客戶使用資源,及時回收這些資源,
TCP具有保活器缺點:
保活并不是TCP規范中的一部分,HostRequirements RFC提供了3個不使用保活定時器的理由:
1)在出現短暫差錯的情況下,這可能會使一個非常好的連接釋放掉;
2)它們耗費不必要的帶寬;
3)在按分組計費的情況下會在互聯網上花掉更多的錢,
wireshark抓包檔案:
鏈接:https://pan.baidu.com/s/1AYU7lrbjE5zaBdhb76irqg 提取碼:yxbf
點擊查看本文所在的專輯,STM32F207網路開發
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/270505.html
標籤:嵌入式
