獲取網路時間(即獲取網路時間同步伺服器的時間)
阿新 • • 發佈:2019-01-27
要獲取準確的時間,用於校時或其他操作,可以通過獲取時間同步伺服器的資訊來實現。下面介紹幾個常用的時間同步伺服器的域名及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;
}
說明一點:那就是這樣校時存在一定的誤差,誤差的範圍很小,取決於網路延遲,要解決這個問題,可以設定一個計時器,取得網路延遲,加到獲得的時間資料後面。
另外,再介紹兩個API函式,用於設定時間,即SetLocalTime和SetSystemTime:
函式原型:
BOOL WINAPI SetLocalTime( _In_ constSYSTEMTIME *lpSystemTime);
函式功能:
設定當前本地時間及日期。
引數:
lpSystemTime 一個SYSTEMTIME結構的指標,包含了新的本地日期和時間。
SYSTEMTIME 結構wDayOfWeek成員被忽略。
返回值:
如果函式呼叫成功,則返回值為非零值。
如果函式失敗,返回值是零。 為了得到擴充套件的錯誤資訊,呼叫GetLastError函式
函式原型:
BOOL SetSystemTime(CONST SYSTEMTIME *lpSystemTime);
函式功能:
此函式設定當前系統的時間和日期
引數說明:
lpSystemTime:指向一個SYSTEMTIME資料結構.它接收當前系統的日期和時間.
總之,setsysemtime中的時間是格林尼治時間,setlocaltime中的是本地時間。最後,為了演示方便加一個按鈕,響應函式為:
void CGetNetworkTimeDlg::OnButton1()
{
// TODO: Add your control notification handler code here
SYSTEMTIME st = FormatServerTime( GetTimeFromServer("132.163.4.101") );
// 校時
SetSystemTime(&st);
//SetLocalTime(&st); // 用這個差八小時
CString m_Time;
m_Time.Format(_T("格林尼治時間為:%d年%d月%d日%d時%d分%d秒%d"), st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
MessageBox(m_Time);
m_Time.Format(_T("北京時間為:%d年%d月%d日%d時%d分%d秒%d"), st.wYear, st.wMonth, st.wDay, st.wHour+8, st.wMinute, st.wSecond, st.wMilliseconds);
MessageBox(m_Time);
}
下面演示程式:
第一步,將系統時間改為一個錯誤值,如圖:
第二步,執行程式,點選校時按鈕,如圖:
結果,如圖: