天天看點

【網絡遊戲專題】時間同步裝置

    在網絡遊戲中,有一個最基本的需求是,如果讓一個玩家的動作(比如行走)即時地、流暢地在其它的遊戲地理位置相鄰的玩家的螢幕上顯現,如果是在區域網路内,這個不是什麼大不了的問題,但是如果遊戲玩家是分散在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又設定不當的話,那麼時間同步的誤差就比較大,不知道你是否有更好的辦法了?能夠提高時間同步的精确度:)

繼續閱讀