|
Winsock I/O模型之WSAAsyncSelect |
Winsock I/O模型之WSAAsyncSelect
Winsock提供了一个很有用的异步I/O模型,利用这个模型,应用程序可以在一个套接字上接收以Windows消息为基础的网络事件通知。这个模型最开始出现在Winsock 1.1版本中,是为了帮助开发者面向一些早期的16位Windows平台而设计的。但是现在的应用程序仍然可以从这种模型中得到好处,就连MFC中的CSocket类也采纳了这种模型。
由于该模型是基于Windows消息机制的,所以要想使用这种模型必须要Create一个窗口,这个窗口将会被用来接收消息。接下来建立套接字,然后调用WSAAsyncSelect函数,打开窗口消息通知,函数原型如下:
int WSAAsyncSelect(SOCKET s, HWND hWnd, unsigned int uMsg, long lEvent);
其中s就是我们想要的那个套接字;hWnd是接收消息通知那个窗口句柄;wMsg参数指定在发生网络事件时要接受的消息,通常设成比WM_USER大的一个值,以避免消息冲突;lEvent指定了一个位掩码,对应一系列网络事件的组合,见下表:
Event
含义
FD_READ
程序想要接收有关是否可读的通知,以便读入数据
FD_WRITE
程序想要接收有关是否可写的通知,以便写入数据
FD_OOB
程序想要接收是否有OOB数据到达的通知
FD_ACCEPT
程序想要接收与进入连接有关的通知
FD_CONNECT
程序想要接收与一次连接或多点接入有关的通知
FD_CLOSE
程序想要接收与套接字关闭有关的通知
FD_QOS
程序想要接收套接字“服务质量(QoS)”发生变化的通知
FD_GROUP_QOS
暂时没用,属于保留事件
FD_ROUTING_INTERFACE_CHANGE
程序想要接收有关到指定地址的路由接口发生变化的通知
FD_ADDRESS_LIST_CHANGE
程序想要接收本地地址变化的通知
当程序在一个套接字上调用WSAAsyncSelect成功后,这个程序就会在与hWnd窗口句柄对应的窗口例程中以Windows消息的形式接收网络事件通知。窗口例程通常定义成这个样子:
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)其中wParam参数指定在其上面发生了一个网络事件的套接字,如果定义了多个套接字,这个参数就显得很重要了。lParam参数则包含了两方面的重要信息,它的低位字指定了已经发生的网络事件,而高位字包含了可能出现的错误代码。
简单的来说,这个模型的具体使用流程就是:当网络消息抵达一个窗口例程后,程序要先检测lParam的高位字节,从而判断是否在套接字上面发生了网络错误。现成的宏已经有在这里了 --> WSAGETSELECTERROR,可以用它返回高字节包含的错误信息,如果没有发现任何的错误,接下来就是确定究竟是什么类型的网络事件触发了这条Windows消息,这个操作也有现成的宏 --> WSAGETSELECTEVENT
下面就是源代码,其中部分很基本的代码我就省略掉了,编译平台为Win2000 Server with SP2 + VC6.0 with SP5
#include <windows.h>#include <winsock2.h>
#define PORT 5150#define DATA_BUFSIZE 8192
typedef struct _SOCKET_INFORMATION { BOOL RecvPosted; CHAR Buffer[DATA_BUFSIZE]; WSABUF DataBuf; SOCKET Socket; DWORD BytesSEND; DWORD BytesRECV; _SOCKET_INFORMATION *Next;} SOCKET_INFORMATION, * LPSOCKET_INFORMATION;
#define WM_SOCKET (WM_USER + 1)
void CreateSocketInformation(SOCKET s, HWND);LPSOCKET_INFORMATION GetSocketInformation(SOCKET s);void FreeSocketInformation(SOCKET s);
LPSOCKET_INFORMATION SocketInfoList;
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow){ DWORD Ret; SOCKET Listen; SOCKADDR_IN InternetAddr; WSADATA wsaData; static TCHAR szAppName[] = TEXT ("HelloWin") ; HWND hwnd ; MSG msg ; WNDCLASS wndclass ;
// Prepare echo server wndclass.style = CS_HREDRAW | CS_VREDRAW ; ... ... RegisterClass (&wndclass); hwnd = CreateWindow (...) ; // creation parameters ShowWindow (hwnd, nCmdShow) ; UpdateWindow (hwnd) ; if ((Ret = WSAStartup(0x0202, &wsaData)) != 0) { MessageBox(hwnd, TEXT("Start socket failed"), TEXT("error"), MB_OK); ExitProcess(1); } if ((Listen = socket (PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) { MessageBox(hwnd, TEXT("socket() failed"), TEXT("error"), MB_OK); ExitProcess(1); } WSAAsyncSelect(Listen, hwnd, WM_SOCKET, FD_ACCEPT|FD_CLOSE); InternetAddr.sin_family = AF_INET; InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY); InternetAddr.sin_port = htons(PORT);
if (bind(Listen, (PSOCKADDR) &InternetAddr, sizeof(InternetAddr)) == SOCKET_ERROR) { MessageBox(hwnd, TEXT("bind() failed"), TEXT("error"), MB_OK); ExitProcess(1); } if (listen(Listen, 5)) { MessageBox(hwnd, TEXT("listen() failed"), TEXT("error"), MB_OK); ExitProcess(1); } // Translate and dispatch window messages for the application thread while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ;}
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){ HDC hdc ; PAINTSTRUCT ps ; RECT rect ; SOCKET Accept; LPSOCKET_INFORMATION SocketInfo; DWORD RecvBytes, SendBytes; DWORD Flags; switch (message) { case WM_CREATE: return 0 ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; GetClientRect (hwnd, &rect) ; DrawText (hdc, TEXT ("Server Started!"), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER) ; EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: PostQuitMessage (0) ; return 0 ;
case WM_SOCKET: if (WSAGETSELECTERROR(lParam)) { MessageBox(...); FreeSocketInformation(wParam); } else { switch(WSAGETSELECTEVENT(lParam)) { case FD_ACCEPT: if ((Accept = accept(wParam, NULL, NULL)) == INVALID_SOCKET) { MessageBox(...); break; } // Create a socket information structure to associate with the // socket for processing I/O. CreateSocketInformation(Accept, hwnd); WSAAsyncSelect(Accept, hwnd, WM_SOCKET, FD_READ|FD_WRITE|FD_CLOSE); break;
case FD_READ: SocketInfo = GetSocketInformation(wParam); // Read data only if the receive buffer is empty. if (SocketInfo->BytesRECV != 0) { SocketInfo->RecvPosted = TRUE; return 0; } else { SocketInfo->DataBuf.buf = SocketInfo->Buffer; SocketInfo->DataBuf.len = DATA_BUFSIZE; Flags = 0; if (WSARecv(SocketInfo->Socket, &(SocketInfo->DataBuf), 1, &RecvBytes, &Flags, NULL, NULL) == SOCKET_ERROR) { if (WSAGetLastError() != WSAEWOULDBLOCK) { MessageBox(...); FreeSocketInformation(wParam); return 0; } } else // No error so update the byte count SocketInfo->BytesRECV = RecvBytes; } // DO NOT BREAK HERE SINCE WE GOT A SUCCESSFUL RECV. // Go ahead and begin writing data to the client.
case FD_WRITE: SocketInfo = GetSocketInformation(wParam); if (SocketInfo->BytesRECV > SocketInfo->BytesSEND) { SocketInfo->DataBuf.buf = SocketInfo->Buffer + SocketInfo->BytesSEND; SocketInfo->DataBuf.len = SocketInfo->BytesRECV - SocketInfo->BytesSEND; if (WSASend(SocketInfo->Socket, &(SocketInfo->DataBuf), 1, &SendBytes, 0,NULL, NULL) == SOCKET_ERROR) { if (WSAGetLastError() != WSAEWOULDBLOCK) { MessageBox(...); FreeSocketInformation(wParam); return 0; } } else // No error so update the byte count SocketInfo->BytesSEND += SendBytes; }
if (SocketInfo->BytesSEND == SocketInfo->BytesRECV) { SocketInfo->BytesSEND = 0; SocketInfo->BytesRECV = 0; // If a RECV occurred during our SENDs then we need to post // an FD_READ notification on the socket.
if (SocketInfo->RecvPosted == TRUE) { SocketInfo->RecvPosted = FALSE; PostMessage(hwnd, WM_SOCKET, wParam, FD_READ); } } if(SocketInfo->DataBuf.buf != NULL) MessageBox(hwnd, SocketInfo->DataBuf.buf, TEXT("Received"), MB_OK); break;
case FD_CLOSE: FreeSocketInformation(wParam); break; } } return 0; } return DefWindowProc(hwnd, message, wParam, lParam);}
void CreateSocketInformation(SOCKET s, HWND hwnd){ LPSOCKET_INFORMATION SI; if ((SI = (LPSOCKET_INFORMATION) GlobalAlloc(GPTR, sizeof(SOCKET_INFORMATION))) == NULL) { MessageBox(...); return; } // Prepare SocketInfo structure for use. SI->Socket = s; SI->RecvPosted = FALSE; SI->BytesSEND = 0; SI->BytesRECV = 0; SI->Next = SocketInfoList; SocketInfoList = SI;}
LPSOCKET_INFORMATION GetSocketInformation(SOCKET s){ SOCKET_INFORMATION *SI = SocketInfoList; while(SI) { if (SI->Socket == s) return SI; SI = SI->Next; } return NULL;}
void FreeSocketInformation(SOCKET s){ SOCKET_INFORMATION *SI = SocketInfoList; SOCKET_INFORMATION *PrevSI = NULL; while(SI) { if (SI->Socket == s) { if (PrevSI) PrevSI->Next = SI->Next; else SocketInfoList = SI->Next; closesocket(SI->Socket); GlobalFree(SI); return; } PrevSI = SI; SI = SI->Next; }}
服务器就这样建好了,只需要一个客户机就可以通信了,具体的代码我就不再贴了,各位可以自己设计客户机,或者去下载区下载源程序。最后向大家推荐《Windows网络编程技术》,真的不错。
本文中部分内容翻译自MSDN,客户机程序使用的是《Windows网络编程技术》中的例子 | |
|
|
|
|