PS:TCP協議中,主動發起請求的一端稱爲『客戶端』,被動鏈接的一端稱爲『服務端』。無論是客戶端仍是服務端,TCP鏈接創建完後都能發送和接收數據。算法
起初,服務器和客戶端都爲CLOSED狀態。在通訊開始前,雙方都得建立各自的傳輸控制塊(TCB)。
服務器建立完TCB後遍進入LISTEN狀態,此時準備接收客戶端發來的鏈接請求。數據庫
第一次握手
客戶端向服務端發送鏈接請求報文段。該報文段的頭部中SYN=1,ACK=0,seq=x。請求發送後,客戶端便進入SYN-SENT狀態。後端
第二次握手
服務端收到鏈接請求報文段後,若是贊成鏈接,則會發送一個應答:SYN=1,ACK=1,seq=y,ack=x+1。
該應答發送完成後便進入SYN-RCVD狀態。緩存
第三次握手
當客戶端收到鏈接贊成的應答後,還要向服務端發送一個確認報文段,表示:服務端發來的鏈接贊成應答已經成功收到。
該報文段的頭部爲:ACK=1,seq=x+1,ack=y+1。
客戶端發完這個報文段後便進入ESTABLISHED狀態,服務端收到這個應答後也進入ESTABLISHED狀態,此時鏈接的創建完成!服務器
爲何鏈接創建須要三次握手,而不是兩次握手?
防止失效的鏈接請求報文段被服務端接收,從而產生錯誤。網絡
PS:失效的鏈接請求:若客戶端向服務端發送的鏈接請求丟失,客戶端等待應答超時後就會再次發送鏈接請求,此時,上一個鏈接請求就是『失效的』。併發
若創建鏈接只需兩次握手,客戶端並無太大的變化,仍然須要得到服務端的應答後才進入ESTABLISHED狀態,而服務端在收到鏈接請求後就進入ESTABLISHED狀態。此時若是網絡擁塞,客戶端發送的鏈接請求遲遲到不了服務端,客戶端便超時重發請求,若是服務端正確接收並確認應答,雙方便開始通訊,通訊結束後釋放鏈接。此時,若是那個失效的鏈接請求抵達了服務端,因爲只有兩次握手,服務端收到請求就會進入ESTABLISHED狀態,等待發送數據或主動發送數據。但此時的客戶端早已進入CLOSED狀態,服務端將會一直等待下去,這樣浪費服務端鏈接資源。socket
TCP鏈接的釋放一共須要四步,所以稱爲『四次揮手』。
咱們知道,TCP鏈接是雙向的,所以在四次揮手中,前兩次揮手用於斷開一個方向的鏈接,後兩次揮手用於斷開另外一方向的鏈接。tcp
第一次揮手
若A認爲數據發送完成,則它須要向B發送鏈接釋放請求。該請求只有報文頭,頭中攜帶的主要參數爲:
FIN=1,seq=u。此時,A將進入FIN-WAIT-1狀態。性能
第二次揮手
B收到鏈接釋放請求後,會通知相應的應用程序,告訴它A向B這個方向的鏈接已經釋放。此時B進入CLOSE-WAIT狀態,並向A發送鏈接釋放的應答,其報文頭包含:
ACK=1,seq=v,ack=u+1。
A收到該應答,進入FIN-WAIT-2狀態,等待B發送鏈接釋放請求。
第二次揮手完成後,A到B方向的鏈接已經釋放,B不會再接收數據,A也不會再發送數據。但B到A方向的鏈接仍然存在,B能夠繼續向A發送數據。
第三次揮手
當B向A發完全部數據後,向A發送鏈接釋放請求,請求頭:FIN=1,ACK=1,seq=w,ack=u+1。B便進入LAST-ACK狀態。
第四次揮手
A收到釋放請求後,向B發送確認應答,此時A進入TIME-WAIT狀態。該狀態會持續2MSL時間,若該時間段內沒有B的重發請求的話,就進入CLOSED狀態,撤銷TCB。當B收到確認應答後,也便進入CLOSED狀態,撤銷TCB。
爲何A要先進入TIME-WAIT狀態,等待2MSL時間後才進入CLOSED狀態?
爲了保證B能收到A的確認應答。
若A發完確認應答後直接進入CLOSED狀態,那麼若是該應答丟失,B等待超時後就會從新發送鏈接釋放請求,但此時A已經關閉了,不會做出任何響應,所以B永遠沒法正常關閉。
1 滑動窗口
數據的傳送過程當中極可能出現接收方來不及接收的狀況,這時就須要對發送方進行控制以避免數據丟失。利用滑動窗口機制能夠很方便地在TCP鏈接上對發送方的流量控制。TCP的窗口單位是字節,不是報文段,發送方的發送窗口不能超過接受方給出的接收窗口的數值
說明: 使發送方暫停發送的狀態將持續到主機B從新發出一個新的窗口值爲止,B向A發送的三個報文段都設置了ACK=1
考慮一種特殊的狀況,接收方若沒有緩存足夠使用,就會發送零窗口大小的報文,此時發送方將發送窗口設置爲0,中止發送數據; 以後接收方有足夠緩存,發送了非零窗口大小的報文,可是這個報文中途丟失,那麼發送方的發送窗口就一直爲0致使死鎖。爲此,TCP爲每個鏈接設有一個持續計時器(Persistence Timer).當TCP鏈接的一方收到對方的零窗口通知時就啓動持續計數器。若持續計時器時間到期,就發送一個零窗口探測報文段(攜有1字節的數據),那麼收到這個報文段的一方就從新設置持續計數器,給出如今的窗口值。
TCP規定,即便設置爲零窗口,也必須接收如下幾種報文段: 零窗口探測報文段, 確認報文段和攜帶緊急數據的報文段
2 發送時機
(1) TCP維持一個變量MSS,等於最大報文段長度。只要緩衝區存放的數據達到MSS字節時,就組裝成了一個TCP報文段發送出去。
(2) 由發送方的應用進程指明要發送的報文段,即:TCP支持推送操做。
(3) 發送方的一個計時器期限到了,這時就把當前已有的緩存數據裝入報文段(但長度不能超過MSS大小)發送出去。
3 Nagle算法
發送方把第一個數據字節發送出去,把後面到達的數據字節緩存起來。當發送方接收對第一個數據字符的確認後,再把發送緩存中的全部數據組裝成一個報文段再發送出去,同時繼續對隨後到達的數據進行緩存。只有在收到前一個報文段的確認後,才繼續發送下一個報文段。TCP規定一個鏈接最多隻能有一個未被確認的未完成的小分組,在該分組的確認到達以前不能發送其餘的小分組。當數據到達較快而網絡速率較慢時,用這樣的方法可明顯地減小所用的網絡帶寬。
Nagle算法還規定: 當到達的數據已達到發送窗口大小的一半或已經達到報文段的最大長度,就可當即發送一個報文段
4 延遲ACK:
若是tcp對每一個數據包都發送一個ack確認,那麼只是一個單獨的數據包爲了發送一個ack代價比較高,因此tcp會延遲一段時間,若是這段時間內有數據發送到對端,則捎帶發送ack,若是在延遲ack定時器觸發時候,發現ack還沒有發送,則當即單獨發送;
延遲ACK好處:
(1) 避免糊塗窗口綜合症;
(2) 發送數據的時候將ack捎帶發送,沒必要單獨發送ack;
(3) 若是延遲時間內有多個數據段到達,那麼容許協議棧發送一個ack確認多個報文段;
5 當Nagle趕上延遲ACK:
試想以下典型操做,寫-寫-讀,即經過多個寫小片數據向對端發送單個邏輯的操做,兩次寫數據長度小於MSS,當第一次寫數據到達對端後,對端延遲ack,不發送ack,而本端由於要發送的數據長度小於MSS,因此nagle算法起做用,數據並不會當即發送,而是等待對端發送的第一次數據確認ack;這樣的狀況下,須要等待對端超時發送ack,而後本段才能發送第二次寫的數據,從而形成延遲
6 糊塗窗口綜合症
TCP接收方的緩存已滿,而交互式的應用進程一次只從接收緩存中讀取1字節(這樣就使接收緩存空間僅騰出1字節),而後向發送方發送確認,並把窗口設置爲1個字節(但發送的數據報爲40字節的的話)。而後,發送方又發來1個字節的數據(發送方的IP數據報是41字節),接收方發回確認,仍然將窗口設置爲1個字節。這樣,網絡的效率很低。要解決這個問題,可以讓接收方等待一段時間,使得或者接收緩存已有足夠空間容納一個最長的報文段或者等到接收方緩存已有一半的空閒空間。只要出現這兩種狀況,接收方就發回確認報文,並向發送方通知當前的窗口大小。此外,發送方也不要發送過小的報文段,而是把數據報積累成足夠大的報文段,或達到接收方緩存的空間的一半大小。
網絡擁塞現象是指到達通訊子網中的某一部分數量過多,使得該部分網絡來不及處理,以至引發這部分乃至整個網絡性能降低的現象,嚴重時致使網絡通訊業務陷入停頓出現死鎖現象。擁塞控制時經過擁塞窗口處理網絡擁塞現象的一種機制
發送報文段速率肯定: [1]. 全局考慮防止擁塞 <- - 擁塞窗口 (Congestion Window) - -> 發送端流量控制,發送端根據本身估計的網絡擁塞程度而設置的窗口值; [2]. 接收端的接收能力 <- - 接收窗口 (Reciver Window) - -> 接收端流量控制,接收端根據目前的接收緩存大小所許諾的最新窗口值; 發送方窗口的上限值 = Min [ rwind, cwind ] 當rwind < cwind 時,接收方的接收能力限制發送方窗口的最大值。 當cwind < rwind 時,網絡的擁塞限制發送方窗口的最大值。
針對擁塞控制共有4種算法: 慢啓動 擁塞避免 快重傳 快恢復。咱們假定:(1) 數據單方向傳送,而另一個方向只傳送確認。 (2)接收方老是有足夠大的緩存空間,由於發送窗口的大小由網絡的擁塞程度來決定
發送方維護一個擁塞窗口cwind的狀態變量,擁塞窗口的大小取決於網絡的擁塞程度,動態變化。經過逐漸增長cwind的大小來探測可用的網絡容量,防止鏈接開始時採用不合適的發送量致使網絡擁塞。
當主機開始發送數據時,若是經過較大的發送窗口當即將所有數據字節都注入到網絡中,因爲不清楚網絡情況,有可能引發網絡擁塞。較好的方法是試探,從小到大逐漸增大發送端擁塞窗口的cwind數值。
例子: 開始發送方先設置cwnd=1,發送第一個報文段M1,接收方接收到M1後,ACK返回給發送端,發送端將cwnd增長到2,接着發送方發送M2,M3,再次接受到ACK後將cwnd增長到4...慢啓動算法每通過一個傳輸輪次,擁塞窗口cwind就加倍。
當rwind足夠大時,爲防止擁塞窗口cwind的增加引發網絡擁塞,還須要另一個變量,慢開始門限ssthresh
當cwind < ssthresh時,使用上述慢啓動算法;當cwind > ssthresh時,中止使用慢啓動算法,改成擁塞避免算法
讓擁塞窗口cwind緩慢地增大,沒通過一個往返時間RTT就把發送方的擁塞窗口cwind+1, 而不是加倍。這樣擁塞窗口cwind線性緩慢增加,比慢開始算法的擁塞窗口增加速率緩慢地多。
不管慢啓動開始階段仍是在擁擠避免階段,只要發送方判斷網絡出現擁塞(沒收到ACK),就把慢啓動門限ssthresh設置爲出現擁塞時的cwind的一半。而後把擁塞窗口cwind從新設置爲1,執行慢啓動算法。目的是迅速減小主機發送到網絡中的分組樹,使得發生阻塞的路由器有足夠的時間把隊列中積壓的分組處理完畢
控制過程(見上圖):
(1) TCP鏈接初始化,將擁塞窗口cwind設置爲1個報文段,即cwind = 1
(2) 執行慢開始算法,cwind按指數規律增加,直到cwind == ssthresh時,開始擁塞避免算法,cwind按線性規律增加
(3) 當網絡發生阻塞,把ssthresh值更新爲擁塞前cwind的一半(12 = 24/2),cwind從新設置爲1,再按照(2)執行
注意⚠️:
擁塞避免是由指數增加拉低到線性增加,下降出現擁塞的可能,並非能徹底避免網絡擁塞
慢開始算法只是在TCP鏈接創建和網絡出現超時時才使用
一條TCP鏈接有時會因等待重傳計時器的超時而空閒較長的時間,慢開始和擁塞避免沒法很好地解決這類問題,所以提出了快重傳和快恢復的擁塞控制方法
爲使發送方及早知道有報文沒有達到對方,快重傳算法首先要求接受方每收到一個報文段後就當即發出重複確認。快重傳算法並不是取消了重傳機制,只是在某些狀況下更早地重傳丟失的報文段。即,當TCP源端收到3個相同的ACK確認時,即認爲有數據包丟失,則源端重傳丟失的數據包,而沒必要等待RTO(Retransmission Timeout)超時。因爲發送方儘早重傳未被確認的報文段。所以,採用快重傳後可使整個網絡吞吐量提升20%
快重傳算法要求首先接收方收到一個失序的報文段後就馬上發出重複確認,而不要等待本身發送數據時才進行捎帶確認。接收方成功的接受了發送方發送來的M一、M2而且分別給發送了ACK,如今接收方沒有收到M3,而接收到了M4,顯然接收方不能確認M4,由於M4是失序的報文段。若是根據可靠性傳輸原理接收方什麼都不作,可是按照快速重傳算法,在收到M四、M5等報文段的時候,不斷重複的向發送方發送M2的ACK,若是接收方一連收到三個重複的ACK,那麼發送方沒必要等待重傳計時器到期,因爲發送方儘早重傳未被確認的報文段。
控制過程(見上圖):
(1) 當發送方連續收到3個重複確認時,執行"乘法減少"算法,慢啓動門限減半,爲了預防網絡發生擁塞
(2) 因爲發送方如今認爲網絡極可能沒有發生擁塞,所以不執行慢啓動。而是把cwind值設爲新的門限值,而後執行擁塞避免算法,cwind值線性增大,避免了當網絡擁塞不夠嚴重時採用"慢啓動"算法而形成過大地減少發送窗口尺寸的現象,這就是快恢復。
在TCP/IP協議中,有個FLAGS字段,這個字段有如下幾個標識: SYN, FIN, ACK, PSH, RST, URG, seq, ack等
SYN(synchronous) 表示創建鏈接
FIN(finish) 表示關閉鏈接
ACK(acknowledgement) 表示響應
PSH(push) 表示有數據傳輸
RST(reset) 表示鏈接重置
URG(urgent) 表示緊急
seq(sequence number) 表示確認號碼
ack(acknowledge number) 表示確認號碼
長鏈接能夠省去較多TCP創建和關閉的操做,減小浪費,節約資源。對於頻繁請求資源的客戶來講,適合使用長鏈接。不過這裏存在一個問題,存活功能的探測週期太長,還有就是它知識探測TCP鏈接的存活,遇到惡意的鏈接時,保活功能就不夠使了。在長鏈接的應用場景下,client端通常不會主動關閉它們之間的鏈接,client與server以前的鏈接若是一直不關閉的話,會存在一個問題,隨着客戶端鏈接愈來愈多,server會扛不住,這時候server端須要採起一些策略:
(1) 關閉一些長時間沒有讀寫事件發生的鏈接,這樣能夠避免一些惡意鏈接致使server端服務受損
(2) 若是條件再容許能夠以"客戶端機器"粒度,限制每一個客戶端的最長鏈接數,這樣能夠徹底避免某個蛋疼的客戶端連累後端服務
相比之下,短連接對於服務端來講管理比較簡單,存在的鏈接都是有用的鏈接,不須要額外的控制手段。但若是客戶請求頻繁,將在TCP的創建和關閉操做上浪費時間和帶寬。
長鏈接多用於操做頻繁,點對點的通信,並且鏈接數不能太多的狀況。每一個TCP鏈接都須要三次握手,這須要時間,若是每一個操做都是先鏈接,再操做的話那麼處理速度會下降不少,因此每一個操做完後都不斷開,再次處理時直接發送數據包就OK了,不用創建TCP鏈接。例如: 數據庫的鏈接用長鏈接,若是用短鏈接頻繁的通訊會形成socket錯誤,並且頻繁的socket建立也是對資源的浪費。
而像WEB網站的http服務通常都用短連接,由於長鏈接對於服務端來講會耗費必定的資源,而像WEB網站這麼頻繁的成千上萬甚至上億客戶端的鏈接用短鏈接會更省一些資源,若是用長鏈接,並且同時有成千上萬的用戶,若是每一個用戶都佔用一個鏈接的話,那可想而知吧。因此併發量大,但每一個用戶無需頻繁操做狀況下需用短連好。