/*-------------------------------------------------------
NETTIME.C -- Sets System Clock from Internet Services
(c) Charles Petzold, 1998
-------------------------------------------------------*/
#include <windows.h>
#include "resource.h"
#define WM_SOCKET_NOTIFY (WM_USER + 1)
#define ID_TIMER 1
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
BOOL CALLBACK MainDlg (HWND, UINT, WPARAM, LPARAM) ;
BOOL CALLBACK ServerDlg (HWND, UINT, WPARAM, LPARAM) ;
void ChangeSystemTime (HWND hwndEdit, ULONG ulTime) ;
void FormatUpdatedTime (HWND hwndEdit, SYSTEMTIME * pstOld,
SYSTEMTIME * pstNew) ;
void EditPrintf (HWND hwndEdit, TCHAR * szFormat, ...) ;
HINSTANCE hInst ;
HWND hwndModeless ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT ("NetTime") ;
HWND hwnd ;
MSG msg ;
RECT rect ;
WNDCLASS wndclass ;
hInst = hInstance ;
wndclass.style = 0 ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = NULL ;
wndclass.hbrBackground = NULL ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass))
{
MessageBox (NULL, TEXT ("This program requires Windows NT!"),
szAppName, MB_ICONERROR) ;
return 0 ;
}
hwnd = CreateWindow (szAppName, TEXT ("Set System Clock from Internet"),
WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU |
WS_BORDER | WS_MINIMIZEBOX,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
// 创建模态对话框
hwndModeless = CreateDialog (hInstance, szAppName, hwnd, MainDlg) ;
// 获取模态对话框大小
GetWindowRect (hwndModeless, &rect) ;
// 该函数依据所需客户矩形的大小,计算需要的窗口矩形的大小
// 参数二 指定将被计算尺寸的窗口的窗口风格
// 参数三 指定窗口是否拥有菜单
AdjustWindowRect (&rect, WS_CAPTION | WS_BORDER, FALSE) ;
// 该函数改变一个子窗口,弹出式窗口或顶层窗口的尺寸,位置和Z序
// SWP_NOMOVE:维持当前位置(忽略参数三、四)
// 该函数更改程序主窗口大小,以保证能被hwndModeless对话框覆盖
SetWindowPos (hwnd, NULL, 0, 0, rect.right - rect.left,
rect.bottom - rect.top, SWP_NOMOVE) ;
// 该函数设置指定窗口的显示状态
// SW_SHOW 在窗口原来的位置以原来的尺寸激活和显示窗口
ShowWindow (hwndModeless, SW_SHOW) ;
// 显示程序主窗口
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
// Normal message loop when a modeless dialog box is used.
while (GetMessage (&msg, NULL, 0, 0))
{
if (hwndModeless == 0 || !IsDialogMessage (hwndModeless, &msg))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
}
return msg.wParam ;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_SETFOCUS: // 传递焦点
SetFocus (hwndModeless) ;
return 0 ;
case WM_DESTROY:
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
BOOL CALLBACK MainDlg (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static char szIPAddr[32] = { "132.163.135.130" } ;
static HWND hwndButton, hwndEdit ;
static SOCKET sock ;
static struct sockaddr_in sa ;
static TCHAR szOKLabel[32] ;
int iError, iSize ;
unsigned long ulTime ;
WORD wEvent, wError ;
WSADATA WSAData ;
switch (message)
{
case WM_INITDIALOG:
// 获取对话框窗口中指定控件的句柄
hwndButton = GetDlgItem (hwnd, IDOK) ;
hwndEdit = GetDlgItem (hwnd, IDC_TEXTOUT) ;
return TRUE ;
case WM_COMMAND:
switch (LOWORD (wParam))
{
case IDC_SERVER: // 点击服务器列表按钮时
// 创建模态对话框,用于显示服务器列表
// 该对话框的回调函数为ServerDlg,自定义传递参数为szIPAddr(用于获取服务器IP)
DialogBoxParam (hInst, TEXT ("Servers"), hwnd, ServerDlg,
(LPARAM) szIPAddr) ;
return TRUE ;
case IDOK:
// WSAStartup 函数完成对Winsock服务的初始化
// 第一个参数指明程序请求使用的Socket版本,其中高位字节指明副版本、低位字节指明主版本。
// MAKEWORD 宏创建一个被指定变量连接而成的WORD变量。返回一个WORD变量。参数一为低8位。
// WSAData 用于装载操作系统利用第二个参数返回请求的Socket的版本信息
// 返回值 0 表示成功
if (iError = WSAStartup (MAKEWORD(2,0), &WSAData))
{
// 如果有错误代码返回,则在文本控件中显示错误代码
EditPrintf (hwndEdit, TEXT ("Startup error #%i./r/n"),
iError) ;
return TRUE ;
}
// WSAData.szDescription 记录了获取的Winsock简要信息
EditPrintf (hwndEdit, TEXT ("Started up %hs/r/n"),
WSAData.szDescription);
// socket 函数创建一个能够进行网络通信的套接字
// AF_INET 指定应用程序使用的通信协议的协议族.表示此处是某种Internet地址
// SOCK_STREAM 指定要创建的套接字类型,流套接字类型为SOCK_STREAM、数据封包套接字类型为SOCK_DGRAM
// IPPROTO_TCP 指定应用程序所使用的通信协议。
// 调用成功就返回新创建的套接字的描述符,如果失败就返回INVALID_SOCKET
sock = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP) ;
if (sock == INVALID_SOCKET)
{
EditPrintf (hwndEdit,
TEXT ("Socket creation error #%i./r/n"),
// 获得上次失败操作的错误代码
WSAGetLastError ()) ;
// 中止Windows Sockets DLL的使用
WSACleanup () ;
return TRUE ;
}
// 显示创建的套接字的描述符
EditPrintf (hwndEdit, TEXT ("Socket %i created./r/n"), sock) ;
// WSAAsyncSelect函数强制阻碍性的函数转为非阻碍性的,即在函数执行完之前把控制权传回给程序
// WM_SOCKET_NOTIFY 程序自订消息,当满足触发条件时,由系统发送给参数二指定的窗口处理程序
// FD_CONNECT 设置触发条件,欲接收已连接好时。
// FD_READ 设置触发条件,欲接收读准备好时。
// 返回值 0 表示成功
if (SOCKET_ERROR == WSAAsyncSelect (sock, hwnd, WM_SOCKET_NOTIFY,
FD_CONNECT | FD_READ))
{
EditPrintf (hwndEdit,
TEXT ("WSAAsyncSelect error #%i./r/n"),
WSAGetLastError ()) ;
// 该函数关闭一个套接口,它释放套接口描述符sock
closesocket (sock) ;
WSACleanup () ;
return TRUE ;
}
// AF_INET 用于表示地址种类,此处是某种Internet地址
sa.sin_family = AF_INET ;
// sin_port 指定为欲连接的端口
// 当大多数数字通过Internet时,这个端口号字段必须是(big-endian)的,即最高的字节排第一个。
// Intel微处理器是little endian ,低位在前。所以用htons将一个无符号短整型数值转换为网络字节序,即(big-endian)
sa.sin_port = htons (IPPORT_TIMESERVER) ;
// inet_addr函数将储存在szIPAddr字符串中的服务器地址转化为无正负号长整数
sa.sin_addr.S_un.S_addr = inet_addr (szIPAddr) ;
// 建立与一个端的连接
// 由于之前呼叫了WSAAsyncSelect,所以connect不会等待连结,它会立即传回SOCKET_ERROR的值。
// 这并不是出现了错误,这只是表示现在还没有联机成功而已。当连接成功或失败时,会触发WM_SOCKET_NOTIFY消息
connect(sock, (SOCKADDR *) &sa, sizeof (sa)) ;
/* WSAEWOULDBLOCK 表示 Output Buffer 已经满了,无法再写入数据。
可以理解为当程序发送数据给对方,对方的接收速度没有程序发送的快或者对方的接受缓冲区已被填满,
所以就返回一个“忙”的标志,而这时程序再发多少数据都没任何意义,这时系统就抛出该异常通知。*/
if (WSAEWOULDBLOCK != (iError = WSAGetLastError ()))
{
EditPrintf (hwndEdit, TEXT ("Connect error #%i./r/n"),
iError) ;
closesocket (sock) ;
WSACleanup () ;
return TRUE ;
}
// 显示正在连接服务器
EditPrintf (hwndEdit, TEXT ("Connecting to %hs..."), szIPAddr) ;
// 设置一个定时器,用于打印“.”
SetTimer (hwnd, ID_TIMER, 1000, NULL) ;
// 获取hwndButton按钮的文字,保存到szOKLabel缓冲区
GetWindowText (hwndButton, szOKLabel, sizeof (szOKLabel) /
sizeof (TCHAR)) ;
// 更改hwndButton按钮的文字。
SetWindowText (hwndButton, TEXT ("Cancel")) ;
// GWL_ID 设置一个新的窗口标识符
SetWindowLong (hwndButton, GWL_ID, IDCANCEL) ;
return TRUE ;
// 取消连接被点击时
case IDCANCEL:
closesocket (sock) ;
sock = 0 ;
WSACleanup () ;
SetWindowText (hwndButton, szOKLabel) ;
SetWindowLong (hwndButton, GWL_ID, IDOK) ;
KillTimer (hwnd, ID_TIMER) ;
EditPrintf (hwndEdit, TEXT ("/r/nSocket closed./r/n")) ;
return TRUE ;
// 点击关闭窗口时
case IDC_CLOSE:
if (sock)
SendMessage (hwnd, WM_COMMAND, IDCANCEL, 0) ;
// GetParent 函数获得一个指定子窗口的父窗口句柄
DestroyWindow (GetParent (hwnd)) ;
return TRUE ;
}
return FALSE ;
case WM_TIMER:
EditPrintf (hwndEdit, TEXT (".")) ;
return TRUE ;
// WSAAsyncSelect函数中的自订消息
case WM_SOCKET_NOTIFY:
// 低字组包含了当前消息的触发条件
wEvent = WSAGETSELECTEVENT (lParam) ; // ie, LOWORD
// 高字组包含了错误代码,0表示没有错误
wError = WSAGETSELECTERROR (lParam) ; // ie, HIWORD
// Process two events specified in WSAAsyncSelect
switch (wEvent)
{
// 当连接成功或失败时
case FD_CONNECT:
EditPrintf (hwndEdit, TEXT ("/r/n")) ;
// 判断是否为连接失败
if (wError)
{
EditPrintf (hwndEdit, TEXT ("Connect error #%i."),
wError) ;
SendMessage (hwnd, WM_COMMAND, IDCANCEL, 0) ;
return TRUE ;
}
EditPrintf (hwndEdit, TEXT ("Connected to %hs./r/n"), szIPAddr) ;
/* 试图从一个套接口接收数据
sock 已连接上的套接口的描述符
ulTime 用于接收数据的缓冲区
4 缓冲区长度
MSG_PEEK 指定数据将被复制到缓冲区中,但并不从输入队列中删除。
由于之前呼叫了WSAAsyncSelect,recv并不等待接收,而是立即传回错误代码,表示函数通常受阻,但这时没有受阻。
理论上来说(但由于网络延迟所以不大可能),函数至少能传回数据的一部分,然后再次呼叫以获得其余的32个字节值。
这就是呼叫recv函数时带有MSG_PEEK选项的原因。
当服务器端准备好,程序可以接收数据时,将再次触发WM_SOCKET_NOTIFY消息,并包含FD_READ值 */
recv (sock, (char *) &ulTime, 4, MSG_PEEK) ;
EditPrintf (hwndEdit, TEXT ("Waiting to receive...")) ;
return TRUE ;
// 当可以接收数据时
case FD_READ:
KillTimer (hwnd, ID_TIMER) ;
EditPrintf (hwndEdit, TEXT ("/r/n")) ;
// 做错误检测
if (wError)
{
EditPrintf (hwndEdit, TEXT ("FD_READ error #%i."),
wError) ;
SendMessage (hwnd, WM_COMMAND, IDCANCEL, 0) ;
return TRUE ;
}
// 正式接收时间数据,最后的参数是0,用于从队列中删除数据
// 返回值 若无错误发生,返回读入的字节数。如果连接已中止,返回0。否则的话,返回SOCKET_ERROR错误
iSize = recv (sock, (char *) &ulTime, 4, 0) ;
// ntohl 将一个无符号长整形数从网络字节顺序转换为计算机字节顺序。
ulTime = ntohl (ulTime) ;
// 接收的32位的ulTime值是从1900年1月1日0:00开始的UTC秒数,但是为(big-endian)字序
EditPrintf (hwndEdit,
TEXT ("Received current time of %u seconds ")
TEXT ("since Jan. 1 1900./r/n"), ulTime) ;
// 更改系统时间,发送取消连接消息
ChangeSystemTime (hwndEdit, ulTime) ;
SendMessage (hwnd, WM_COMMAND, IDCANCEL, 0) ;
return TRUE ;
}
return FALSE ;
}
return FALSE ;
}
BOOL CALLBACK ServerDlg (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static char * szServer ;
static WORD wServer = IDC_SERVER1 ;
char szLabel [64] ;
switch (message)
{
case WM_INITDIALOG:
// 保存服务器IP传递缓冲区
szServer = (char *) lParam ;
// 该函数给一组单选按钮中的一个指定按钮加上选中标志,并且清除组中其他按钮的选中标志
// 默认情况下所有的单选按钮都在一个组内
CheckRadioButton (hwnd, IDC_SERVER1, IDC_SERVER10, wServer) ;
return TRUE ;
case WM_COMMAND:
switch (LOWORD (wParam))
{
case IDC_SERVER1:
case IDC_SERVER2:
case IDC_SERVER3:
case IDC_SERVER4:
case IDC_SERVER5:
case IDC_SERVER6:
case IDC_SERVER7:
case IDC_SERVER8:
case IDC_SERVER9:
case IDC_SERVER10:
// 保存选中的单选按钮标识符
wServer = LOWORD (wParam) ;
return TRUE ;
case IDOK:
// 获取选中的单选按钮的文本到szLabel缓冲区
GetDlgItemTextA (hwnd, wServer, szLabel, sizeof (szLabel)) ;
// 分解字符串为一组字符串,首次调用时,参数一指向要分解的字符串,之后再次调用要把参数一设成NULL
// 参数二为分隔符字符串,函数在szLabel中查找包含在参数二中的字符并用NULL来替换,直到找遍整个字符串。
// 成功执行后,szLabel被分割成了N个NULL隔开的字符串组。szLabel指向第一个分隔符后的字符串首地址。
// 该函数已被strsep函数替代。
strtok (szLabel, "(") ;
// strtok 的第二次调用,从"("之后的第一个字符开始执行分割。返回值正好为IP地址字符串
// 然后复制IP地址,到用于传递的缓冲区中。
strcpy (szServer, strtok (NULL, ")")) ;
EndDialog (hwnd, TRUE) ;
return TRUE ;
case IDCANCEL:
EndDialog (hwnd, FALSE) ;
return TRUE ;
}
break ;
}
return FALSE ;
}
void ChangeSystemTime (HWND hwndEdit, ULONG ulTime)
{
FILETIME ftNew ;
LARGE_INTEGER li ;
SYSTEMTIME stOld, stNew ;
// 该函数用来获取本机当前系统日期和时间
GetLocalTime (&stOld) ;
stNew.wYear = 1900 ;
stNew.wMonth = 1 ;
stNew.wDay = 1 ;
stNew.wHour = 0 ;
stNew.wMinute = 0 ;
stNew.wSecond = 0 ;
stNew.wMilliseconds = 0 ;
// 该函数根据参数一的数据,填写FILETIME结构
// FILETIME结构实际上只是由两个32位的DWORD一起组成64位的整数;
// 用来表示从1601年1月1日至参数一中的时间间隔为100纳秒(nanosecond,十亿分之一秒)的间隔数。
SystemTimeToFileTime (&stNew, &ftNew) ;
// 将ftNew地址强制转换为LARGE_INTEGER类型的指针,再解除引用
// LARGE_INTEGER 是一个union,允许64位的值可以被当成两个32位的值使用;
// 或者当成一个__int64数据型态的64位整数使用(__int64数据型态是Microsoft编译器对ANSI C标准的扩充)。
li = * (LARGE_INTEGER *) &ftNew ;
// ulTime值是从1900年1月1日0:00开始的UTC秒数,乘以一千万倍。
// li.QuadPart 最终等于1601年1月1日至ulTime时间,间隔为100纳秒的间隔数
li.QuadPart += (LONGLONG) 10000000 * ulTime ;
ftNew = * (FILETIME *) &li ;
// 将作为最终计算结果的FILETIME值转换回SYSTEMTIME结构
FileTimeToSystemTime (&ftNew, &stNew) ;
// 设置当前系统的时间和日期;执行成功,返回值为TRUE,如果发生了错误,则返回FALSE。
// 调用者必须具有SE_SYSTEMTIME_NAME权限,函数的执行才会成功.
if (SetSystemTime (&stNew))
{
// 获取系统新的时间,和之前的旧时间一同发送给自定义函数,用于显示更新状态
GetLocalTime (&stNew) ;
FormatUpdatedTime (hwndEdit, &stOld, &stNew) ;
}
else
// 执行失败时,显示错误消息
EditPrintf (hwndEdit, TEXT ("Could NOT set new date and time.")) ;
}
void FormatUpdatedTime (HWND hwndEdit, SYSTEMTIME * pstOld, SYSTEMTIME * pstNew)
{
TCHAR szDateOld [64], szTimeOld [64], szDateNew [64], szTimeNew [64] ;
// 针对指定的“当地”格式,对一个系统日期进行格式化
// 参数一 用于决定格式的地方ID。参数四中指定的任何信息(倘若不是NULL)都优先于特定于地方的信息
// LOCALE_USER_DEFAULT 指定使用用户或进程的默认区域设置,该常数的值为0x0400
// 参数二 如指定了lpFormat,该参数应该为零。否则可设为LOCALE_NOUSEROVERRIDE,强制使用系统地方参数;
// DATE_SHORTDATE 或 DATE_LONGDATE 用于选择不同的日期格式
// pstOld 包含了一个系统日期的结构
// 参数四 用于指定使用特定于不同地方的值;例如d,dd,ddd,dddd或m,mm,mmm,mmmm或y,yy,yyyy这样的字符串
// szDateOld 指定一个缓冲区,用于容纳格式化过后的字串
// 最后是缓冲区的长度
GetDateFormat (LOCALE_USER_DEFAULT, LOCALE_NOUSEROVERRIDE | DATE_SHORTDATE,
pstOld, NULL, szDateOld, sizeof (szDateOld)) ;
// 针对指定的“当地”格式,对一个系统时间进行格式化
// TIME_NOTIMEMARKER 表示不使用时间标记;例如"AM"和"PM"
// TIME_FORCE24HOURFORMAT 表示始终使用24小时时间格式
GetTimeFormat (LOCALE_USER_DEFAULT, LOCALE_NOUSEROVERRIDE |
TIME_FORCE24HOURFORMAT | TIME_NOTIMEMARKER ,
pstOld, NULL, szTimeOld, sizeof (szTimeOld)) ;
GetDateFormat (LOCALE_USER_DEFAULT, LOCALE_NOUSEROVERRIDE | DATE_SHORTDATE,
pstNew, NULL, szDateNew, sizeof (szDateNew)) ;
GetTimeFormat (LOCALE_USER_DEFAULT, LOCALE_NOUSEROVERRIDE |
TIME_NOTIMEMARKER | TIME_FORCE24HOURFORMAT,
pstNew, NULL, szTimeNew, sizeof (szTimeNew)) ;
// 显示更新前后时间信息
EditPrintf (hwndEdit,
TEXT ("System date and time successfully changed ")
TEXT ("from/r/n/t%s, %s.%03i to/r/n/t%s, %s.%03i."),
szDateOld, szTimeOld, pstOld->wMilliseconds,
szDateNew, szTimeNew, pstNew->wMilliseconds) ;
}
void EditPrintf (HWND hwndEdit, TCHAR * szFormat, ...)
{
TCHAR szBuffer [1024] ;
va_list pArgList ;
va_start (pArgList, szFormat) ;
wvsprintf (szBuffer, szFormat, pArgList) ;
va_end (pArgList) ;
SendMessage (hwndEdit, EM_SETSEL, (WPARAM) -1, (LPARAM) -1) ;
SendMessage (hwndEdit, EM_REPLACESEL, FALSE, (LPARAM) szBuffer) ;
SendMessage (hwndEdit, EM_SCROLLCARET, 0, 0) ;
}