對於網絡游戲來說,從物體的移動、攻擊到最基礎的計時等等,都需要客戶端與服務器保持時間的相對一致,那麼服務器與客戶端同步便是一個必須要解決的問題。通常,網絡游戲都會利用心跳來進行同步,那麼當客戶端並不需要如此精度的同步時,有沒有其他方法呢?這裡主要討論低精度的時間同步(精確到秒)。
工作中接觸過3種簡單的時間同步方法:
首先,定義時間同步類
/// 32位操作系統 typedef unsigned int64_t QWORD; typedef unsigned long DWORD; class TimeSynchronize() { public: void ServerTimeSync(QWORD ServerTime); QWORD GetLocalTime(); private: QWORD initServerTime; QWORD initClientTime; DWORD initClientTimeFromStartup; }; /// 服務器下發同步消息 void TimeSynchronize::ServerTimeSync(unsigned int64_t serverTime) { initClientTime = time(NULL); initServerTime = serverTime; initClientTimeFromStartup = timeGetTime(); }
DWORD TimeSynchronize::GetLocalTime() { return initServerTime - initClientTime + time(NULL); ///< initServerTime - initClientTime為了消除客戶端與服務器的時間誤差 }看到這裡,細心的同學要笑了,這種做法修改本地的系統時間,不就修改了該函數的返回值麼。確實,接口中只要取用了本地時間,便將修改本地時間的風險帶入了接口。
2.服務器定時向客戶端發送同步消息,就是所謂的心跳機制,類似TCP中的心跳,服務器定時發送一個自定義的結構體(心跳包或心跳幀),讓對方知道自己“在線”。 以確保鏈接的有效性。當服務器超過一定時間沒有收到來自客戶端的回復,則當做玩家掉線,服務器關閉socket鏈接。不同的游戲也會根據游戲類型的不同,根據自己的需要設計心跳機制,間隔從幾十ms到幾s不等。
那麼當客戶端在大部分時間中並不需要高精度的時間同步時,有沒有其他辦法以降低對服務器性能的消耗?
3.利用timeGetTime接口,與同步時獲取的服務器時間模擬當前時間。
// timeGetTime:函數以毫秒計的系統時間。該時間為從系統開啟算起所經過的時間。 // DWORD timeGetTime(VOID); // 參數:無參數。 // 返回值:以毫秒值返回系統時間。 DWORD TimeSynchronize::GetLocalTime() { return initServerTime + timeGetTime() - initClientTimeFromStartup + initServerTime - initClientTime; }
可以看到timeGetTime的返回值精度為(ms),對於精度要求為(s)級的應用來說,已然夠用了。
當然利用timeGetTime()要注意返回值為32位,取值范圍為0~2^32,約為49.71天,要避免溢出。如果服務端利用該接口,又不便於重啟服務器,建議利用精度更高的接口,如:QueryPerformanceFrequency(),QueryPerformanceCounter()。