zoukankan      html  css  js  c++  java
  • 第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
  • 相关阅读:
    WordPress插入图片无法居中的解决方法
    wordpress文章显示同一分类下的上一篇下一篇
    git4
    git3
    git2
    git1
    百度地图vue版本标记点拖拽事件传参问题
    postcss-plugin-px2rem的使用
    保留小数位toFixed()方法的怪异表现
    大公司是怎样开发和部署前端代码(转张云龙大神的回答)
  • 原文地址:https://www.cnblogs.com/5iedu/p/4715315.html
Copyright © 2011-2022 走看看