1、TCP重傳機制
通過序列號和確認號確保可靠傳輸,當發送端發送數據給接收到,接收端會返回一個確認號,表示收到消息了
- 超時重傳:沒有在指定時間內收到
ACK
報文- 超時重傳的兩種可能:數據包丟失、確認包丟失
- 超時重傳時間
RTO
:RTO
較大:重發就變慢了,丟包之后需要半天才能重發,效率低RTO
較小:可能沒有丟包,還在等待ACK過程中,就重發了,會導致網絡擁塞,進而導致更多的超時重發- 所以
RTO
需要略大于報文往返的時間RTT
(數據發送到接收到ACK的時間差)
- 由于網絡波動問題,
RTT
可能不是固定的,所以RTO
對應也不是固定的 - 一般超時重傳一次,下一次超時重傳的間隔會加倍,避免網絡環境差的頻繁發送
- 快速重傳:
超時重傳的問題
:重傳周期可能很長(時間會加倍),快速重傳可以解決這個問題,以數據為驅動作為重傳- 發送了數據包
seq1
~seq5
,- 發送端發送
seq1
,接收端回復ack=2
,表示接收到包1 seq2
丟失- 發送端連續發送
seq3
~seq5
,接收端會重復返回ack=2
- 連續三次
ack=2
,觸發快速重傳
,重復seq2
- 由于接收到了
seq3
~seq5
,發送端會返回ack=6
- 發送端發送
- 快速重傳只解決了超時問題,但是還有一個問題:重傳的時候重傳一個還是所有數據包
- 因為如果只重傳一個,當有兩個數據包丟失的時候,需要判斷兩次返回的ack(三次重復),來進行單包重傳,效率低
- 如果重傳所有,就會有多余的包被重新發送,無用功
- SACK:選擇性確認,可以知道哪些數據丟失了
- 需要
TCP
頭部選項
字段中,添加一個SACK
,可以將已接收到的數據的信息發送給發送方,發送方就知道了哪些數據丟失了,從而只發送丟失的數據 - 在快速重傳的基礎上,發送方收到三次相同的ack報文,觸發快速重傳機制,通過
SACK
信息可以知道哪段數據丟失了,只對丟失的數據重傳
- 需要
D-SACK
:通過SACK
告訴發送端哪些數據被重復接收了- 可以讓發送端知道是包丟失,還是ACK包丟失了
- 可以知道發送的數據包是不是被網絡延遲了
- 可以知道網絡中是不是把發送端的數據包給復制了
- 小結:
- 如果數據包丟失或者ACK包丟失,超過一定時間會觸發超時重傳,超時時間
RTO
略大于RTT
時間(發送數據到接收ACK包的時間間隔) - 為了解決超時等待的時間,提高效率,就有了快速重傳,當有一個包丟失的時候,可以通過判斷重復的ack進行快速重傳,通過在TCP頭部的選項字段里面添加SACK,就可以知道哪些包是丟失的,從而只重傳丟失的包
- 如果在
ACK
包丟失,導致的超時重傳,發送端接收到接收端SACK
從而知道收到了重復數據,這個SACK
就是D-SACK
,如果一個包由于網絡問題導致的快速重傳,也可以通過D-SACK
來進行判斷
- 如果數據包丟失或者ACK包丟失,超過一定時間會觸發超時重傳,超時時間
2、TCP滑動窗口
TCP每次發送數據都要進行確認應答,當上一個包收到應答了再發下一個
- 如果是一問一答的形式,則數據包往返的時間周期越長,通信效率就越低
- 為了解決上面的問題,就有窗口概念,
- 可以指定窗口大小,窗口大小是不需要等待確認應答,可以繼續發送數據的最大值
- 窗口是內核里面開辟的緩存區,需要保留發送的數據,只有收到應答才會從緩沖區中刪除
- 在
TCP頭部
可以指定窗口大小,發送端會根據ACK
返回的窗口大小,來發送數據,從而保證對端可以正常接收數據,所以窗口大小是由接收端決定的
- 發送端的窗口:
- 有兩個絕對指針:一個指向沒有收到ack的第一個字節的序列號, 一個指向窗口中可用空間的第一個字節的序列號
- 會根據ACK報文中的窗口大小進行調整,因為應用層不一定及時收數據
- 此外,如果發送端窗口滿了,沒有及時收到
ACK
,就不能在應用層進行發送數據,如果序列號較前的收到ack,窗口會向右移動,存在可發空間,應用層可以繼續拷貝發送數據
- 接收端的窗口:可以接收發送端發送的數據量,一次能處理的數據量
- 只有一個絕對指針,指向期望對端發送來的下一個字節的序列號
- 窗口里面是還沒進行確認的數據,也就是還沒收到的數據,但是可以接收的數據大小
- 發送端窗口約等于接收端窗口,因為傳輸存在延遲,不一定及時調整窗口大小,所以是約等于
- 小結:
- 三次握手確定窗口大小,接收端能接收多少數據,發送端根據接收端數據進行調整,去發送數據,在發送端窗口大小內,發送端可以一直發,并等待接收端返回ack確認號來發送窗口移動,每次ack報文都會攜帶窗口大小,可能會改變;
3、TCP流量控制
利用滑動窗口實現流量控制,發送端不能一股腦的發過去,如果對方處理不過來就會觸發重傳機制
- 流量控制:發送方 根據 接收方的實際接收能力發送數據,也就是接收端滑動窗口的大小
- TCP內核緩沖區和滑動窗口的關系:緩沖區大小會影響窗口大小
- 應用層不及時收取數據,會導致窗口變小:
- TCP滑動窗口(指針實現)是處于TCP緩沖區中間的,如果接收端接收到數據并返回ACK,但是應用層沒有及時收取,當緩沖區內存不夠,可能會導致滑動窗口變小,當窗口大小變成0,就發生了窗口關閉
- 系統資源減少,會導致緩沖區變小:
- 如果用戶空間沒有及時讀取緩沖區數據,并且接收緩沖區由于系統資源突然變小,發送端來不及調整,會導致數據丟包現象,這是因為先減少緩沖區再收縮窗口
- 所以不允許同時減少緩沖又收縮窗口,而先收縮窗口后一段時間再減少緩沖,從而避免丟包
- 應用層不及時收取數據,會導致窗口變小:
- 窗口關閉:
- 窗口大小為0的情況,就會阻止發送端發送數據,直到大小變成非0
- 如果接收端窗口變成非0,會通過發送攜帶窗口大小的ACK報文,如果ACK報文丟失,發送端會一直等待,造成死鎖問題
- 如何避免死鎖問題:當窗口關閉,就會啟動一個持續計時器,如果超時就會發送一個
窗口探測報文
,接收端收到這個報文,就會返回一個攜帶當前窗口大小的ACK報文,如果窗口依舊為0,就會重啟持續計時器
- 糊涂窗口綜合癥
- 接收端太忙,會導致發送端的窗口越來越小,到最后只有接收端騰出字節空間,發送端就會馬上發送,但是TCP/IP包頭就有40字節,有數據就傳輸的話,開銷太大了
- 導致的原因是:
- 接收端告知小窗口大小
- 發送端發送小數據
- 如何避免:
- 接收方不告知小窗口,窗口小于某個值就發送窗口為0的ACK,阻止對端發送數據
- 發送發避免發送小數據,開啟Nagle算法,避免小包發送
- Nagle算法思想,延時處理,滿足下面一個條件即可:
- 窗口大小>=MSS并且數據大小>=MSS
- 收到前一個數據的ack報文
- 一般需要搭配 不通知小窗口給發送方+開啟Nagle算法才能避免糊涂窗口綜合癥
4、TCP擁塞控制
-
流量控制:是為了避免,發送端 數據填滿 接收端的緩存,但是流量控制并不知道網絡中發送的情況
-
網絡擁塞:網絡發生擁堵的時候,繼續發送大量數據包,就可能導致數據包延時、丟失等情況,TCP就會重傳數據,一旦重傳就會導致網絡更加擁堵,從而不斷惡性循環
-
擁塞控制:避免發送方的數據填滿整個網絡,并且為了調節發送數據的量,定義了一個
擁塞窗口(cwnd)
的概念 -
擁塞窗口(cwnd):是發送方維護的一個狀態變量,會根據網絡的擁塞程度進行變化,滑動的發送窗口=min(滑動的接收窗口,擁塞窗口),當網絡沒有出現擁塞,
cwnd
窗口就會越大,當網絡出現擁塞,cwnd
就會越小 -
如何判斷網絡擁塞:發送方沒有在指定時間接收到ACK應答報文,也就是發送超時重傳,就會認為網絡出現擁塞了
-
擁塞控制主要是四算法:
- 慢啟動:(指數增長)
- TCP建立連接后,一點點點提高數據包發送的數量,發送端沒收到一個ACK,擁塞窗口
cwnd
的大小就會+1; - 存在一個慢啟動門限
ssthresh
,如果cwnd
<ssthresh
使用的就是慢啟動算法,如果cwnd
>=ssthresh
,就使用擁塞避免算法;ssthresh
一般大小為65535字節
- TCP建立連接后,一點點點提高數據包發送的數量,發送端沒收到一個ACK,擁塞窗口
- 擁塞避免:
- 沒收到一個ACK,
cwnd
增加1/cwnd
,是為了確保cwnd
的線性增長 - 如果一直保持增長,網絡就會慢慢進入擁塞狀態,從而出現了丟包現象
- 如果觸發了重傳機制,就會進入擁塞發生算法
- 沒收到一個ACK,
- 擁塞發送:
- 發生重傳(超時、快速)的時候就會進入擁塞發生算法
- 超時重傳的擁塞發生:
ssthresh
會設置為cwnd/2
,并且cwnd
會回復為初始值,linux的初始值是10(10個MSS);- 設置完
ssthresh
和cwnd
之后,會重新開始慢啟動; - 這種方式下來的擁塞發生太激進了,容易造成網絡卡頓
- 快速重傳的擁塞發生:
- 設置
cwnd
為cwnd/2
,再設置ssthresh
為cwnd
- 設置完之后,會進入
快速回復算法
- 設置
- 快速回復:
- 當發生快速重傳的時候,表示網絡不是太糟糕,一般快速回復和快速重傳同時使用
- 進入快速回復前,
cwnd
和ssthresh
都全部設置完了 快速回復
:cwnd
=ssthresh+3
,3表示接收到了三個數據包- 重傳丟失的數據包;
- 如果再收到重復的
ACK
,cwnd
就+1 - 如果收到的是新的ACK,表示網絡沒問題了,則將cwnd設置為ssthresh,進入擁塞避免狀態;
- 慢啟動:(指數增長)
-
擁塞控制算法過程:
- 從TCP三次握手建立連接開始,發送端開始慢啟動,沒收到一個包的ACK,擁塞窗口就會+1,如果達到閾值,就會進入擁塞避免算法,每收到一個包的ACK,擁塞窗口就會增加窗口的倒數,從而保證擁塞窗口的線性增加
- 當遇到丟包的情況,就判斷為網絡擁塞,從而重復重傳機制,進入擁塞發送算法,根據重傳機制的不同,擁塞發送算法也會不同
- 超時重傳,sthresh=cwnd/2,并且cwnd=初始值,重新重慢啟動開始
- 快速重傳,cwnd=cwnd/2,ssthresh=cwnd,開啟快速回復
- 快速回復:先是設置cwnd=ssthresh+3,并重傳丟失數據包,如果依舊重復收到相同的ACK,就會重復觸發快速重傳和快速回復,如果收到的是新的數據包,則進入擁塞避免狀態。
參考文章:小林coding