史上最全TCP機制

tcp和udp的區別

(基於連接vs無連接)tcp是面向連接的(三次握手;四次揮手);udp不是面向連接的
(重量級vs輕量級)tcp是一個重量級的協議;udp則是輕量級的協議。一個tcp數據報的報頭大小最少20字節,udp數據報的包頭固定8個字節
(可靠性)tcp交付保證:如果消息在傳輸中丟失,那麼它將重發;udp沒有交付保證,一個數據包在運輸過程中可能丟失。
(有序性)消息到達網絡的另一端可能是無序的,tcp協議將爲你拍好序。Udp不提供任何有序性的保證。
(速度)tcp慢,適合傳輸大量數據;udp快,適合傳輸少量數據。
(流量控制和擁塞控制)TCP有流量控制和擁塞控制,udp沒有。
tcp面向字節流,udp面向報文
tcp只能單播,不能發送廣播和組播;udp可以廣播和組播。
Tcp應用:郵件傳輸 udp應用:qq聊天、qq視頻

流量控制和擁塞控制:

流量控制:就是讓發送方發送速率不要太快,要讓接收方來的及接收。

擁塞控制:防止過多的數據注入到網絡中,這樣可以使網絡中的路由器或鏈路不致過載。擁塞控制所要做的都有一個前提:網絡能夠承受現有的網絡負荷。擁塞控制是一個全局性的過程,涉及到所有的主機、路由器,以及與降低網絡傳輸性能有關的所有因素。

確認應答機制(ACK)

在這裏插入圖片描述
TCP將每個字節的數據都進行了編號,即爲序列號。
每一個ACK都帶有對應的確認序列號,意思是告訴發送者,我已經收到了哪些數據,下一次你從哪裏開始發。

超時重傳機制

在這裏插入圖片描述
主機A發送數據給B以後,可能因爲網絡擁堵等原因,數據無法到達主機B;
如果主機A在一個特定時間間隔內沒有收到B發來的確認應答,就會進行重發。

但是,主機A未收到主機B發來的確認應答,也可能是因爲ACK丟失了:
在這裏插入圖片描述
因此主機B會收到很多重複數據,那麼TCP協議需要能夠識別出哪些包是重複的包,並且將這些重複的包丟棄掉,這時我們就可以利用前面提到的序列號,就可以很容易做到去重的效果。

那麼超時的時間如何確定呢?最理想的情況下,找到一個最小的時間,保證“確認應答一定能在這個時間內返回”,但是這個時間的長短隨着網絡環境的不同是有差異的。如果超時時間設的太長,會影響整體的重傳效率;如果超時時間設的太短,有可能會頻繁地發送重複的包。

TCP爲了保證無論在何種情況下都能比較高性能的通信,因此會動態計算這個最大超時時間。Linux中,超時以500ms爲一個單位進行控制,每次判定超時重發的超時時間都是500ms的整數倍。若重發一次之後仍得不到應答,等待2500ms後再進行重傳;如果仍得不到應答,等待4500ms進行重傳,依次類推,以指數形式遞增。累計到一定的重傳次數,TCP認爲網絡或者對端主機出現異常,強制關閉連接。

滑動窗口

剛纔我們討論了確認應答(ACK)機制,對每一個發送的數據段,都要給一個ACK確認應答,收到ACK後再發送下一個數據段。這樣做有一個較大的缺點,就是性能較差,尤其是數據往返時間較長的情況下。
在這裏插入圖片描述
既然一收一發的方式性能較低,那麼我們可以一次發送多條數據,這樣就可以大大的提高性能。這樣做的本質其實就是將多個段的等待時間重疊在一起了。
在這裏插入圖片描述
窗口大小指的是無需等待確認應答而可以繼續發送的數據的最大值,這個最大值由接收方的接收緩衝區大小決定。上圖所示的窗口大小即爲4000個字節(四個段)。

窗口大小是如何確定的呢?其實TCP在建立連接時,就已經協商好了窗口大小。

發送前四個段時,不需要等待任何ACK,直接發送。收到第一個ACK後,滑動窗口向後移動,繼續發送第五個段的數據,依次類推。

操作系統內核爲了維護這個滑動窗口,需要開闢發送緩衝區來記錄當前還有哪些數據沒有應答;只有確認應答過的數據才能從緩衝區刪掉。

窗口越大,則網絡的吞吐率就越高。
在這裏插入圖片描述
這裏我們來探討一種情形:
當主機A收到5001的應答,但未收到3001、4001的應答時,我們可以考慮兩種情況:
(1)3001、4001的應答較慢,5001的應答較快,所以先到達;
(2)3001、4001的應答丟失了;
這兩種情況其實都不影響,因爲收到5001的應答,就表示前5000個數據全收到了。

