測量 RTT 的辦法很簡單:
host A 發一條消息給 host B,其中包含 host A 發送消息的本地時間
host B 收到之後立刻把消息 echo 回 host A
host A 收到消息之後,用當前時間減去消息中的時間就得到了 RTT。
NTP 協議的工作原理與之類似,不過,除了測量 RTT,NTP 還需要知道兩台機器之間的時間差 (clock offset),這樣才能校准時間。
以上是 NTP 協議收發消息的協議,RTT = (T4-T1) – (T3-T2),時間差 = ((T4+T1)-(T2+T3))/2。NTP 的要求是往返路徑上的單程延遲要盡量相等,這樣才能減少系統誤差。偶然誤差由單程延遲的不確定性決定。
在我設計的 roundtrip 示例程序中,協議有所簡化:
簡化之後的協議少取一次時間,因為 server 收到消息之後立刻發送回 client,耗時很少(若干微秒),基本不影響最終結果。
我設計的消息格式是 16 字節定長消息:
T1 和 T2 都是 muduo::Timestamp,一個 int64_t,表示從 Epoch 到現在的微秒數。
為了讓消息的單程往返時間接近,server 和 client 發送的消息都是 16 bytes,這樣做到對稱。
由於是定長消息,可以不必使用 codec,在 message callback 中直接用
while (buffer->readableBytes() >= frameLen) { ... } 就能 decode。
請讀者思考,如果把 while 換成 if 會有什麼後果?
client 程序以 200ms 為間隔發送消息,在收到消息之後打印 RTT 和 clock offset。一次運作實例如下:
這個例子中,client 和 server 的時鐘不是完全對准的,server 的時間快了 850 us,用 roundtrip 程序能測量出這個時間差。有了這個時間差就能校正分布式系統中測量得到的消息延遲。
比方說以上圖為例,server 在它本地 1.235000 時刻發送了一條消息,client 在它本地 1.234300 收到這條消息,直接計算的話延遲是 –700us。這個結果肯定是錯的,因為 server 和 client 不在一個時鐘域(這是數字電路中的概念),它們的時間直接相減無意義。如果我們已經測量得到 server 比 client 快 850us,那麼做用這個數據一次校正: -700+850 = 150us,這個結果就比較符合實際了。當然,在實際應用中,clock offset 要經過一個低通濾波才能使用,不然偶然性太大。
請讀者思考,為什麼不能直接以 RTT/2 作為兩天機器之間收發消息的單程延遲?
這個程序在局域網中使用沒有問題,如果在廣域網上使用,而且 RTT 大於 200ms,那麼受 Nagle 算法影響,測量結果是錯誤的(具體分析留作練習,這能測試對 Nagle 的理解),這時候我們需要設置 TCP_NODELAY 參數,讓程序在廣域網上也能正常工作。