1. 程式人生 > >28-TCP 協議(超時與重傳)

28-TCP 協議(超時與重傳)

TCP 超時與重傳應該是 TCP 最複雜的部分之一了。Windows 和 Linux 對這部分的實現還有所不同,但是演算法基本上還是差不多的。

超時重傳是 TCP 保證可靠傳輸的基礎。當 TCP 在傳送資料時,資料和 ack 都有可能會丟失,因此,TCP 通過在傳送時設定一個定時器來解決這種問題。如果定時器溢位還沒有收到確認,它就重傳資料。

無論是 Windows 還是 Linux,關鍵之處就在於超時和重傳的策略,需要考慮兩方面:

  • 超時時間設定
  • 重傳的頻率(次數)

目前來說,在 Linux 較高的核心版本中,比如 3.15 中,已經有了至少 9 個定時器:超時重傳定時器,持續定時器,ER延遲定時器,PTO定時器,ACK延遲定時器,SYNACK定時器,保活定時器,FIN_WAIT2定時器,TIME_WAIT定時器。

這實在是太多了,對初學者來說,我們重點掌握以下 4 個:

  • 超時重傳定時器(retransmit)
  • 持續定時器(persist)
  • 保活定時器(keepalive,這和 HTTP 協議中的 keepalive 不是同一個概念)
  • TIME_WAIT 定時器

1. 一個超時重傳的例子


這裡寫圖片描述
圖1 超時重傳

本實驗所使用的程式路徑為 unp/program/echo/processzombie/echo.cc,你可以直接使用 make 命令進行編譯。

  • 伺服器端啟動方式
$ ./echo -s -h flower // 或者你可以這樣寫 ./echo -s -h 192.168.166.47
,flower 是我其中一臺 linux 主機的名字
  • 客戶端啟動方式
$ ./echo -h flower // 或者你可以寫 ./echo -h 192.168.166.47

當客戶端連線成功後,傳送一行資料'helloworld',對方回射回來,一切正常,接下來,將伺服器主機 flower 斷網,然後客戶端再次傳送資料 'hehe'

大約等等了 16 分鐘左右(圖2),客戶端返回一個錯誤:No route to host.


這裡寫圖片描述
圖2 客戶端等待約 16 分鐘後返回錯誤

這裡寫圖片描述
圖3 第 9 次重傳後,主機親自發送 ARP 協議詢問對方 MAC 地址

做這個實驗時,兩個主機都屬於同一個網段,有機會,我會將兩個主機放到不同的網段再試一次,看看結果是否還是這樣。因為在同一個網段,主機 sun 傳送了 17

×3=51 次 ARP 請求,每發 3 次 ARP 就等 50 s 左右。

實驗反映的現象已經和 《TCP/IP 詳解卷1:協議》(後面簡稱《詳解》)不再一致。

《詳解》中的第 21 章的例子(圖 21-1),是在經歷了 12 次重傳後放棄(約 9 分鐘),向對方傳送 RST 段。

《詳解》這本書由 W.Richard Stevens(1951-1999) 在 1993 年編寫,時隔 24 年,TCP/IP 協議早已經歷了無數次的演化,這和書上描述的現象不一致太正常了。然而,Stevens 先生不幸在 1999 年去逝(據說是攀巖失足?),這是電腦科學界和教育界最重大的損失。

雖然超時重傳演算法今非昔比,但是如果直接拿到現在所使用的演算法來講解,初學者也會因為太複雜而放棄學習,所以,還是按照 Stevens 先生在《詳解》敘述的演算法來學習吧!

2. 往返時間(RTT)

超時重傳時間(Retransmission TimeOut, RTO)要怎麼設定呢?

資料包過去,到 ack 返回,這個時間一般約等於 RTT 時間,如果一個 RTT 時間內沒有收到 ack,很可能對方就沒有收到資料,或者回送的 ack 丟失。

所以,最直觀的想法是,RTO 應該比 RTT 稍稍大一點。

比如:

RTO=RTT+Δt

當然,這只是我們自己臆想的公式,說不定,TCP 一開始創造出來的時候,RTO 真的是這麼算的呢?

2.1 RTT 測量

可是,在公式中,RTT 是如何測量呢?在 TCP 中,每一次資料包傳送過去到接收到對方的 ack 這個時間差,就會被 TCP 記錄,然後儲存到一個變數 RTTs 中去。

在區域網中,我們的網路一般是很穩定的,每次重新計算一個 RTT,基本上變化不太大,但是在廣域網中,網路就會變得異常複雜,這一次的 RTT 為 100ms,說不定下一次就變 800 ms 了,這時候,採用實時計算的 RTT 就會不合理,在 RFC 中,使用了加權的 RTT。它的公式如下:

RTTs=(1α)×RTTs+α×RTTnew

RFC 2988 建議 α=0.125。上面這個公式說明,我想使用舊的 RTTs 中的 87.5% 的成分,新的 RTT 樣本中 12.5% 的成分,這樣就不至於讓加權的 RTTs 發生劇烈抖動。一個直觀的例子就是,某些同學的成績太差,拉低了班級的整體水平,為了防止這種現象,可以在計算平均值的時候動些手腳。

舉個例子,當前 RTTs=200ms,最新一次測量的 RTT=800ms,那麼更新後的 RTTs=200×0.875+800×0.125=275ms.

2.2 Δt 怎麼定義

在前面,我們臆想了一個公式:

RTO=RTT+Δt

現在我們將其更新為

RTO=RTTs+Δt

RTTS 的計算方法我們在 2.1 中已經介紹過了,現在是 Δt 是什麼,它怎麼算?我們只知道,它應該很小。RFC 2988 規定:

RTO=RTTs+4×RTTD

因此,按照上面的定義,Δt=4×RTTD. 而 RTTD 計算公式如下:

RTTD=(1β)×RTTD+β×|RTTsRTT|

實際上,RTTD 就是一個均值偏差,它就相當於某個樣本到其總體平均值的距離。這就好比你的成績與你班級平均成績差了多少。

RFC 推薦 β=0.25.

2.3 指數退避

假設在某一次傳送資料的時候,資料丟失了,根據前面的公式,我們計算出了一個 RTO 值。如果和重傳後,還是沒有等到對方的 ack,那麼 RTO 的值就會翻倍。只要重傳的的資料沒有 ack,那麼 RTO 就會一直翻倍。

則第 n 次重傳的 RTOn 值為:

RTOn=2n1×RTO1

2.3 Karn 演算法

假設一個分組被髮送,經過若干次和重傳後,收到了對方的 ack,則新的 RTT 如何計算呢?實際上,我們根本沒有辦法知道這個 ack 是對哪一次重傳資料的確認,因此,Karn 演算法規定:此時不更新 RTTs 的值

如果下一次再發生重傳,使用退避後的 RTO 的值。

3. 回到圖 1

在圖 1 中,我們發現,第一次重傳的 RTTs=0.225s,而第二次,並沒有使用指數退避演算法,到了第 3 次重傳才使用了指數退避演算法,後面依次是 0.451, 0.902, 1.804, 3.612, 7.215, 14.416, 28.865。

另一方面,如果我們按照前面的 RTTs 計算公式,計算出來的 RTTs 肯定是非常小的,大概就只有幾毫秒,如果根據這個來計算 RTO,肯定也幾毫秒的樣子。可是為什麼重傳超時時間的 RTO達到了約200ms? 現代的 Linus 核心中,規定了 RTO 有一個最大值和最小值,最大值是是 120 秒,最小是 200 ms:

#define TCP_RTO_MAX ((unsigned)(120*HZ))  // 120秒
#define TCP_RTO_MIN ((unsigned)(HZ/5))    // 0.2s

另一方面,為什麼 Linux 中的 TCP 重傳 8 次後就在那停住,還得去看核心到底是怎麼實現的了。相信大家在掌握了基本的 TCP 超時原理後,一定會找出這個答案的。

4. 總結

  • 理解超時重傳時間如何計算