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)非阻塞模式下网络程序常见的结构

第23章 尝试互联网(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程序】网络校对时间程序(需以管理员身份运行)

第23章 尝试互联网(3)_非阻塞_02



/*--------------------------------------------------------------------
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