一、網絡授時服務
網絡授時服務是在網絡上設置一些時間服務器,用戶通過Internet訪問這些時間服務器就可同步本地計算機時鐘的服務。網絡授時服務有三個協議,分別是Network Time Protocol (RFC-1305),Daytime Protocol (RFC-867),Time Protocol (RFC-868)。
有關這些協議的詳細信息,可參考以下網站:
http://www.boulder.nist.gov/timefreq/service/its.htm
http://www.faqs.org/rfcs/rfc867.html
http://www.faqs.org/rfcs/rfc1305.html
http://www.faqs.org/rfcs/rfc868.html
我的程序中列出的時間服務器列表,主要來自:
http://www.boulder.nist.gov/timefreq/service/time-servers.html
更多的時間服務器列表請參考以下網站:
http://www.eecis.udel.edu/~mills/ntp/servers.html
二、Time Protocol (RFC-868)協議
Time Protocol (RFC-868)協議是一種較簡單的協議。此協議提供了一個獨立於站點的,機器可讀的日期和時間信息。時間服務返回的是以秒數,是從1900年1月1日午夜到現在的秒數。
這個協議可以工作在TCP和UDP協議下。下面是通過TCP協議工作的時間協議的工作過程:這裡S代表服務器,C代表客戶。
S: 檢測端口37
U: 連接到端口37
S: 以32位二進制數發送時間
U: 接收時間
U: 關閉連接
S: 關閉連接
如果服務器不能決定現在是什麼時間,服務器會拒絕連接或不發送任何數據而直接關閉連接。
下面我們看看使用UDP協議的情況:這裡S代表服務器,C代表客戶。
S: 檢測端口37
U: 發送一個空數據報到端口37
S: 接收這個空數據報
S: 發送包含32位二進制數(用於表示時間)的數據報
U: 接收時間數據報
如果服務器不能決定現在是什麼時間,服務器會拋棄接收到的數據報而不作出任何應答。
三、網絡對時的程序實現
下面是使用TCP協議的實現網絡對時的部分代碼。GetRemoteTime 函數主要通過連接服務器szSever,並取得其回傳的32位值:
BOOL GetRemoteTime(char* szSever, unsigned long& ulTime)
{
SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0); //使用UDP協議
if(sock == INVALID_SOCKET)
{
return FALSE;
}
sockaddr_in severAddr;
severAddr.sin_family = AF_INET;
severAddr.sin_port = htons(NET_TIME_PORT);
severAddr.sin_addr.S_un.S_addr = inet_addr(szSever);
if (sendto(sock, (char*)&ulTime, 4, 0, (sockaddr*)&severAddr, sizeof(severAddr)) == 4)
{
unsigned long flag = 1;
if ((ioctlsocket(sock, FIONBIO, &flag) == 0))
{
struct fd_set mask;
FD_ZERO(&mask);
FD_SET(sock, &mask);
struct timeval timeout;
timeout.tv_sec = TIMEOUT_RECEIVE;
timeout.tv_usec = 0;
if (select(0, &mask, NULL, NULL, &timeout) == 1)
{
if (recv(sock, (char*)&ulTime, 4, 0) == 4)
{
ulTime = ntohl(ulTime);
closesocket(sock);
return TRUE;
}
}
}
}
closesocket(sock);
return FALSE;
}
MySetTime 函數的功能是將32位值轉換為系統時間,並設置系統時間。
void MySetTime(unsigned long ulTime)
{
FILETIME ft;
SYSTEMTIME st;
st.wYear = 1900;
st.wMonth = 1;
st.wDay = 1;
st.wHour = 0;
st.wMinute = 0;
st.wSecond = 0;
st.wMilliseconds = 0;
SystemTimeToFileTime(&st, &ft);
LARGE_INTEGER li = *(LARGE_INTEGER*)&ft;
li.QuadPart += (LONGLONG)10000000 * ulTime;
ft = *(FILETIME*)&li;
FileTimeToSystemTime(&ft, &st);
SetSystemTime(&st);
}
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
//初始化TCP協議
WSADATA wsaData;
if(WSAStartup(MAKEWORD(2,2), &wsaData)!= 0)
{
MessageBox(NULL, "初始化網絡協議失敗!", "錯誤報告", MB_OK|MB_ICONHAND);
return -1;
}
int i = 0;
unsigned long ulTime = 0;
while (sever[i] != NULL)
{
if (GetRemoteTime(sever[i], ulTime))
{
MySetTime(ulTime);
char buff[100];
sprintf(buff, "已成功與時間服務器\r\n%s\r\n的時間同步", sever[i]);
MessageBox(NULL, buff, "成功報告", MB_OK|MB_ICONINFORMATION);
return 0;
}
i++;
}
MessageBox(NULL, "所有服務器均不能正常連接或超時!", "錯誤報告", MB_OK|MB_ICONHAND);
WSACleanup();
return 0;
}
至於使用UDP協議實現程序詳見本文附帶的代碼。
四、結束語
程序在VC6+WinXP下編寫調試正確,並在Win98下運行正確。時間精度本人不敢妄下結論,但經與電視台對時,應小於1秒。也可到國家授時中心上去對時。但通常第一次打開這個網頁時服務器時間和本地時間差別大些,多刷新幾次又幾乎一致了。
本文配套源碼