在網絡遊戲中,有一個最基本的需求是,如果讓一個玩家的動作(比如行走)即時地、流暢地在其它的遊戲地理位置相鄰的玩家的螢幕上顯現,如果是在區域網路内,這個不是什麼大不了的問題,但是如果遊戲玩家是分散在Internet上的使用者,由于網絡的延時的影響,使得其成為項頗有難度的任務。你隻要想一下,玩家A發出的消息經過伺服器中轉到達玩家B時可能已經經過了幾秒鐘,那麼A和B的遊戲狀态同步就是個問題了。關于解決狀态同步問題的方案有一些,比如,預測拉扯技術、伺服器驗證同步技術、半伺服器同步技術等,但是所有這些技術都需要依賴一個更基礎的裝置--時間同步裝置,時間同步裝置用于讓所有的遊戲用戶端、遊戲服務端都遵守同一個标準的時間,這樣,由不同節點發出的消息上的時間戳才有統一的參考标準,這樣我們才能比較準确的計算消息從A到B的延時。
我采用這樣的方式來實作時間同步裝置:
(1)采用伺服器(組)的時間為标準時間,這樣所有用戶端隻要與伺服器時鐘同步即可。
(2)采用類似ping的技術來測量消息從用戶端到服務端再回到用戶端所花費的時間。
(3)通過一個系數可以調整消息上行速度和下行速度的比例,該比例預設為1,可以根據網絡狀況進行動态調整。
(4)可以每隔一段指定時間間隔自動同步一次,也支援強制手動調用同步。
這基本上是時間同步裝置的核心思想,在設計時,我考慮把它設計得更友善應用,詳細講解如下:
首先,是定義時間同步裝置的接口--IStandardTimeSynchronizer。
/// <summary>
/// IStandardTimeSynchronizer 時間同步裝置,将伺服器的時間作為标準時間參考系。
/// zhuweisky 2008.03.05
/// </summary>
public interface IStandardTimeSynchronizer
{
/// <summary>
/// SynchronizeSpanInSecs 自動同步伺服器時間的時間間隔。
/// </summary>
int SynchronizeSpanInSecs { get;set; }
IPinger Pinger { set; }
void Initialize();
/// GetCurrentStandardTime 擷取經過本地預測的伺服器目前标準時間
/// </summary>
DateTime GetCurrentStandardTime();
/// Synchronize 與伺服器進行時間同步,手動強制同步
void Synchronize();
/// ComputeLifeInMSecs 計算标記戳時的時刻到現在過了多少ms。(即,消息的目前年齡:))
/// 注意,消息中的時間戳也是記錄的當時(預測)的伺服器标準時間。
/// <param name="timeStamp">消息中的時間戳</param>
double ComputeLifeInMSecs(DateTime msgStampTime);
}
Initialize方法用于啟動自動同步的定時器。IPinger接口用于發送ping消息到伺服器,并帶回伺服器接收到ping消息的時間:
/// IPinger 用于向伺服器發送ping消息,用于計算消息延遲和預測伺服器标準時間。
/// 伺服器對該ping消息的應答中必須包含伺服器應答時刻的時間戳。
/// zhuweisky 2008.01.05
public interface IPinger
/// Ping 當該方法被調用時,用戶端立即向伺服器發送ping消息然後阻塞,直至收到伺服器對該ping消息的應答後該函數才能傳回。
void Ping(out DateTime serverTimeStamp);
下面我們繼續看時間同步裝置的完整實作:
public class StandardTimeSynchronizer :BaseCycleEngine ,IStandardTimeSynchronizer
/// timeModifiedInMSecs 本地時間與伺服器時間的內插補點(ms)。如果本地時間快,則取正值,否則取負值。
private double timeModifiedInMSecs = 0;
/// circleSpanInMSecs 從發出ping消息到收到伺服器應答的總時間(ms)
private double circleSpanInMSecs = 0;
/// coefUpDownSpeed 消息下行的速度與上行速度的比例
private double coefDownUpSpeed = 1;
#region IStandardTimeSynchronizer 成員
#region SynchronizeSpanInSecs
private int synchronizeSpanInSecs = 10;
public int SynchronizeSpanInSecs
{
get { return base.DetectSpanInSecs; }
set { base.DetectSpanInSecs = value; }
}
#endregion
#region Pinger
private IPinger pinger;
public IPinger Pinger
set { pinger = value; }
#endregion
public void Initialize()
base.Start();
}
protected override bool DoDetect()
Stopwatch stopwatch = Stopwatch.StartNew();
DateTime serverTimeStamp;
this.pinger.Ping(out serverTimeStamp);
this.circleSpanInMSecs = stopwatch.ElapsedMilliseconds;
DateTime pingBackTime = DateTime.Now;
double downCostInMSecs = this.circleSpanInMSecs / (coefDownUpSpeed + 1);//消息下行時間
DateTime estimateServerActionTime = pingBackTime - TimeSpan.FromMilliseconds(downCostInMSecs);
TimeSpan timeModifiedSpan = estimateServerActionTime - serverTimeStamp ;
this.timeModifiedInMSecs = timeModifiedSpan.TotalMilliseconds;
return true;
public DateTime GetCurrentStandardTime()
return DateTime.Now.AddMilliseconds(-this.timeModifiedInMSecs);
public void Synchronize()
this.DoDetect();
public double ComputeLifeInMSecs(DateTime msgStampTime)
DateTime estimateServerCurTime = this.GetCurrentStandardTime();
TimeSpan span = estimateServerCurTime - msgStampTime;
return span.TotalMilliseconds;
BaseCycleEngine是一個循環工作的引擎,用于每隔一段時間執行一次DoDetect方法調用。
上面介紹的是時間同步裝置的用戶端的實作,服務端的實作相當簡單,隻需要實作ESFramework中一個消息處理器接口并插入到架構即可。
關于這種方案的缺陷是,如果消息上行速度和下行速度差異很大,而速度比例系數coefDownUpSpeed又設定不當的話,那麼時間同步的誤差就比較大,不知道你是否有更好的辦法了?能夠提高時間同步的精确度:)