在滑動窗口中,如果出現了丟包,如何進行重傳呢?我們還是分兩種情況來討論:
(1)數據報已經抵達,ACK被丟失了;
在這種情況下,部分ACK丟了並不要緊,因爲可以通過後續的ACK進行確認,也就是我們上面舉得這個例子。
(2)數據包直接丟失了;
在這裏插入圖片描述
當某一段報文段丟失以後,發送端會一直收到1001這樣的ACK,就像是在提醒發送端“我想要的是1001”一樣。

如果發送端主機連續三次收到了同樣一個“1001”這樣的應答,就會將對應的數據1001~2000重新發送。

這時接收端收到了1001以後,再次返回的ACK就是7001了。因爲2001~7000接收端其實之前就已經收到了,被放到了接收端操作系統內核的接收緩衝區中。

這種機制被稱爲“高速重發控制”,也稱爲“快重傳”。

快重傳其實是超時重傳的一種優化。

流量控制

接收端處理數據的速度是有限的,如果發送端發的太快,導致接收端的緩衝區被打滿,這時如果發送端繼續發送,就會造成丟包問題,繼而導致丟包重傳等等一系列連鎖反應。因此TCP支持根據接收端的處理能力,來決定發送端的發送速度,這個機制就叫做流量控制。

接收端將自己可以接收的緩衝區大小放入TCP首部中的“窗口大小”字段,通過ACK端通知發送端。我們在討論滑動窗口時就已經瞭解到,窗口大小字段越大,說明網絡的吞吐量就越高。

接收端一旦發現自己的緩衝區快滿了,就會將窗口大小設置爲一個更小的值通知給發送端。發送端接收到這個窗口以後,就會減慢自己的發送速度。如果接收端緩衝區滿了,就會將窗口置爲0,這時發送方不再發送數據,但是需要定期發送一個窗口探測數據段,使接收端把窗口大小告訴發送端。
在這裏插入圖片描述
接收端通過TCP首部中的16位窗口字段(存放了窗口大小信息)將窗口大小告訴發送端。

那麼問題來了,16位數字最大可以表示的數字爲65535,那麼TCP窗口最大就是65535字節麼?實際上,TCP首部40字節選項中還包含了一個窗口擴大因子M,實際窗口大小是窗口字段的值左移M位。

擁塞控制

雖然TCP擁有滑動窗口,能夠高效可靠的傳送大量的數據,但是如果在剛開始階段就發送大量的數據,仍然可能引發問題。因爲網絡上有很多的計算機,可能當前的網絡狀態就已經比較擁堵了,在不清楚當前網絡狀態的情況下,貿然發送大量的數據,很有可能使得網絡狀態更加擁堵。

爲了解決這個問題,TCP引入了慢啓動機制,先發少量的數據摸清當前的網絡擁堵狀態,再決定按照多大的速度傳送。
在這裏插入圖片描述
此處我們要引入一個新的概念——擁塞窗口。

發送開始的時候,我們將擁塞窗口的大小定義爲1,每次收到一個ACK應答時,擁塞窗口加1。每次發送數據包時,將擁塞窗口和接收端主機反饋的窗口大小作比較,取較小的值作爲實際發送的窗口。

像上面這樣的擁塞窗口增長速度,是指數級別的。“慢啓動”只是指初始時慢,但是增長速度非常快。爲了避免增長速度過快,因此不能使擁塞窗口單純的加倍。此處引入一個叫做慢啓動的閾值,當擁塞窗口超過這個閾值的時候,就不再按照指數方式增長,而是按照線性方式增長。
在這裏插入圖片描述
當TCP開始啓動的時候,慢啓動閾值等於窗口最大值。在每次超時重發的時候,慢啓動閾值會變爲原來的一半,同時擁塞窗口置回1。

少量的丟包,我們僅僅是觸發超時重傳,大量的丟包,我們就認爲網絡擁塞。

當TCP通信開始後,網絡吞吐量會逐漸上升,隨着網絡發生擁堵,吞吐量會立即下降。

所謂的擁塞控制,歸根結底是TCP協議想儘可能快的將數據傳輸給對方,但是又要避免給網絡造成太大壓力而採取的折中方案。

延遲應答

如果接收數據的主機立即返回ACK應答,此時返回的窗口可能比較小。假設接收端緩衝區爲1M,一次收到了500K的數據,若此時立即應答,則返回的窗口大小爲500K。但實際上可能處理端處理的速度非常快,幾毫秒內就能將數據從緩衝區消費掉。在這種情況下,接收端處理還遠未達到自己的極限,即使窗口大小再大一些,也能處理的過來。如果接收端稍微等一會再應答,比如等待幾百毫秒再應答,那麼此時返回的窗口大小就是1M。

