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