1. 程式人生 > >獲取網路時間(即獲取網路時間同步伺服器的時間)

獲取網路時間(即獲取網路時間同步伺服器的時間)

    要獲取準確的時間,用於校時或其他操作,可以通過獲取時間同步伺服器的資訊來實現。下面介紹幾個常用的時間同步伺服器的域名及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);

}
下面演示程式:

第一步,將系統時間改為一個錯誤值,如圖:



第二步,執行程式,點選校時按鈕,如圖:


結果,如圖: