第23章 尝试互联网(3)

23.3.2 以非阻塞方式工作的TCP聊天室客户端

(1)WSAAsyncSelect函数——设置非阻塞模式

参数

含义

SOCKET s

套接字句柄

HWND hWnd

套接字的通知消息将被发往的hwnd的窗口过程

unsigned int wMsg

自定义通知消息的编号,如

#define WM_SOCKET WM_USER+XXX中任取一个。

long lEvent

指定哪些通知码需要发送,可以是以下通知知的组合

①FD_READ:套接字收到对端发送过来的数据,表明可以去读套接字了。

②FD_WRITE:当短时间内向一个套接字发送太多数据造成缓冲区满以后,send函数会返回出错信息,当缓冲区再次有空的时候,WinSock通过这个通知码告知应用程序,表示可以继续发送数据了。但是缓冲区未溢的情况下,数据被发送完毕的时候并不会发送这个通知码

③FD_ACCEPT:监听中的套接字检测到有连接进入

④FD_CONNECT:如果用一个套接字去连接对方主机,当连接动作完成以后将收到这个通知码。当connect调用以后,是否应该成功,会通知该通知码告知应用程序(不管是成功还是失败应用程序都会收到此通知。

⑤FD_CLOSE:当套接字连接被对方关闭。(即对方关闭自己的套接字,这个动作会被WinSock接收到,并通过该通知码告知我们的应用程序)。

★注意UDP没有FD_CONNECT、FD_CLOSE、FD_ACCEPT是没有意义的。

(2)通知消息:WM_SOCKET(在上述WSAAsyncSelect指定的自定义Socket消息)

 参数

含义

wParam

触发消息的套接字句柄(可能多个套接字绑定到同一个窗口中)

lParam

LOWORD(lParam)——通知码(如FD_READ)

HIWORD(lParam)——错误代码(0表示函数执行成功。失败时为出错代码,相当于阻塞模式下调用了WSAGetLastError后得到的代码)

(3)非阻塞模式下网络程序常见的结构

 

【使用 TCP 协议的聊天室客户端程序】(非阻塞模式)
 效果图:与阻塞模式的客户端界面一样
使用的上次的:Message.h、resource.h、ChatClient.rc 3个文件

/*--------------------------------------------------------------------
 CHATCLIENT(NONBLOCK).C —— 使用 TCP 协议的聊天室客户端程序(非阻塞模式)
; 本例子使用非阻塞模式socket    (c)浅墨浓香,2015.7.2
--------------------------------------------------------------------*/
#include <windows.h>
#include <strsafe.h>
#include "..\ChapClient\resource.h"
#include "..\ChapService\Message.h"

#pragma  comment(lib,"WS2_32.lib")

#define TCP_PORT      9999
#define WM_SOCKET     WM_USER + 100

TCHAR   szAppName[] = TEXT("ChatClient(NonBlock)");
TCHAR   szErrIP[] = TEXT("无效的服务器IP地址!");
TCHAR   szErrConnect[] = TEXT("无法连接到服务器!");
TCHAR   szErrLogin[] = TEXT("无法登录到服务器,请检查用户名密码!");
TCHAR   szSpar[] = TEXT(" : ");

typedef struct _tagSOCKPARAMS
{
    TCHAR   szUserName[12];
    TCHAR   szPassword[12];
    TCHAR   szText[256];
    char    szServer[16];
    HWND    hWinMain;
    SOCKET  sock;
    int     nLastTime;

    MSGSTRUCT* szSendMsg;
    MSGSTRUCT* szRecvMsg;
    int cbSendBufSize;
    int cbRecvBufSize;
    int nStep;

}SOCKPARAMS,*PSOCKPARAMS;

BOOL CALLBACK DlgProc(HWND, UINT, WPARAM, LPARAM);
DWORD WINAPI WorkThread(LPVOID lpParameter);


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int nCmdShow)
{
    if (-1==DialogBox(hInstance, TEXT("ChatClient"), NULL, DlgProc))
    {
        MessageBox(NULL, TEXT("This program requires Windows NT!"), szAppName, MB_OK | MB_ICONEXCLAMATION);
    }
    return 0;
}

void EnableWindows(HWND hwnd, BOOL bEnable)
{
    EnableWindow(GetDlgItem(hwnd,IDC_SERVER), bEnable);
    EnableWindow(GetDlgItem(hwnd, IDC_USER),  bEnable);
    EnableWindow(GetDlgItem(hwnd, IDC_PASS),  bEnable);
    EnableWindow(GetDlgItem(hwnd, IDC_LOGIN), bEnable);
}

/*********************************************************************
   断开连接
*********************************************************************/
void DisConnect(SOCKPARAMS* pParams)
{
    EnableWindow(GetDlgItem(pParams->hWinMain, IDC_TEXT), FALSE);
    EnableWindow(GetDlgItem(pParams->hWinMain, IDC_LOGOUT), FALSE);
    if (pParams->sock)
    {
        closesocket(pParams->sock);
        pParams->sock = 0;
    }
    EnableWindows(pParams->hWinMain,TRUE);
}

/*********************************************************************
  连接到服务器 
*********************************************************************/
void Connect(SOCKPARAMS* pParams)
{
    SOCKADDR_IN sa;
    int iRet;

    EnableWindows(pParams->hWinMain, FALSE);
    pParams->nStep = 0;
    pParams->cbRecvBufSize = 0;
    pParams->cbSendBufSize = 0;

    memset(&sa, 0, sizeof(SOCKADDR_IN));
    
    iRet = inet_addr(pParams->szServer);

    if (iRet == INADDR_NONE)
    {
        MessageBox(pParams->hWinMain, szErrIP, szAppName, MB_OK | MB_ICONSTOP);
        DisConnect(pParams);
    }
    sa.sin_family = AF_INET;
    sa.sin_port = htons(TCP_PORT);
    sa.sin_addr.S_un.S_addr = iRet;

    pParams->sock = socket(AF_INET, SOCK_STREAM, 0);
    
    //将socket设置为非阻塞模式
    WSAAsyncSelect(pParams->sock, pParams->hWinMain, WM_SOCKET,
                                FD_CONNECT | FD_READ | FD_CLOSE | FD_WRITE);

    //连接到服务器
    if (SOCKET_ERROR == connect(pParams->sock, (PSOCKADDR)&sa, sizeof(SOCKADDR_IN)))
    {
        if (WSAEWOULDBLOCK != WSAGetLastError())  //WSAEWOULDBLOCK说明正常!
        {
            MessageBox(pParams->hWinMain, szErrConnect, szAppName, MB_OK | MB_ICONSTOP);
            DisConnect(pParams);
        }
    }
}

/*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  发送缓冲区中的数据,上次的数据有可能未发送完,故每次发送前,先将发送缓冲区合并
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/
void SendData(SOCKPARAMS* pParams, int cbSize)
{
    int iRet;
    BYTE* pBuffer = (BYTE*)pParams->szSendMsg;

    //将要发送的内容加到缓冲区的尾部
    if (cbSize != 0)
        CopyMemory(pBuffer + pParams->cbSendBufSize, pBuffer, cbSize);

    pParams->cbSendBufSize += cbSize;

    while (pParams->cbSendBufSize>0)
    {
        //发送缓冲区数据
        iRet = send(pParams->sock, pBuffer, pParams->cbSendBufSize, 0);
        if (SOCKET_ERROR == iRet)
        {
            if (WSAEWOULDBLOCK == WSAGetLastError()) //缓冲区己满,正在等待发送
            {
                //灰色聊天语句输入框和发送按钮,防止继续输入聊天语句
                EnableWindow(GetDlgItem(pParams->hWinMain, IDC_TEXT), FALSE);
                EnableWindow(GetDlgItem(pParams->hWinMain, IDOK), FALSE);
            }
            else
            {
                DisConnect(pParams);
            }
            break;
        }
        //将剩下未发送的字节移到缓冲区的最前面
        pParams->cbSendBufSize -= iRet;
        CopyMemory(pBuffer, pBuffer + iRet, pParams->cbSendBufSize);
    }
    return;
}

/*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  非阻塞模下式的处理消息
  注意:阻塞模式下,程序是按顺序执行的。逻辑上的第1步是登录,第2步是发送聊天语句
        那么程序在第2步执行时,就可以确定第1步己经执行过了。但非阻塞模式下,则不同
        不管是哪一步先,程序总是在同样的窗口过程中执行(这就是消息驱动的弊端)。
        因为这是消息驱动的,我们也就无法确定哪步先执行了,所以设计一个用来记录程序
        逻辑状态的变量nStep
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/
void ProcMessage(SOCKPARAMS* pParams)
{
    MSGSTRUCT* pMsg;
    BYTE szBuffer[512];

    pMsg = pParams->szRecvMsg;

    switch (pMsg->MsgHead.nCmdID)
    {
    case CMD_LOGIN_RESP:
        if (0==pMsg->LoginResp.dbResult)
        {
            MessageBox(pParams->hWinMain, szErrLogin, szAppName, MB_OK | MB_ICONSTOP);
            DisConnect(pParams);
        }
        else  //登录成功
        {
            pParams->nStep = 1;
            EnableWindow(GetDlgItem(pParams->hWinMain, IDOK), FALSE);
            EnableWindow(GetDlgItem(pParams->hWinMain, IDC_TEXT), TRUE);
            EnableWindow(GetDlgItem(pParams->hWinMain, IDC_LOGOUT), TRUE);
        }
        return;

    case CMD_MSG_DOWN:
        if (pParams->nStep<1)
            DisConnect(pParams);
        else
        {
            StringCchCopy((TCHAR*)szBuffer, lstrlen(pMsg->MsgDown.szSender) + 1, pMsg->MsgDown.szSender);
            StringCchCat((TCHAR*)szBuffer, lstrlen((TCHAR*)szBuffer) + lstrlen(szSpar) + 1, szSpar);
            StringCchCat((TCHAR*)szBuffer, lstrlen((TCHAR*)szBuffer) + lstrlen(pMsg->MsgDown.szContent) + 1,
                                         pMsg->MsgDown.szContent);
            SendDlgItemMessage(pParams->hWinMain, IDC_INFO, LB_INSERTSTRING, 0, (LPARAM)szBuffer);        
        }
        return;
    }
}

/*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  接收数据句——在非阻塞下,每次接收到的可能不是一个完整的数据包,甚至可能连数据包头
                部都可能没接收完,所以在每个收到FD_READ消息时,要不断接收数据进来。
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/
void RecvData(SOCKPARAMS* pParams)
{
    int iNeedSize;
    int iRet;

    //如果缓冲区里数据小于数据包头长度,则先接收数据包头部;——即先接收一个完整的头部
    //大于数据包头部,则接收的总长度由数据包头里的cbSize指定;——即接收整个数据包。

    if (pParams->cbRecvBufSize <sizeof(MSGHEAD))
        iNeedSize = sizeof(MSGHEAD);  //如果缓冲区数据小于数据包头长度,先接收数据包头部
    else
    {
        iNeedSize = pParams->szRecvMsg->MsgHead.cbSize;  //否则,接收完整的数据包
        if (iNeedSize<sizeof(MSGHEAD) || iNeedSize > sizeof(MSGSTRUCT))
        {
            pParams->cbRecvBufSize = 0;
            DisConnect(pParams);
            return;
        }
    }

    //总共要接收iNeedSize,己接收的为pParams->cbRecvBufSize,接收剩余的字节。
    if (iNeedSize - pParams->cbRecvBufSize > 0)
    {
        iRet = recv(pParams->sock, (BYTE*)pParams->szRecvMsg + pParams->cbRecvBufSize, 
                   iNeedSize - pParams->cbRecvBufSize,0);
        
        if (SOCKET_ERROR == iRet)
        {
            if (WSAEWOULDBLOCK !=WSAGetLastError())
            {
                DisConnect(pParams);
                return;
            }
        }

        pParams->cbRecvBufSize += iRet;
    }

    //如果整个数据包接收完毕,则进行处理
    if (pParams->cbRecvBufSize >=sizeof(MSGHEAD))
    {
        if (pParams->cbRecvBufSize ==pParams->szRecvMsg->MsgHead.cbSize)
        {
            ProcMessage(pParams);
            pParams->cbRecvBufSize = 0;
        }
    }
}

BOOL CALLBACK DlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    static SOCKPARAMS sockParam;
    MSGSTRUCT* pMsg;
    RECT rect;
    WSADATA wsa;
    BOOL  bEnable;

    switch (message)
    {
    //处理Socket消息
    case WM_SOCKET://wParam为发送消息的套接字句柄
                   //LOWORD(lParam)通知码,HIWORD(lParam)出错代码
        switch (LOWORD(lParam))
        {
        case FD_CONNECT:
            pMsg = sockParam.szSendMsg;
            if (HIWORD(lParam) ==0 )  //连接成功,则登录
            {
                StringCchCopy(pMsg->Login.szUserName, lstrlen(sockParam.szUserName) + 1, sockParam.szUserName);
                StringCchCopy(pMsg->Login.szPassword, lstrlen(sockParam.szPassword) + 1, sockParam.szPassword);
                pMsg->MsgHead.nCmdID = CMD_LOGIN;
                pMsg->MsgHead.cbSize = sizeof(MSGHEAD)+sizeof(MSGLOGIN);
                SendData(&sockParam, pMsg->MsgHead.cbSize);

            }
            else
            {
                MessageBox(sockParam.hWinMain, szErrConnect, szAppName, MB_OK | MB_ICONSTOP);
                DisConnect(&sockParam);
            }

            return TRUE;

        case FD_READ:
            RecvData(&sockParam);
            return TRUE;

        case FD_WRITE:
            SendData(&sockParam, 0); //0表示没有新的数据要发送,直接将缓冲区的未发送的数据发送出去
            EnableWindow(GetDlgItem(sockParam.hWinMain, IDC_TEXT), TRUE);
            EnableWindow(GetDlgItem(sockParam.hWinMain, IDOK), TRUE);
            return TRUE;

        case FD_CLOSE:
            DisConnect(&sockParam);
            return TRUE;
        }
        return TRUE;

    case WM_COMMAND:
        switch (LOWORD(wParam))
        {
        case IDC_SERVER:
        case IDC_USER:
        case IDC_PASS:
            GetDlgItemTextA(hwnd, IDC_SERVER, sockParam.szServer, sizeof(sockParam.szServer));
            GetDlgItemText(hwnd, IDC_USER,   sockParam.szUserName, sizeof(sockParam.szUserName));
            GetDlgItemText(hwnd, IDC_PASS,   sockParam.szPassword, sizeof(sockParam.szPassword));
            bEnable = sockParam.szServer[0] && sockParam.szUserName[0] && sockParam.szPassword[0] && (sockParam.sock==0);
            EnableWindow(GetDlgItem(hwnd, IDC_LOGIN), bEnable);
            return TRUE;

        //登录成功后,输入聊天语句后才能激活“发送”按钮
        case IDC_TEXT: 
            GetDlgItemText(hwnd, IDC_TEXT, sockParam.szText, sizeof(sockParam.szText));
            bEnable = (lstrlen(sockParam.szText) > 0) && sockParam.sock;
            EnableWindow(GetDlgItem(hwnd, IDOK), bEnable);
            return TRUE;

        case IDC_LOGIN:
            Connect(&sockParam);

            return TRUE;

        case IDC_LOGOUT:
            DisConnect(&sockParam);
            return TRUE;

        case IDOK:    
            pMsg = sockParam.szSendMsg;

            StringCchCopy((TCHAR*)(pMsg->MsgUp.szConetent), lstrlen(sockParam.szText)+1, sockParam.szText);
            pMsg->MsgUp.cbSizeConent = sizeof(TCHAR)*(lstrlen(sockParam.szText) + 1);
            pMsg->MsgHead.nCmdID = CMD_MSG_UP;
            pMsg->MsgHead.cbSize = sizeof(MSGHEAD)+sizeof(pMsg->MsgUp.cbSizeConent) + pMsg->MsgUp.cbSizeConent;

            SendData(&sockParam, pMsg->MsgHead.cbSize);

            sockParam.nLastTime = GetTickCount();
            SetDlgItemText(hwnd, IDC_TEXT, NULL);
            SetFocus(GetDlgItem(hwnd, IDC_TEXT));
            return TRUE;

        }
        break;

    case WM_INITDIALOG:
        sockParam.hWinMain = hwnd;
        sockParam.szRecvMsg = malloc(10 * sizeof(MSGSTRUCT));
        sockParam.szSendMsg = malloc(10 * sizeof(MSGSTRUCT));

        GetWindowRect(hwnd, &rect);
        SetWindowPos(hwnd, NULL, (GetSystemMetrics(SM_CXSCREEN) - rect.right + rect.left) / 2,
            (GetSystemMetrics(SM_CYSCREEN) - rect.bottom + rect.top) / 2,
            rect.right - rect.left, rect.bottom - rect.top, SWP_SHOWWINDOW);

        SendDlgItemMessage(hwnd, IDC_SERVER, EM_SETLIMITTEXT, 15, 0);
        SendDlgItemMessage(hwnd, IDC_USER, EM_SETLIMITTEXT, 11, 0);
        SendDlgItemMessage(hwnd, IDC_PASS, EM_SETLIMITTEXT, 11, 0);
        SendDlgItemMessage(hwnd, IDC_TEXT, EM_SETLIMITTEXT, 250, 0);

        SetDlgItemText(hwnd, IDC_SERVER, TEXT("127.0.0.1"));
        SetDlgItemText(hwnd, IDC_USER, TEXT("SantaClaus"));
        SetDlgItemText(hwnd, IDC_PASS, TEXT("123456"));

        WSAStartup(0x0002, &wsa);
    
        return TRUE;

    case WM_CLOSE:
        WSACleanup();
        if (NULL != sockParam.szRecvMsg)
            free(sockParam.szRecvMsg);

        if (NULL != sockParam.szSendMsg)
            free(sockParam.szSendMsg);

        EndDialog(hwnd, 0);
        return TRUE;
    }
    return FALSE;
}

【NetTime程序】网络校对时间程序(需以管理员身份运行)

/*--------------------------------------------------------------------
   NETTIME.C —— Sets System Clock from Internet Sevices
                (c)Charles Petzold,1998
   http://tf.nist.gov/tf-cgi/servers.cgi //Internet时间服务器一览表
--------------------------------------------------------------------*/
#include <windows.h>
#include "resource.h"

#pragma comment(lib,"WS2_32.lib")

#define WM_SOCKET_NOTIFY   WM_USER + 100
#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 EditPrint(HWND hwndEdit, TCHAR* szFormat, ...);
void ChangeSystemTime(HWND hwndEdit, ULONG ulTime);
void FormatUpdateTime(HWND hwndEdit, SYSTEMTIME* pstOld, SYSTEMTIME* pstNew);

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.hbrBackground = NULL;
    wndclass.hCursor = NULL;
    wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wndclass.hInstance = hInstance;
    wndclass.lpszClassName = szAppName;
    wndclass.lpszMenuName = NULL;

    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);
    
    //调整rect,增加大到有标题栏和边框,第3个选项表明没有菜单
    AdjustWindowRect(&rect, WS_CAPTION | WS_BORDER, FALSE);
    SetWindowPos(hwnd, NULL, 0, 0, rect.right - rect.left, 
                       rect.bottom - rect.top,SWP_NOMOVE);

    ShowWindow(hwndModeless, SW_SHOW);
    ShowWindow(hwnd, iCmdShow);
    UpdateWindow(hwnd);

    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.4.101" };
    static TCHAR  szOKLabel[32];
    static HWND hwndButton, hwndEdit;
    static SOCKET sock;
    static SOCKADDR_IN sa;
    WSADATA  WSAdata;
    int iError, iSize;
    unsigned long ulTime;
    WORD  wEvent, wError;

    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:
            DialogBoxParam(hInst, TEXT("Servers"), hwnd, ServerDlg, (LPARAM)szIPAddr);
            return TRUE;

        case IDOK:
            //调用WSAStartup函数并显示WinSock库信息
            if (iError = WSAStartup(MAKEWORD(2, 0), &WSAdata))
            {
                EditPrint(hwndEdit, TEXT("Startup error #%i.
"), iError);
                return TRUE;
            }

            EditPrint(hwndEdit, TEXT("Started up %hs
"), WSAdata.szDescription); //%hs窄字符格式输出
            
            //创建socket对象
            sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
            if (sock == INVALID_SOCKET)
            {
                EditPrint(hwndEdit, TEXT("Socket createion error #%i.
"), 
                       WSAGetLastError());
                WSACleanup(); //卸载WinSock库
                return TRUE;
            }
            EditPrint(hwndEdit, TEXT("Socket %i created.
"), sock);

            //调用设置异步函数
            if (SOCKET_ERROR == WSAAsyncSelect(sock, hwnd, WM_SOCKET_NOTIFY, 
                                                   FD_CONNECT | FD_READ))
            {
                EditPrint(hwndEdit, TEXT("WSAAsyncSelect error #%i.
"),
                    WSAGetLastError());
                closesocket(sock);
                WSACleanup(); //卸载WinSock库
                return TRUE;
            }
            
            //连接到指定的服务器和端口
            sa.sin_family = AF_INET;
            sa.sin_addr.S_un.S_addr = inet_addr(szIPAddr);
            sa.sin_port = htons(IPPORT_TIMESERVER); //IPPORT_TIMESERVER=37,定义在winsock.h文件

            connect(sock, (SOCKADDR*)&sa, sizeof(SOCKADDR));
        
            //connect函数会立即返回,并返回SOCKET_ERROR。即使成功,也会返回该值,因为该函数
            //需要用阻塞方式使用,但这里却使用了非阻塞的方式,只有以下的情况才是真正的错误
            if (WSAEWOULDBLOCK !=(iError = WSAGetLastError()))
            {
                EditPrint(hwndEdit, TEXT("Connect error #%i.
"), iError);
                closesocket(sock);
                WSACleanup();
                return TRUE;
            }
            EditPrint(hwndEdit, TEXT("Connecting to %hs..."), szIPAddr);

            //connect的结果将通过WM_SOCKET_NOTIFY(自定义)消息发送给窗口过程。
            //设置定时器并改变按钮为Cancel
            SetTimer(hwnd, ID_TIMER, 1000, NULL);
            GetWindowText(hwndButton, szOKLabel, sizeof(szOKLabel) / sizeof(TCHAR));
            SetWindowText(hwndButton, TEXT("Cancel"));
            SetWindowLong(hwnd, GWL_ID, IDCANCEL); //将按钮ID改为取消
            return TRUE;

        case IDCANCEL:
            closesocket(sock);
            sock = 0;
            WSACleanup();
            SetWindowText(hwndButton, szOKLabel);
            SetWindowLong(hwndButton, GWL_ID, IDOK);

            KillTimer(hwnd, ID_TIMER);
            EditPrint(hwndEdit, TEXT("
Socket closed.
"));
            return TRUE;

        case IDC_CLOSE:
            if (sock)
                SendMessage(hwnd, WM_COMMAND, IDCANCEL, 0);

            DestroyWindow(GetParent(hwnd));//销毁父窗口,本窗口也会自动被销毁
            return TRUE;
        }
        break;

    case WM_TIMER:
        EditPrint(hwndEdit, TEXT("."));
        return TRUE;

    case WM_SOCKET_NOTIFY:
        wEvent = WSAGETSELECTEVENT(lParam);  //LOWORD
        wError = WSAGETSELECTERROR(lParam);  //HIWORD

        //处理指定的两个异步事件
        switch (wEvent)
        {
        case FD_CONNECT: //connect函数调用的结果
            EditPrint(hwndEdit, TEXT("
"));
            if (wError) //连接失败
            {
                EditPrint(hwndEdit, TEXT("Connect error#%i."), wError);
                SendMessage(hwnd, WM_COMMAND, IDCANCEL, 0);
                return TRUE;
            }
            //连接成功
            EditPrint(hwndEdit, TEXT("Connected to %hs.
"), szIPAddr);
            
            //尝试去接收数据。该调用会产生一个WSAEWOULDBLOCK错误和一个FD_READ事件
            recv(sock, (char*)&ulTime, 4, MSG_PEEK); //最后一个为PEEK,表示只是看看,
                                                     //不会将其从输入缓冲队列中删除。
                                                     //该函数可能至少会从服务器获得
                                                     //部分数据,必须在FD_READ中接收
                                                     //剩余的数据。
            EditPrint(hwndEdit, TEXT("Waiting to receive..."));
            return TRUE;

        case FD_READ:
            KillTimer(hwnd, ID_TIMER);
            EditPrint(hwndEdit, TEXT("
"));

            if (wError)
            {
                EditPrint(hwndEdit, TEXT("FD_READ error#%i."), wError);
                SendMessage(hwnd, WM_COMMAND, IDCANCEL, 0);
                return TRUE;
            }

            //读取服务器的时间,ulTime是从1900.1.1 零时以来的秒数,并且是网络字节顺序
            iSize = recv(sock, (char*)&ulTime, 4, 0); //最后一个参数为0,表示接收完后
                                                      //从接收缓冲区删除队列数据
            ulTime = ntohl(ulTime);
            EditPrint(hwndEdit, TEXT("Received current time of %u seconds ")
                                 TEXT("since Jan.1 1900.
"),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];
    char* pstr;

    char* pContext;

    switch (message)
    {
    case WM_INITDIALOG:
        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:
            GetDlgItemTextA(hwnd, wServer, szLabel, sizeof(szLabel));
            //strok_s分割字符串,将szLabel中的指定的字符用替换以达到分割字符串的目的
            //如“ntp-nist.ldsbc.net (198.60.73.8) LDSBC, Salt Lake City, Utah”当
            //第一次调用strtok_s时,将左括号处的字符替换为,分割成2个字符串,返回值
            //指定第一串的首字母的位置。第2次调用时(注意,参数转入NULL),将右括号
            //替换为,返回值指向这里szLabel被分为三个串,返回值指向第2串字符串的首字母位置
            //即IP地址的首字符。
            strtok_s(szLabel, "(",&pContext); //返回值指向"("之前的字符串,第1次调用转入szLabel
            pstr = strtok_s(NULL, ")", &pContext);//返回值指向")"之前的字符串,即IP地址字符串
                                                  //第2次调用,转入NULL参数
            strcpy_s(szServer,lstrlenA(pstr)+1,pstr);
            EndDialog(hwnd, TRUE);
            return TRUE;

        case IDCANCEL:
            EndDialog(hwnd, FALSE);
            return TRUE;
        }
        break;
    }
    return FALSE;
}

void EditPrint(HWND hwndEdit, TCHAR* szFormat, ...)
{
    TCHAR szBuffer[1024];
    va_list  pArtList;

    va_start(pArtList, szFormat);
    wvsprintf(szBuffer, szFormat, pArtList);
    va_end(pArtList);

    SendMessage(hwndEdit, EM_SETSEL, (WPARAM)-1, (LPARAM)-1); //wParam-1为取消选择,
    SendMessage(hwndEdit, EM_REPLACESEL, FALSE, (LPARAM)szBuffer); //在编辑框末尾加入文本
    SendMessage(hwndEdit, EM_SCROLLCARET, 0, 0);//将插入光标滚动到可视范围
}

//在Vista、Win7及以上版本的系统,更改系统时间需要管理员身份运行
//才能成功。
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;

    SystemTimeToFileTime(&stNew, &ftNew);
    li = *(LARGE_INTEGER*)&ftNew;
    li.QuadPart += (LONGLONG)10000000 * ulTime; //1纳秒等于10亿分之一秒在,而ftNew的单位是100纳秒
    ftNew = *(FILETIME*)&li;
    FileTimeToSystemTime(&ftNew, &stNew);

    if (SetSystemTime(&stNew))
    {
        GetLocalTime(&stNew);
        FormatUpdateTime(hwndEdit, &stOld, &stNew);
    }
    else
        EditPrint(hwndEdit, TEXT("Could Not set new data and time."));
}

//GetDateFormat函数说明:
//作用:用来针对指定的“当地”格式,对一个系统日期进行格式化
//参数:
//Locale long:用来决定格式的地方ID
void FormatUpdateTime(HWND hwndEdit, SYSTEMTIME* pstOld, SYSTEMTIME* pstNew)
{
    TCHAR szDataOld[64], szTimeOld[64], szDataNew[64], szTimeNew[64];

    //pstOld格式化成“当地”格式和短日期格式并存放在szDataOld缓冲区中
    GetDateFormat(LOCALE_USER_DEFAULT, LOCALE_NOUSEROVERRIDE | DATE_SHORTDATE, 
                      pstOld,NULL,szDataOld,sizeof(szDataOld));//系统默认格式和短日期(如2015/7/3
    GetTimeFormat(LOCALE_USER_DEFAULT, LOCALE_NOUSEROVERRIDE | TIME_NOTIMEMARKER | TIME_FORCE24HOURFORMAT,
                    pstOld,NULL,szTimeOld,sizeof(szTimeOld)); //24小时制

    GetDateFormat(LOCALE_USER_DEFAULT, LOCALE_NOUSEROVERRIDE | DATE_SHORTDATE, //系统默认格式和短日期
        pstNew, NULL, szDataNew, sizeof(szDataNew));
    GetTimeFormat(LOCALE_USER_DEFAULT, LOCALE_NOUSEROVERRIDE | TIME_NOTIMEMARKER | TIME_FORCE24HOURFORMAT,//  
        pstNew, NULL, szTimeNew, sizeof(szTimeNew)); //24小时制

    EditPrint(hwndEdit, 
              TEXT("System data and time successfully changed ")
              TEXT("from
	%s, %s.%03i   to
	%s, %s.%03i."),
              szDataOld,szTimeOld,pstOld->wMilliseconds,
              szDataNew,szTimeNew,pstNew->wMilliseconds);
}

//resource.h

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ 生成的包含文件。
// 供 NetTime.rc 使用
//
#define IDC_SERVER1                     1001
#define IDC_SERVER2                     1002
#define IDC_SERVER3                     1003
#define IDC_SERVER4                     1004
#define IDC_SERVER5                     1005
#define IDC_SERVER6                     1006
#define IDC_SERVER7                     1007
#define IDC_SERVER8                     1008
#define IDC_SERVER9                     1009
#define IDC_SERVER10                    1010

#define IDC_CLOSE                       1011
#define IDC_SERVER                      1012
#define IDC_TEXTOUT                     1013

// Next default values for new objects
// 
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        103
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1005
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif

//NetTime.rc

// Microsoft Visual C++ generated resource script.
//
#include "resource.h"

#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"

/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS

/////////////////////////////////////////////////////////////////////////////
// 中文(简体,中国) resources

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED

#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//

1 TEXTINCLUDE 
BEGIN
    "resource.h"
END

2 TEXTINCLUDE 
BEGIN
    "#include ""winres.h""
"
    ""
END

3 TEXTINCLUDE 
BEGIN
    "
"
    ""
END

#endif    // APSTUDIO_INVOKED


/////////////////////////////////////////////////////////////////////////////
//
// Dialog
//

SERVERS DIALOGEX 0, 0, 271, 176
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "NIST Time Service Servers"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
    DEFPUSHBUTTON   "OK",IDOK,70,148,50,14
    PUSHBUTTON      "Cancel",IDCANCEL,143,148,50,14
    CONTROL         "time-a.timefreq.bldrdoc.gov (132.163.4.101) NIST, Boulder, Colorado",IDC_SERVER1,
                    "Button",BS_AUTORADIOBUTTON,15,12,241,10
    CONTROL         "time-b.timefreq.bldrdoc.gov (132.163.4.102) NIST, Boulder, Colorado",IDC_SERVER2,
                    "Button",BS_AUTORADIOBUTTON,15,25,241,10
    CONTROL         "time-c.timefreq.bldrdoc.gov (132.163.4.103) NIST, Boulder, Colorado",IDC_SERVER3,
                    "Button",BS_AUTORADIOBUTTON,15,38,220,10
    CONTROL         "utcnist.colorado.edu (128.138.140.44) University of Colorado, Boulder",IDC_SERVER4,
                    "Button",BS_AUTORADIOBUTTON,15,51,243,10
    CONTROL         "nist1-pa.ustiming.org (206.246.122.250) Hatfield, PA",IDC_SERVER5,
                    "Button",BS_AUTORADIOBUTTON,15,64,188,10
    CONTROL         "ntp-nist.ldsbc.net (198.60.73.8) LDSBC, Salt Lake City, Utah",IDC_SERVER6,
                    "Button",BS_AUTORADIOBUTTON,15,77,209,10
    CONTROL         "nist1-lv.ustiming.org (64.250.229.100) Las Vegas, Nevada",IDC_SERVER7,
                    "Button",BS_AUTORADIOBUTTON,15,90,209,10
    CONTROL         "time-nw.nist.gov (131.107.13.100) Microsoft, Redmond, Washington",IDC_SERVER8,
                    "Button",BS_AUTORADIOBUTTON,15,103,238,10
    CONTROL         "nist-time-server.eoni.com (216.228.192.69) La Grande, Oregon",IDC_SERVER9,
                    "Button",BS_AUTORADIOBUTTON,15,116,215,10
    CONTROL         "wwv.nist.gov (24.56.178.140) WWV, Fort Collins, Colorado",IDC_SERVER10,
                    "Button",BS_AUTORADIOBUTTON,15,129,236,10
END

NETTIME DIALOGEX 0, 0, 291, 176
STYLE DS_SETFONT | WS_CHILD
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
    DEFPUSHBUTTON   "Set Correct Time",IDOK,128,148,74,14
    PUSHBUTTON      "Close",IDC_CLOSE,215,148,50,14
    PUSHBUTTON      "Select Server...",IDC_SERVER,18,148,97,14
    EDITTEXT        IDC_TEXTOUT,16,14,260,126,ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY | WS_VSCROLL | NOT WS_TABSTOP
END


/////////////////////////////////////////////////////////////////////////////
//
// DESIGNINFO
//

#ifdef APSTUDIO_INVOKED
GUIDELINES DESIGNINFO
BEGIN
    "SERVERS", DIALOG
    BEGIN
        LEFTMARGIN, 7
        RIGHTMARGIN, 264
        TOPMARGIN, 7
        BOTTOMMARGIN, 169
    END

    "NETTIME", DIALOG
    BEGIN
        LEFTMARGIN, 7
        RIGHTMARGIN, 284
        TOPMARGIN, 7
        BOTTOMMARGIN, 169
    END
END
#endif    // APSTUDIO_INVOKED

#endif    // 中文(简体,中国) resources
/////////////////////////////////////////////////////////////////////////////



#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//


/////////////////////////////////////////////////////////////////////////////
#endif    // not APSTUDIO_INVOKED
原文地址:https://www.cnblogs.com/5iedu/p/4715315.html