歡迎去gitbook(https://legacy.gitbook.com/@rogerzhu/)看到完整版。linux
若是對和程序員有關的計算機網絡知識,和對計算機網絡方面的編程有興趣,雖說如今這種「看不見」的東西真正能在實用中遇到的機會很少,可是我始終以爲不管計算機的語言,熱點方向怎麼變化,做爲一個程序員,不少基本的知識都應該有所瞭解。而當時在網上搜索資料的時候,這方面的資料真的是少的可憐,因此,我有幸前兩年接觸了這方面的知識,我以爲我應該把我知道的記錄下來,雖然寫的不必定很好,可是但願能給須要幫助的人多個參考。個人計劃是用半年時間來寫完這一系列文章,這個標題也是我對太多速成文章的一種態度,好了,廢話再也不多扯了,下面是其中的一節內容,更多內容能夠去gitbook上找到。git
在TCP中,超時重傳機制是和應答確認機制同樣組成TCP可靠傳輸的關鍵設計。而超時重傳機制中最最重要的就是超時計時器的時間選擇的了,很明顯,在工程上,在數據發送的過程當中,若是用一個固定的值一直做爲超時計時器的時長是很是不經濟也很是不許確的方法,因此這一篇就來講說TCP中的超時計時器的設計哲學。程序員
超時超時,首先你得定義什麼是正常的時間,才能知道有沒有超過正常的時間。先假設一個很是理想的環境,這個環境理想到和之前不少物理題同樣,不考慮摩擦力。咱們假設網絡很通暢且速率穩定,並且處理包的速度忽略不計,這樣一個包發送到對端的時間永遠都是同樣的,將這個時間記爲t。那麼很明顯,若是超過兩倍的t尚未收到對端的回覆,咱們就能夠確定超時了。因此在這種狀況下超時計時器只要設置的比兩倍t長就好了。只要過了這個時間,發送端就會從新發送這個包。算法
那麼這個時間是否是越長越好呢?答案很明顯不是,由於太長會人爲的減小通訊的速率,對於通訊這種有時候一點點速率的提升都讓人欣喜若狂了,若是你還人爲的浪費時間那真是暴殄天物了。編程
那麼若是這個時間設置的過短會怎樣呢?在這個理想狀況下就是小於2倍的t,這會致使太多沒必要要的重傳。也許ACK正在路上,你卻錯誤的認爲是丟失了,那麼網絡中就會增長不少原本就沒必要要的包。網絡
並且,要知道,現實的網絡環境是十分複雜多變的,有時候可能忽然的抽風,有時候可能忽然的又很順暢,因此說若是隻用一個一直不變的時間做爲重傳計時器的時長是徹底不現實,不可用的。因此不少計算重傳時間的算法就被設計出來。spa
TCP把一個包從發送端發送出去到接收到這個包的回覆這段時間稱之爲RTT,學名round-trip time。若是在一個包發送出去之後,超過了RTT尚未接受到回覆確認,那麼很明顯,這個包超時了。若是你還記得前面的關於的PING那一篇,裏面就有一個time指示了來回時間,可是這個是ICMP的來回時間,和TCP的這個RTT是徹底不同的概念。經過wireshark你能夠看到每一次的RTT的值。計算機網絡
這個RTT的計算很簡單,只要把收到確認包的時間減去發送包的時間就獲得了這個答案。設計
如今開始對於重傳計時的第一次思考,上面說了這樣一個來回就說明包是成功的接收了而且沒有發生任何異常,那麼可不能夠簡單的用這個值做爲標準來做爲判斷超時的依據呢?也就是若是超過了0.285s沒有收到ack就開始重傳,很明顯,不能。緣由是這個RTT是過去完成時了,是上一次成功的時候的時間,和下一次網絡會不會忽然抽風,仍是會忽然變得更通暢沒有太大的必然關係,最愚蠢的一種思惟就是簡單的用過去表明將來。可是數學給咱們提供了一種用已知大概去逼近未知的方法,那就是用機率統計的思惟。因此最簡單的一個辦法是用過去的幾回平均值來做爲這一次重傳計時器的時長,畢竟這是初中學過的理論。不過這個方法明顯太過於幼稚,缺少靈活的控制,因此說,第一次設計的嘗試就出現了。3d
爲了可以用更加靈活的方法來估算出重傳時間,一個叫SRTT的概念被引進,SRTT學名是Smoothed RTT。估算重傳時間(之後稱之爲RTO,Retransmission Timeout)的算法以下:
SRTT = (α * SRTT) + ( (1 -α) *RTT)
其中這個奇妙莫測的阿爾法取值在0.8到0.9之間,爲何這樣取,我也不知道,我至今也沒有找到緣由。對於這第一個公式,具體實際中的作法是這樣的,首先採樣幾回RTT的值,而後在第一次迭代的時候SRTT的初始值爲RTT,後面就是根據每次計算出來的SRTT來計算就好了。這個公式有個你應該比較熟悉的中文名字,叫作加權移動平均。
在計算出SRTT以後,就使用這個值來計算咱們須要的RTO,其方法以下:
RTO = min[UBOUND, max[LBOUND,(β * SRTT)]]
這其中UBOUND是一個上限時間,好比1分鐘,LBOUND是一個下限時間,好比1秒鐘,β,哈哈,又是一個神奇的參數,取值在1.3到2.0之間,叫作延遲方差因子,到底取啥,爲何取這個值,我,仍是不知道。
這個方法有什麼問題呢?問題就在這個RTT的計算上,前面說過RTT的計算是接收到ACK的時間和包發送出去的時間的差值,在正常狀況下還好,若是是在採樣的過程當中發生了重傳,那麼到減去的時間是第一次發送的時間仍是重傳發送的時間呢?
若是是減去第一次發送的時間,那麼很明顯,這個RTT計算大了。那你可能會說了,從直觀上說,用第二次發送的時間計算纔是合理的。可是有一種狀況,假設原本應該到達的ACK不是丟失了,只是延遲到達了,也就是說你剛重傳,這個迷路的ACK就到了,那麼你用這個時間減去第二次發送的時間,明顯就小了。
這個時候兩個叫Phil Karn和Craig Partridge的人就針對這個問題提出了一個算法,其解決方案十分簡單,既然重傳狀況這麼複雜,那麼在採用RTT的時候直接忽略重傳不就好了。你先收起你的吐槽說尼瑪這樣我要早出生幾年也能想出這個辦法啊,人家論文裏還寫了不少其餘的東西,這個只是其中之一,並且這個算法也有很大的問題,Karn針對這個問題還提出了一個可行的解決方案,至少在工程上有了個可行的路子。
這個問題是什麼呢?假設在某個時間,網絡極度的抽風,忽然由快變得很慢,致使全部的包都要重傳。這下好了,由於前面一直很通暢,因此必然RTO很小,那麼你又說重傳的包不參與RTT的採樣,這下完了,RTO永遠不會更新,只會不斷的重傳,狀況會愈來愈糟。而Karn針對這個提出了一個解決方案,只要重傳,那麼RTO就翻倍,這樣就保證了在極端狀況下不會致使愈來愈糟。
Karn的算法解決了初代算法的問題而且有了個可行的方案,可是RTO粗暴翻倍的作法感受仍是比較浪費。因此,在一年以後又有兩我的Jacobson 和 Karels 針對這種加權移動平均的算法對RTT波動handle能力不強的弊端作了修正。其原理是用最新採樣的RTT和平滑過的SRTT的差距來做爲另外一個影響因子。
SRTT = SRTT + α * (RTT - SRTT)
DevRTT = (1-β) * DevRTT + β *(|RTT - SRTT|)
RTO = μ * SRTT + δ * DevRTT
這三個公式就是如今TCP協議中真正運用的算法,關於這些參數,α是取0.125,β是0.25,μ 是1,δ是4,這就是linux中的取值,至於爲何,沒有人知道,可是在實際效果中,果然就頗有效,在編程過程當中,咱們稱這種玄學叫作調的一手好參數。