慢啟動和丟包重傳
在TCP連接剛開始的時候, 不啟用延遲確認功能(而是立刻對數(shù)據(jù)包發(fā)送ACK), 這樣可以讓連接迅速渡過快啟動模式
慢啟動只會在剛開始和超時重傳之后發(fā)生. 同時, 如果長時間無操作或者認為cwnd不能反應當前網(wǎng)絡狀況, 都應該重頭開始.
各種版本的TCP
- Tahoe : 最原始的版本(一開始是丟包就恢復到1, 后來改為ssthresh)
- Reno : 增加了快重傳: 丟包后的任何一個ACK都意味著可以增加速率, 即使這些ACK是重復的. 任何"新"ACK都意味著可以退出快重傳模式
缺陷: 如果有多個包丟失, 那么針對一個包的ACK會不會讓連接退出快重傳? (稱為部分ACK). 因此Reno采用了超時計時器, 針對所有丟失包進行計時. 新問題: 如果不夠3個重復ACK來激發(fā)快重傳, 那么超時計時器就會被出發(fā), 最終回到了慢啟動 - newReno: 追蹤最高序號的包, 只有收到了最高序號的包的ACK, 才意味著退出快重傳
SACK重傳:
更高效, 高效重傳真正需要重傳的內(nèi)容, 但是可能因為過度發(fā)包又引發(fā)了擁塞. SACK只是知道了要發(fā)什么, 缺乏信息得知發(fā)送多少和何時發(fā)送. 因此SACK需要記錄飛行中的真正可抵達的數(shù)據(jù)的大小來估計本次重傳的大小.
FACK 轉(zhuǎn)發(fā)確認:
遇到重傳狀況時, 只要收到了兩個重復的ACK, 就允許發(fā)送一個包, 從而避免了剛開始RTT/2時間的等待. 缺點: 如果亂序抵達會讓FACK更加激進. Linux中已經(jīng)啟用了FACK功能
有限傳輸:
當窗口很小的時候, 可能沒有足夠的包來激發(fā)快速重傳, 因此當窗口小時, 只要收到了連續(xù)的重復ACK 就允許發(fā)送下一個包. 這樣保證了網(wǎng)絡中存在足夠的包(來激發(fā)快重傳).
擁塞窗口校驗CWV
問題: 如果在發(fā)送過程中, 停止一段時間, 那么恢復后會引發(fā)一個"劇烈的重傳"或者"雙方寂靜". 原因: 雙方都使用著過時的ssthreash
Congestion Window validation 算法: 記錄最后一個發(fā)送的包的時間, 如果超過一個RTO沒有回應, 就更ssthreath改為max(ssthreath, 0.75*cwnd)
cwnd不斷變小, 這種思想是: 減緩過去的ssthreath帶來的影響, 平滑之
處理假RTO
問題: 如果出現(xiàn)了偽丟包(臨時超時, 后來又得到了), 那么ssthreath不應該降下去, 從而浪費了很多不必要的重傳.
思想:
- cwnd = 飛行大小 + min(bytes_acked, IW). 新加入的IW不會對現(xiàn)有的網(wǎng)絡造成過大的壓力,
- 使用一個值來記錄ssthreath降之前的值, 同時 同時如果發(fā)現(xiàn)了是"偽丟包"的情況, "retract"之前的算法, 把ssthreath變回去.
實戰(zhàn)
本地鏈路丟包
如果發(fā)送方由于系統(tǒng)調(diào)用等其他因素一段時間沒有發(fā)送, 再發(fā)送時, TCP可能會將大量的包丟給下面, 如果IP層處理不及時, 就會選擇直接丟棄, 這些丟棄在Wireshark是看不到的.稱為"本地擁塞", 可以在linux下使用tc -s -d命令查看接口上又多少包被丟掉了-
拉伸的ACK: 有可能一個ACK同時確認了兩個最大的TCP包, 最有可能的原因是上一個TCP的ACK包在傳輸過程中丟失了. 不過這個同時確認兩個TCP包的ACK可能會引發(fā)擁塞避免過程.
image.png 一次超時過后, TCP變成了慢啟動的狀態(tài). 雖然Receiver還會發(fā)送SACK報文, 但是Sender不一定持續(xù)相信. 這就是TCP"傾向于遺忘"的特性. 因為接收端是允許縮小接收窗口的, 如果縮小了, 那么接收方說得就是"食言".
又一次超時過后, 雖然收到了Receiver對這個報文的ACK. 但是慢啟動行為不能undo, 因此又慢騎電動了
TCP的信息共享性
TCP和相同Endpoints剛剛關閉的連接共享ssthreath和cwnd變量, 有利于前者的生成. 默認在Linux中開啟
TCP的友善性
可以設置一個友善度, 通過RTT, 包大小, 丟包率, 重傳超時時間來設定這個友善值, 意味著TCP和其他TCP/非TCP共享一條鏈路時的退避程度, 使得這種設置友善性的終端對于鏈路有更好的利用率.
高時延帶寬積TCP
兩個問題: cwnd的快速增長和慢啟動的初始大小.
思想: cwnd增長時要考慮當前的cwnd大小
慢啟動的初始大小可以設置很大.
低丟包率的時候增長更激進
研究發(fā)現(xiàn), 低RTT在相同條件下能獲得更好的效率, 當然這也好推測, 因為高RTT會消耗更多網(wǎng)絡資源(路由), 理應更低效.
BIC-TCP: 一種意境被應用到linux的用于高時延帶寬積網(wǎng)絡的算法. 該算法有兩種算法(不可同時啟用):
- 二分查找法. 假設無丟包時的window是最小, 剛丟包的時候window是最大. 那么二分查找最合適的window大小, 缺點是越靠近最佳值, 這個算法的步進越小, 仿佛和其他標準的TCP算法是反著來的哈..
- 加性增長法: 二分查找法剛開始的時候, window直接變到了mid點, 這時候會發(fā)送大量的數(shù)據(jù)到網(wǎng)絡中. 加性增長法嚴格限制新注入進來的數(shù)據(jù)包的量. 在探測max window的時候和標準TCP一樣, 線性增長, 這樣只要到達了最佳點附近, 大概率會出現(xiàn)丟包.
CUBIC算法: BIC算法在特定情況下過于激進. CUBIC在window的增長上既能體現(xiàn)"激進性", 又能體現(xiàn)"保守性"
基于延遲的擁塞控制
除去上文中提及的算法, 都是基于丟包和超時重傳, 還有一種基于ECN(報告網(wǎng)絡阻塞狀況的選項)的算法: 基于延遲的擁塞控制.
即使沒有ECN, 終端也可以根據(jù)RTT激增來判斷是出現(xiàn)了阻塞.
Vegas算法: 相比于傳統(tǒng)TCP算法喜歡將鏈路"注滿", Vegas更希望"保持排空", 發(fā)現(xiàn)一點擁塞就降速. 缺點: 如果反向的鏈路狀態(tài)不好, Vegas算法會被迷惑, 從而降低發(fā)送速率.
未來 : 結合延遲和丟包的控制算法, 兩種都用?
緩沖爆炸
大量的內(nèi)存反而會導致TCP性能下降?
是這樣的, TCP的性能調(diào)優(yōu)是為了讓瓶頸盡可能的滿著, 但是過大的buffer會延遲 反饋問題所在的位置. 同時, 小上行(比如同時開著別的上傳進程) , 會導致接收方的消息難以反饋到發(fā)送方.
一個提議的解決方案就是使用上文的基于延遲的擁塞控制, 但是可能會導致延遲震蕩時性能不好.
ECN精準擁塞通知
普通的路由器是被動(passive)的, 擁塞的時候只知道丟包, 不擁塞的時候FIFO. 主動的(Active)的路由器應當會報告鏈路質(zhì)量, 同時只能排序進來的TCP包. ECN技術常用在網(wǎng)絡供應商的路由器. ECN可以在網(wǎng)絡層實現(xiàn), 也可以在傳輸層TCP實現(xiàn).
好處: 過度擁塞的時候, 兩個Endpoint不要再快速重傳, 直接慢啟動
相關攻擊
這部分格外的有趣. 主要是和自私的客戶端相關.
對于一個片的逐個ACK
對于收到的一個大包, 分成小ACK回應, 會讓發(fā)送方認為這個對方網(wǎng)絡狀況良好(因為TCP基于收到的ACK包的總數(shù)來估計網(wǎng)絡狀況),cwnd激增, 這個客戶端受到"更不公平更好"的待遇. 解決方法是基于發(fā)送的數(shù)據(jù)總大小來估算cwnd而不是收到的ack包.
重復ACK
用于快恢復的時候, 客戶端發(fā)送超多的ACK, 來讓服務器盡快提升cwnd.
姑且的解決方法時在快恢復的時候限制最大發(fā)送速率.
提前ACK
客戶端提前ACK(即使還沒收到), 發(fā)送方收到了自己還沒發(fā)送的序號的包的ACK只會簡單丟棄, 因此這個方法也能讓發(fā)送方快速擴大cwnd.
問: namespace假如客戶端丟數(shù)據(jù)了怎么辦? 這個方法特別適用于HTTP. 因為這些數(shù)據(jù)都是可重傳, Session層可以保證完整接收.
結語
讓一個TCP協(xié)議同時兼容上述這么多的特性屬實不容易, 因此Linux的TCP源碼已經(jīng)超過了20000行, 因此使用可視化的分析是肯定的 如tcpdump, Wireshark, tcptrace等能夠基于時間軸分析.