第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.\r\n"), iError);
                return TRUE;
            }

            EditPrint(hwndEdit, TEXT("Started up %hs\r\n"), WSAdata.szDescription); //%hs窄字符格式输出

            //创建socket对象
            sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
            if (sock == INVALID_SOCKET)
            {
                EditPrint(hwndEdit, TEXT("Socket createion error #%i.\r\n"),
                       WSAGetLastError());
                WSACleanup(); //卸载WinSock库
                return TRUE;
            }
            EditPrint(hwndEdit, TEXT("Socket %i created.\r\n"), sock);

            //调用设置异步函数
            if (SOCKET_ERROR == WSAAsyncSelect(sock, hwnd, WM_SOCKET_NOTIFY,
                                                   FD_CONNECT | FD_READ))
            {
                EditPrint(hwndEdit, TEXT("WSAAsyncSelect error #%i.\r\n"),
                    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.\r\n"), 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("\r\nSocket closed.\r\n"));
            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("\r\n"));
            if (wError) //连接失败
            {
                EditPrint(hwndEdit, TEXT("Connect error#%i."), wError);
                SendMessage(hwnd, WM_COMMAND, IDCANCEL, 0);
                return TRUE;
            }
            //连接成功
            EditPrint(hwndEdit, TEXT("Connected to %hs.\r\n"), 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("\r\n"));

            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.\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];
    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中的指定的字符用\0替换以达到分割字符串的目的
            //如“ntp-nist.ldsbc.net (198.60.73.8) LDSBC, Salt Lake City, Utah”当
            //第一次调用strtok_s时,将左括号处的字符替换为\0,分割成2个字符串,返回值
            //指定第一串的首字母的位置。第2次调用时(注意,参数转入NULL),将右括号
            //替换为\0,返回值指向这里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\r\n\t%s, %s.%03i   to\r\n\t%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\0"
END

2 TEXTINCLUDE
BEGIN
    "#include ""winres.h""\r\n"
    "\0"
END

3 TEXTINCLUDE
BEGIN
    "\r\n"
    "\0"
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
时间: 2024-10-14 04:58:08

第23章 尝试互联网(3)的相关文章

第23章 尝试互联网(1)

23.1 Windows Socket接口简介 (1)TCP/IP模型 ①TCP/IP的核心协议运行于传输层和Internet层,主要包括TCP.UDP和IP协议,而TCP协议和UDP协议是以IP协议为基础而封装的.这两种协议提供了不同方式的数据通信服务. ②IP协议比喻为道路,则下一层的网络访问层上的协议相当于不同的铺路材料,上面的TCP和UPD协议相当于路上跑的不同类型的车辆,再上层应用层的协议相当于车上的丰富多彩的货物.他们都是以TCP.UDP为载体的. (2)WinSock动态库 ①早期

第23章 尝试互联网(2)

23.3 TCP应用程序设计 23.3.1 通信协议的工作线程的设计——阻塞模式 (1)设计TCP链路的通信协议 ①数据包的设计:数据包头和数据包体(可参考代码中的消息定义部分)——TLV(Type-Length-Value) 组成 说明 数据包头 包含命令代码字段和整个数据包大小的字段(这个字段长度是固定的),即使通信双方己约定好各种命令数据包的长度,可以直接从命令代码中间接地判断出该数据包的长度,但仍建议设计该结构头时,保留数据包长度这个字段. 命令代码如:登录命令.消息上传.下载命令.退出

第23章 尝试互联网(4)【全书完】

23.4 WinInet和FTP (1)WinInet接口(含HTTP.FTP)及FTP函数层次关系 (2)Ftp函数介绍 ①InternetOpen——初始化,它告诉 Internet DLL 初始化内部数据结构并准备接收应用程序之后的其他调用. 参数 含义 LPCTSTR lpszAgent 调用WinInet函数的应用程序名字,在HTTP协议中作为用户代理项 DWORD   dwAccessType 访问要求类型: INTERNET_OPEN_TYPE_DIRECT:解析所有本地主机,使用

Spring Framework Reference Documentation 3.2.8.RELEASE 第23章中文翻译

23. JMS (Java Message Service) [中文翻译 by [email protected]] 23.1 介绍 Spring提供了一个JSM集成框架,简化了JMS API的使用.这点很像Spring对JDBC的集成. JMS大致提供生产消息和消费消息两类功能.JmsTemplate类用来生产消息和同步接收消息[译注:接收消息也就是消费消息].为了异步接收消息(异步接收消息类似于JavaEE的消息驱动Bean(Message-Driven Bean,MDB),Spring提供

4.26日第14次作业,23章项目整体绩效评估,24-32章信息安全相关知识

一.23章:项目整体绩效评估 1.三E审计是什么的合称?(记)P524 答:三E审计是经济审计.效率审计和效果审计的合称,因为三者的第一个英文字母均为E,顾称为三E审计. 2.霍尔三维结构是从哪三个方面考察系统工程的工作过程的?P527-528 答:霍尔三维结构是霍尔(A Hall)提出的关于系统方法论的结构,它从逻辑.时间.知识三方面考察系统工程的工作过程. 3.投资回收期的公式?(记,并理解)P533答:投资回收期的公式:(累计净现金流量出现正值的年份-1) + (上年累计净现金流量值的绝对

JavaScript高级程序设计(第三版)学习笔记20、21、23章

第20章,JSON JSON(JavaScript Object Notation,JavaScript对象表示法),是JavaScript的一个严格的子集. JSON可表示一下三种类型值: 简单值:字符串,数值,布尔值,null,不支持js特殊值:undefined 对象:一组无序的键值对 数组:一组有序的值的列表 不支持变量,函数或对象实例 注:JSON的字符串必须使用双引号,这是与JavaScript字符串最大的区别 对象 { "name":"Nicholas"

第23章 CSS边框图片效果

本章学习日后开发使用参考一下内容 https://www.qianduan.net/css3border-image-bian-kuang-tu-xiang-xiang-jie/ 或W3C 或者百度 未排版的PDF转WORD(不想排版了) 第 23章 CSS3边框图片效果学习要点:1.属性初探2.属性解释3.简写和版本 本章主要探讨 HTML5中 CSS3中边框图片背景的效果,通过这个新属性让边框更加的丰富多彩.一.属性解释CSS3提供了一个新的属性集合,用这几个属性可以嵌入图片形式的边框.这样

Lua_第23章 C API 纵览

第23章 C  API 纵览 Lua是一个嵌入式的语言,意味着 Lua 不仅可以是一个独立运行的程序包也可以是一个用来嵌入其他应用的程序库.你可能觉得奇怪:如果 Lua 不只是独立的程序,为什么到目前为止贯穿整本书我们都是在使用 Lua 独立程序呢? 这个问题的答案在于 Lua 解释器(可执行的 lua).Lua解释器是一个使用 Lua 标准库实现的独立的解释器,它是一 个很小的应用(总共不超过500 行的代码).解释器负责程序和使用者的接口:从使用者那里获取文件或者字符串,并传给 Lua 标准

第23章、OnFocuChangeListener焦点事件(从零开始学Android)

在Android App应用中,OnFocuChangeListener焦点事件是必不可少的,我们在上一章的基础上来学习一下如何实现. 基本知识点:OnFocuChangeListener事件 一.界面 打开“res/layout/activity_main.xml”文件. 1.分别从工具栏向activity拖出2个编辑框EditText.控件来自Form Widgets. 2.打开activity_main.xml文件. [html] view plaincopy <LinearLayout