windows 程序设计之「NetTime.C」范例分析笔记

/*-------------------------------------------------------

   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 指定数据将被复制到缓冲区中,但并不从输入队列中删除。

      由于之前呼叫了WSAAsyncSelectrecv并不等待接收,而是立即传回错误代码,表示函数通常受阻,但这时没有受阻。

      理论上来说(但由于网络延迟所以不大可能),函数至少能传回数据的一部分,然后再次呼叫以获得其余的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值是从1900110: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被分割成了NNULL隔开的字符串组。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位的整数;

     // 用来表示从160111日至参数一中的时间间隔为100纳秒(nanosecond,十亿分之一秒)的间隔数。

     SystemTimeToFileTime (&stNew, &ftNew) ;

     // ftNew地址强制转换为LARGE_INTEGER类型的指针,再解除引用

     // LARGE_INTEGER 是一个union,允许64位的值可以被当成两个32位的值使用;

     // 或者当成一个__int64数据型态的64位整数使用(__int64数据型态是Microsoft编译器对ANSI C标准的扩充)。

     li = * (LARGE_INTEGER *) &ftNew ;

     // ulTime值是从1900110:00开始的UTC秒数,乘以一千万倍。

     // li.QuadPart 最终等于160111日至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,ddddm,mm,mmm,mmmmy,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) ;

}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值