C++獲取時間函數眾多,何時該用什么函數,拿到的是什么時間?該怎么用?很多人都會混淆。
本文是本人經歷了幾款游戲客戶端和服務器開發后,對游戲中時間獲取的一點總結。
最早學習游戲客戶端時,為了獲取最精確的時間,使用兩個函數
BOOL QueryPerformanceFrequency(LARGE_INTEGER *lpFrequency);
BOOL QueryPerformanceCounter(LARGE_INTEGER *lpCount);
這兩個函數分別是獲取CPU的時鍾頻率和CPU計數器,是能夠獲取到的最精確的時間差。對於需要獲取每幀走過的精確時間,使用這兩個函數是最最精確的。
#include<windows.h> LARGE_INTEGER lFrequency; QueryPerformanceFrequency(&lFrequency); LARGE_INTEGER lBeginCount; QueryPerformanceCounter(&lBeginCount); Sleep(100); LARGE_INTEGER lEndCount; QueryPerformanceCounter(&lEndCount); double time = (double)(lEndCount.QuadPart - lBeginCount.QuadPart) / (double)lFrequency.QuadPart;
在客戶端代碼的時間處理模塊中,每一幀調用QueryPerformanceCounter獲取當前的counter,即可獲取每一幀使用的時間。(當然現在有Unity,估計沒人關注這倆函數了)
雖然利用這兩個函數能夠精確的統計經過的時間,但是卻無法得到當前時間,並且以上兩個函數是Windows系統所特有的,unix/linux系統中並不具備。
為了獲取系統的當前精確時間,需要使用另一個系統函數
int gettimeofday(struct timeval *tv, struct timezone *tz);
獲取從1970年1月1日到現在經過的時間和時區(UTC時間),(按照linux的官方文檔,時區已經不再使用,正常應該傳NULL)。
#include <sys/time.h> struct timeval start_tv; gettimeofday(&start_tv, NULL); sleep(1000); struct timeval end_tv; gettimeofday(&end_tv, NULL); double time = (end_tv.tv_sec - start_tv.tv_sec) + (double)(end_tv.tv_usec - start_tv.tv_usec)/(double)1000000;
這樣同樣可以獲得精確到微秒的每幀經過的時間。服務器上的幀運行機制,一般便是這個時間函數來計算和同步。
如果不需要非常精確的時間,而只要精確到秒,可以使用另一個時間函數
time_t time(time_t* timer);
該函數返回一個UTC時間戳,如果傳入timer參數,則為timer設置時間戳的值。
然而以上兩個函數獲取的都是UTC時間戳,如果在游戲中需要顯示當前時區的時間,該怎么辦呢?
使用localtime或localtime_r,兩者效果一致,只是獲取結果參數位置不同。
struct tm *localtime(const time_t *timep); // 傳入UTC時間戳,返回當前時區的tm結構指針 struct tm *localtime_r(const time_t *timep, struct tm *result);
第一個函數獲取tm結構靜態變量的指針,第二個函數則傳入一個tm結構變量的地址,並為之賦值。最終得到的tm變量,存儲了當前時區的時間。
tm的結構定義如下,可以直接用來顯示。
struct tm { int tm_sec; /* seconds */ int tm_min; /* minutes */ int tm_hour; /* hours */ int tm_mday; /* day of the month */ int tm_mon; /* month */ int tm_year; /* year */ int tm_wday; /* day of the week */ int tm_yday; /* day in the year */ int tm_isdst; /* daylight saving time */ };
通常來說服務器和客戶端通信同步時間時,不會傳這么多int,只會傳一個int64的UTC時間戳,給客戶端自己轉成當前時區。另外服務器也不會每次都調一次localtime轉一次。一般來說,服務器和客戶端都是維護一個int64的當前時間的UTC時間戳,以及一個當前時區的偏移時間,(客戶端還會定時更新和服務器的時間誤差,對時)。
因此,需要用到另外兩個函數:
struct tm *gmtime(const time_t *timep); // time_t 到 tm 的轉換,前后都是UTC時間,沒有時區轉換 struct tm *gmtime_r(const time_t *timep, struct tm *result); // gmtime 傳入result方式 time_t mktime(struct tm *timeptr); // localtime的逆向操作,從當前時區的 tm 轉到UTC的 time_t
在進程啟動時
time_t t0 = 0; // 當前時間為0時 time_t t1 = mktime(gmtime(&t0)); // UTC的時間 int timezone_diff = (int)(t0 - t1); // 當前時區和UTC的時間差
每次需要拿到當前時區時間,只需要用 UTCStamp + timezone_diff 即可。
除了獲取時間,為了格式化顯示時間,我們也可以利用一些系統函數,格式化輸出時間字符串
char *asctime(const struct tm *tm); char *asctime_r(const struct tm *tm, char *buf); char ctime(const time_t *timep); char ctime_r(const time_t *timep, char *buf);
在游戲的實際應用中,一般都是根據具體需求來顯示格式化的時間,甚至要做一些邊界時間的特殊處理,因此這里都不詳細討論時間的格式化顯示了。