#include <winsock.h>#include <tchar.h>#define PORT 5000#define MSGSIZE 1024#define WM_SOCKET WM_USER+0 #pragma comment(lib, "ws2_32.lib")LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow){static TCHAR szAppName[] = _T("AsyncSelect Model");HWND hwnd ;MSG msg ;WNDCLASS wndclass ;wndclass.style = CS_HREDRAW | CS_VREDRAW ;wndclass.lpfnWndProc = WndProc ;wndclass.cbClsExtra = 0 ;wndclass.cbWndExtra = 0 ;wndclass.hInstance = hInstance ;wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;wndclass.lpszMenuName = NULL ;wndclass.lpszClassName = szAppName ;if (!RegisterClass(&wndclass)){MessageBox (NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ;return 0 ;}hwnd = CreateWindow (szAppName, // window class nameTEXT ("AsyncSelect Model"), // window captionWS_OVERLAPPEDWINDOW, // window styleCW_USEDEFAULT, // initial x positionCW_USEDEFAULT, // initial y positionCW_USEDEFAULT, // initial x sizeCW_USEDEFAULT, // initial y sizeNULL, // parent window handleNULL, // window menu handlehInstance, // program instance handleNULL) ; // creation parametersShowWindow(hwnd, iCmdShow);UpdateWindow(hwnd);while (GetMessage(&msg, NULL, 0, 0)){TranslateMessage(&msg) ;DispatchMessage(&msg) ;}return msg.wParam;}LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){WSADATA wsd;static SOCKET sListen;SOCKET sClient;SOCKADDR_IN local, client;int ret, iAddrSize = sizeof(client);char szMessage[MSGSIZE];switch (message){case WM_CREATE://初始化Socket库WSAStartup(0x0202, &wsd);// 创建监听的socketsListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);// 绑定socketlocal.sin_addr.S_un.S_addr = inet_addr("192.168.1.100");local.sin_family = AF_INET;local.sin_port = htons(PORT); //指定监听端口bind(sListen, (struct sockaddr *)&local, sizeof(local));// 监听listen(sListen, 3);// 注册监听socket FD_ACCEPT事件,当 监听socket 有客户连接时,会发送窗口消息WM_SOCKET给本窗口WSAAsyncSelect(sListen, hwnd, WM_SOCKET, FD_ACCEPT);return 0;case WM_DESTROY:closesocket(sListen);WSACleanup();PostQuitMessage(0);return 0;case WM_SOCKET:if (WSAGETSELECTERROR(lParam)){closesocket(wParam);break;}switch (WSAGETSELECTEVENT(lParam)) //分析WM_SOCKET的消息类型{case FD_ACCEPT:// 接受一个客户连接sClient = accept(wParam, (struct sockaddr *)&client, &iAddrSize);MessageBox(hwnd,"客户接入事件",NULL,NULL);// 注册客户socket FD_READ 和 FD_CLOSE 事件WSAAsyncSelect(sClient, hwnd, WM_SOCKET, FD_READ | FD_CLOSE);break;case FD_READ:ret = recv(wParam, szMessage, MSGSIZE, 0);MessageBox(hwnd,"读取事件",NULL,NULL);if (ret == 0 || ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET){closesocket(wParam);}else{szMessage[ret] = '\0';send(wParam, szMessage, strlen(szMessage), 0);}break;case FD_CLOSE:MessageBox(hwnd,"关闭事件",NULL,NULL);closesocket(wParam); break;default: //默认消息,也可以认为socket断开.closesocket(wParam); break;}return 0;}return DefWindowProc(hwnd, message, wParam, lParam);}
在我看来,WSAAsyncSelect是最简单的一种Winsock I/O模型(之所以说它简单是因为一个主线程就搞定了)。使用Raw Windows API写过窗口类应用程序的人应该都能看得懂。这里,我们需要做的仅仅是:1.在WM_CREATE消息处理函数中,初始化Windows Socket library,创建监听套接字,绑定,监听,并且调用WSAAsyncSelect函数表示我们关心在监听套接字上发生的FD_ACCEPT事件;2.自定义一个消息WM_SOCKET,一旦在我们所关心的套接字(监听套接字和客户端套接字)上发生了某个事件,系统就会调用WndProc并且message参数被设置为WM_SOCKET;3.在WM_SOCKET的消息处理函数中,分别对FD_ACCEPT、FD_READ和FD_CLOSE事件进行处理;4.在窗口销毁消息(WM_DESTROY)的处理函数中,我们关闭监听套接字,清除Windows Socket library下面这张用于WSAAsyncSelect函数的网络事件类型表可以让你对各个网络事件有更清楚的认识:表1FD_READ 应用程序想要接收有关是否可读的通知,以便读入数据 FD_WRITE 应用程序想要接收有关是否可写的通知,以便写入数据 FD_OOB 应用程序想接收是否有带外(OOB)数据抵达的通知 FD_ACCEPT 应用程序想接收与进入连接有关的通知 FD_CONNECT 应用程序想接收与一次连接或者多点join操作完成的通知 FD_CLOSE 应用程序想接收与套接字关闭有关的通知 FD_QOS 应用程序想接收套接字“服务质量”(QoS)发生更改的通知 FD_GROUP_QOS 应用程序想接收套接字组“服务质量”发生更改的通知(现在没什么用处,为未来套接字组的使用保留) FD_ROUTING_INTERFACE_CHANGE 应用程序想接收在指定的方向上,与路由接口发生变化的通知 FD_ADDRESS_LIST_CHANGE 应用程序想接收针对套接字的协议家族,本地地址列表发生变化的通知