窗口越大,網絡吞吐量就越大,傳輸速率就越高。我們的目標就是在保證網絡不擁塞的情況下儘量提高傳輸效率。

也不是所有的包都可以延遲應答,這是有數量限制和時間限制的。所謂的數量限制,就是每隔N個包就應答一次;所謂的時間限制,就是超過最大延遲時間就應答一次。而具體的數量和超時時間,依操作系統不同也有差異。一般來說N取2,超時時間取 200ms。
在這裏插入圖片描述

捎帶應答

在延遲應答的基礎上,我們發現,在很多情況下,客戶端服務器在應用層也是“一發一收”的,這就意味着客戶端給服務器說了“hello”,服務器也會給客戶端回一個“hello”。那麼此時ACK就可以搭順風車,和服務器迴應的“hello“一起回給客戶端

面向字節流

創建一個TCP的socket,同時在內核中創建一個發送緩衝區和一個接收緩衝區。

在調用write時,數據會先寫入發送緩衝區中。如果發送的字節數過長,就會被拆分成多個TCP的數據包發出。若發送的字節數太短,就會先在緩衝區裏等待,等到緩衝區的長度差不多或者其他合適的時機,再發送出去。

接收數據時,數據也是從網卡驅動程序到達內核的接收緩衝區,然後應用程序可調用read從接收緩衝區拿數據。

另一方面,TCP的一個連接,既有發送緩衝區,又有接收緩衝區,那麼對於這樣一個連接,我們既可以讀數據,也可以寫數據,這個概念就叫做“全雙工”。

由於緩衝區的存在,TCP程序的讀和寫不需要一一匹配。例如:在寫100個字節的數據時,既可以調用一次write寫100個字節,也可以調用100次write,每次寫一個字節。同理,read也是如此。

粘包問題

TCP協議是面向字節流的協議,接收方不知道消息的界限,不知道一次提取多少數據,這就造成了粘包問題。我們可以就生活中的一個例子來幫助理解。在熱包子新鮮出爐時,我們免不了會看到這種情況:有些包子粘在了一起,必須通過掰扯才能將它們分開。數據也是如此,“粘包問題”中的包,指的是應用層的數據包。

在TCP的協議頭裏,沒有如同UDP一樣的“報文長度”的字段,但是有一個序號的字段。站在傳輸層的角度,TCP是一個一個報文傳過來的,按照序號排好序放到緩衝區中;站在應用層的角度,看到的就只是一串連續的字節數據。那麼應用程序看到這一連串的字節數據時,就不知道從哪個部分開始,到哪個部分結束纔算是一個完整的應用層數據包。

粘包問題出現的原因:

(1)發送端需要等緩衝區滿時才發送出去,造成粘包;

(2)接收端不及時的接收緩衝區內的包,造成多個包接收。

避免粘包問題的方法,歸根到底,就是要明確兩個包之間的邊界:

(1)對於定長的包,保證每次都按固定大小讀取即可;

(2)對於變長的包,可以在包頭的位置約定一個包總長度的字段,從而就知道了包的結束位置;

(3)對於變長的包,還可以在包和包之間使用明確的分隔符,這個分隔符是由程序員自己來定的,只要保證分隔符不和正文衝突即可。

對於UDP協議來說,就不存在“粘包問題”了,因爲:

(1)對於UDP,若還沒有上層交付數據,UDP的報文長度仍然在。同時,UDP是一個一個將數據交付給應用層的,具有很明顯的數據邊界;

(2)站在應用層的角度,使用UDP時,要麼收到完整的UDP報文,要麼不收,不會出現“半個”的情況。

TCP異常情況

進程終止:文件描述符的生命週期隨進程,所以進程終止會釋放文件描述符,仍然可以發送FIN,和正常關閉沒有什麼區別。

機器重啓:和進程終止的情況相同。

機器掉電或網線斷開:接收端認爲連接還在,一旦接收端有寫入操作,接收端發現連接已經不在了,就會進行reset。即使無寫入操作,TCP自己也內置了一個保活定時器,會定期詢問對方是否還在,若對方不在,也會將連接釋放掉。

另外,應用層的某些協議,也有一些這樣的檢測機制,例如HTTP長連接中,也會定期檢測對方的狀態。例如QQ,在QQ掉線之後,也會定期嘗試重新連接。

TCP小結

TCP複雜是因爲它既要保證可靠性,同時又要儘可能的提高性能。

可靠性:

(1)校驗和;

(2)序列號(保證數據的按序到達);

(3)確認應答;

(4)超時重傳;

(5)連接管理;

(6)流量控制;

(7)擁塞控制。

提高性能:

(1)滑動窗口;

(2)快速重傳;

(3)延遲應答;

(4)捎帶應答。

其他:

定時器:超時重傳定時器。保活定時器。TIME_WAIT定時器等。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章