要獲取准確的時間,用於校時或其他操作,可以通過獲取時間同步服務器的信息來實現。下面介紹幾個常用的時間同步服務器的域名及IP地址:
域名 IP地址
time-a.nist.gov 129.6.15.28
time-b.nist.gov 129.6.15.29
time-a.timefreq.bldrdoc.gov 132.163.4.101
time-b.timefreq.bldrdoc 132.163.4.102
time-c.timefreq.bldrdoc.gov 132.163.4.103
utcnist.colorado.edu 128.138.140.44
time.nist.gov 192.43.244.18
time-nw.nist.gov 131.107.1.10
nist1.datum.com 66.243.43.21
nist1-dc.glassey.com 216.200.93.8
nist1-ny.glassey.com 208.184.49.9
nist1-sj.glassey.com 207.126.98.204
nist1.aol-ca.truetime.com 207.200.81.113
nist1.aol-va.truetime.com 205.188.185.33
國家授時 210.72.145.44
可以通過套接字實現對時間的獲取,但是獲取到的時間信息是 基於1900年1月1日0時0分0秒的信息,也就是說從時間同步服務器返回的是1900年1月1日0時0分0秒至今的秒數。顯然需要將其轉化為我們常用的時間格式。另外,還需注意一點:時差。
時間同步服務器返回的時間數據是基於世界時(GMT),也就是格林尼治所在地的標准時間。而北京時間與倫敦GMT存在8小時的時差。所以,在轉化過程中要考慮時差。
下面先介紹利用套接字獲取時間數據的函數GetTimeFromServer:
頭文件:
#include "winsock2.h"
#pragma comment(lib, "WS2_32.lib") // 顯式連接套接字庫
函數:
/************************************************************************/
/* 從時間同步服務器獲取時間信息 */
/************************************************************************/
DWORD CGetNetworkTimeDlg::GetTimeFromServer(char *ip_addr)
{
// 參數ip_addr:表示指定的時間服務器IP地址
// 返回:自1900年1月1日午0時0分0秒至今的毫秒數 或 0(表示獲取失敗)
// 默認的時間服務器為"國家授時中心"
if (ip_addr == NULL)
{
ip_addr = _T("210.72.145.44");
}
// 定義WSADATA結構體對象
WSADATA date;
// 定義版本號碼
WORD w = MAKEWORD(2, 0);
// 初始化套接字庫
if ( ::WSAStartup(w, &date) != 0 )
{
MessageBox(_T("初始化套接字庫失敗!"));
return 0;
}
// 定義連接套接字句柄
SOCKET s;
// 定義接收信息保存變量
DWORD m_serverTime;
// 創建TCP套接字
s = ::socket(AF_INET, SOCK_STREAM, 0);
if (INVALID_SOCKET == s)
{
MessageBox(_T("創建套接字失敗!"));
// 關閉套接字句柄
::closesocket(s);
// 釋放套接字庫
::WSACleanup();
return 0;
}
// 定義套接字地址結構
sockaddr_in addr;
// 初始化地址結構
addr.sin_family = AF_INET;
addr.sin_port = htons(37);
addr.sin_addr.S_un.S_addr = inet_addr(ip_addr);
// 連接
if ( ::connect(s, (sockaddr*)&addr, sizeof(addr)) !=0 )
{
int errorCode = ::WSAGetLastError();
switch(errorCode)
{
case 10060:
MessageBox(_T("連接超時!"));
break;
case 10051:
MessageBox(_T("網絡不可抵達!"));
break;
default:
char temp[20];
sprintf(temp, _T("WSAGetLastError()錯誤代碼:%d"), errorCode);
MessageBox(temp);
}
// 關閉套接字句柄
::closesocket(s);
// 釋放套接字庫
::WSACleanup();
return 0;
}
// 接收
if ( ::recv(s, (char *)&m_serverTime, 4, MSG_PEEK) <= 0 )
{
MessageBox(_T("接收錯誤!"));
// 關閉套接字句柄
::closesocket(s);
// 釋放套接字庫
::WSACleanup();
return 0;
}
// 關閉套接字句柄
::closesocket(s);
// 釋放套接字庫
::WSACleanup();
// 網絡字節順序轉換為主機字節順序
m_serverTime = ::ntohl(m_serverTime);
// 返回接收到的數據
return m_serverTime;
}
介紹另一個函數,用於將毫秒數(上述函數的返回值)轉化為SYSTEMTIME型時間:
/************************************************************************/
/* 將從毫秒數轉化為SYSTEMTIME */
/************************************************************************/
SYSTEMTIME CGetNetworkTimeDlg::FormatServerTime(DWORD serverTime)
{
FILETIME ftNew ;
SYSTEMTIME stNew ;
stNew.wYear = 1900 ;
stNew.wMonth = 1 ;
stNew.wDay = 1 ;
stNew.wHour = 0 ;
stNew.wMinute = 0 ;
stNew.wSecond = 0 ;
stNew.wMilliseconds = 0 ;
::SystemTimeToFileTime (&stNew, &ftNew);
/* 將SYSTEMTIME結構設定為1900年1月1日午夜(0時)。
並將這個SYSTEMTIME結構傳遞給SystemTimeToFileTime,將此結構轉化為FILETIME結構。
FILETIME實際上只是由兩個32位元的DWORD一起組成64位元的整數,
用來表示從1601年1月1日至今間隔為100奈秒(nanosecond)的間隔數。 */
LARGE_INTEGER li ; //64位大整數
li = * (LARGE_INTEGER *) &ftNew;
li.QuadPart += (LONGLONG) 10000000 * serverTime;
ftNew = * (FILETIME *) &li;
::FileTimeToSystemTime (&ftNew, &stNew);
// 返回時間(注意:這里返回的是格林尼治時間,與北京時間相差8小時)
return stNew;
}
說明一點:那就是這樣校時存在一定的誤差,誤差的范圍很小,取決於網絡延遲,要解決這個問題,可以設置一個計時器,取得網絡延遲,加到獲得的時間數據后面。