漳州网站建设哪家最正规,注销备案号 网站,公司oa办公平台,端游网络游戏排行榜2023简介
WSAAsyncSelect 模型也是 WinSock 中常见的异步 I/O 模型。
使用这个模型#xff0c;网络应用程序通过接收以 Windows 消息为基础的网络事件通知来处理网络请求。
这篇文章我们就来看看如何使用 WSAAsyncSelect api 来实现一个简单的 TCP 服务器.
API 基础
要使用 W…简介
WSAAsyncSelect 模型也是 WinSock 中常见的异步 I/O 模型。
使用这个模型网络应用程序通过接收以 Windows 消息为基础的网络事件通知来处理网络请求。
这篇文章我们就来看看如何使用 WSAAsyncSelect api 来实现一个简单的 TCP 服务器.
API 基础
要使用 WSAAsyncSelect 模型我们必须创建一个窗口 再为该窗口对象提供一个窗口历程WinProc). 通过适当的配置之后当有网络请求到来的时候windows 会将网络消息投递到我们所创建的窗口对象上然后我们通过对应的窗口例程来处理该请求.
WinProc
WindowProc 回调函数用来处理 Windows 系统投递到特定窗口的消息。
它的方法签名如下
LRESULT CALLBACK WindowProc(_In_ HWND hwnd,_In_ UINT uMsg,_In_ WPARAM wParam,_In_ LPARAM lParam
);hwnd当前窗口消息关联的窗口句柄uMsg消息值wParam 额外的消息信息。 具体含义依赖于 uMsg 的值lParam额外的消息信息。 具体含义依赖于 uMsg 的值
RegisterClass
RegisterClass 用来注册一个窗口类型以便在后续的 CreateWindow 或 CreateWindowEx 中使用.
ATOM RegisterClassA(const WNDCLASSA *lpWndClass
);这里不详细介绍该函数的用法参考 实例 章节。 值得注意的是我们的窗口例程WinProc 函数便需要设置到 lpWndClass 对象上. 同时非常重要的是这个类型上还需要包含我们需要注册的窗口类型的名称.
CreateWindowEx
CreateWindowEx 用来创建一个窗口对象.
HWND CreateWindowExA(DWORD dwExStyle,LPCSTR lpClassName,LPCSTR lpWindowName,DWORD dwStyle,int X,int Y,int nWidth,int nHeight,HWND hWndParent,HMENU hMenu,HINSTANCE hInstance,LPVOID lpParam
);在我们的程序中我们仅仅需要一个简单的窗口对象因此在我们的实例中绝大部分参数都是用默认值。 这里也不详细展开用法参考 实例 章节。
WSAAsyncSelect
WSAAsyncSelect 用于将窗口和 SOCKET 对象绑定起来可以指定关心的 SOCKET 事件.
int WSAAsyncSelect(SOCKET s,HWND hWnd,u_int wMsg,long lEvent
);wMsg 指定一个消息值当对应的 SOCKET 上有 SOCKET 事件发生的时候窗口例程会被调用这个消息值会被传回来给我们。主要用于区别系统的窗口事件和我们自定义的事件.
GetMessage
GetMessage 从当前线程的消息队列中获取消息。
BOOL GetMessage(LPMSG lpMsg,HWND hWnd,UINT wMsgFilterMin,UINT wMsgFilterMax
);lpMsg 是一个 MSG 结构体用来接收消息信息hWnd指定想要获取窗口信息的窗口句柄wMsgFilterMin略wMsgFilterMax略
TranslateMessage
TranslateMessage 用于将 virtual-key message 转化为 character message. character mesage。 character message 可以再使用 DispatchMessage 将消息分发到窗口例程WinProc 函数。
BOOL TranslateMessage(const MSG *lpMsg
);DispatchMessage
DispatchMessage 用于将窗口消息分发到窗口例程WinProc 函数。
LRESULT DispatchMessage(const MSG *lpMsg
);实现思路
创建一个窗口对象指定对应窗口的 WinProc 函数创建 SOCKET 对象作为监听的 SOCKET使用 WSAAsyncSelect 函数将窗口与 SOCKET 关联起来. 同时指定SOCKET消息的消息值和 关心的 SOCKET 事件调用 listen开始接收客户端连接使用 GetMessage 函数来从消息队列中获取可用的消息获取到消息后使用TranslateMessage 处理消息然后调用 DispatchMessage 来分发 SOCKET 消息到我们步骤1中指定的窗口 WinProc 函数中。循环 5-6 步骤再 WinProc 函数中使用 WSAGETSELECTEVENT 来判断具体的 SOCKET 消息并进行处理如果有新的SOCKET连接到来接收它并再次使用 WSAAsyncSelect 将该客户端 SOCKET 与我们步骤1 中创建的窗口关联起来并指定关心的 SOCKET 事件
实例
这里我们通过一个实例来看看如何实现
#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS#include winsock2.h
#include windows.h
#include stdio.h
#include conio.h#pragma comment(lib,ws2_32.lib)#define PORT 8080
#define DATA_BUFSIZE 8192typedef struct _SOCKET_CONTEXT {BOOL RecvPosted;CHAR Buffer[DATA_BUFSIZE];WSABUF DataBuf;SOCKET Socket;DWORD BytesSEND;DWORD BytesRECV;struct _SOCKET_CONTEXT *Next;
} SOCKET_CONTEXT, *LPSOCKET_CONTEXT;// 我们使用 WM_SOCKET 作为 SOCKET 消息的消息值 这样在 WinProc 中我们可以通过检查
// 当前消息的消息值是否是 VM_SOCKET来决定是否处理该消息
#define WM_SOCKET (WM_USER 1)void CreateSocketContext(SOCKET s);
LPSOCKET_CONTEXT GetSocketContext(SOCKET s);
void FreeSocketContext(SOCKET s);
HWND MakeWorkerWindow(void);
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
LPSOCKET_CONTEXT SocketContexts;int main() {MSG msg;DWORD Ret;SOCKET ListenSocket;SOCKADDR_IN Addr;HWND Window;WSADATA wsaData;// 创建用户接收 SOCKET 事件消息的窗口将 WinProc 函数指定为 WindowProcif ((Window MakeWorkerWindow()) NULL) {printf(MakeWorkerWindow() failed!\n);return 1;}// 初始化 Listen Socket 对象if (WSAStartup(0x0202, wsaData) ! 0) {printf(WSAStartup() failed with error %d\n, WSAGetLastError());return 1;}if ((ListenSocket socket(AF_INET, SOCK_STREAM, 0)) INVALID_SOCKET) {printf(socket() failed with error %d\n, WSAGetLastError());return 1;}// 将 ListenSocket 与我们创建的 Window 关联起来// 指定 SOCKET 消息的消息值为 WM_SOCKET// 我们关心的 ListenSocket 事件为 FD_ACCEPT 和 FD_CLOSEif(WSAAsyncSelect(ListenSocket, Window, WM_SOCKET, FD_ACCEPT|FD_CLOSE) ! 0) {printf(WSAAsyncSelect() failed with error code %d\n, WSAGetLastError());return 1;}Addr.sin_family AF_INET;Addr.sin_addr.s_addr htonl(INADDR_ANY);Addr.sin_port htons(PORT);if (bind(ListenSocket, (PSOCKADDR) Addr, sizeof(Addr)) SOCKET_ERROR) {printf(bind() failed with error %d\n, WSAGetLastError());return 1;}if (listen(ListenSocket, 10)) {printf(listen() failed with error %d\n, WSAGetLastError());return 1;}// 循环处理当前消息队列中的消息// 当有新的消息可用时使用 TranslateMessage 转化消息并将转换后的消息通过 DispatchMessage 分发到我们的 WinProc 函数 while(Ret GetMessage(msg, NULL, 0, 0)) {if (Ret -1) {printf(\nGetMessage() failed with error %d\n, GetLastError());return 1;}TranslateMessage(msg);printf(Dispatching a message...\n);DispatchMessage(msg);}
}// 当有新的消息可用时windows 操作系统会回调我们这个函数
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {SOCKET AcceptSocket;LPSOCKET_CONTEXT SocketContext;DWORD RecvBytes;DWORD SendBytes;DWORD Flags;// 我们仅仅关系消息值是 WM_SOCKET 的消息其他消息值代表系统的消息我们不处理// 对于其他消息我们使用 DefWindowProc 函数来调用系统默认窗口例程来处理该消息if (uMsg WM_SOCKET) {// 使用 WSAGETSELECTERROR 来检查是否发生了 SOCKET 错误if (WSAGETSELECTERROR(lParam)) {printf(Socket failed with error %d\n, WSAGETSELECTERROR(lParam));FreeSocketContext(wParam);} else {// 使用 WSAGETSELECTEVENT 来获取具体的 SOCKET 消息类型switch(WSAGETSELECTEVENT(lParam)) {// 有新的客户端连接请求接收它case FD_ACCEPT:if ((AcceptSocket accept(wParam, NULL, NULL)) INVALID_SOCKET) {printf(accept() failed with error %d\n, WSAGetLastError());break;}CreateSocketContext(AcceptSocket);printf(Socket number %d connected\n, AcceptSocket);// 将新的 SOCKET 与我们的窗口句柄关联这样我们便能获取到这个 SOCKET 上的所有消息了。 // 对于客户端连接我们关心的事件类型包括: FD_READ, FD_WRITE, FD_CLOSEWSAAsyncSelect(AcceptSocket, hwnd, WM_SOCKET, FD_READ|FD_WRITE|FD_CLOSE);break;// 客户端链接上有数据到来读取数据case FD_READ:SocketContext GetSocketContext(wParam);if (SocketContext-BytesRECV ! 0) {SocketContext-RecvPosted TRUE;return 0;} else {SocketContext-DataBuf.buf SocketContext-Buffer;SocketContext-DataBuf.len DATA_BUFSIZE;Flags 0;if (WSARecv(SocketContext-Socket, (SocketContext-DataBuf), 1, RecvBytes, Flags, NULL, NULL) SOCKET_ERROR) {if (WSAGetLastError() ! WSAEWOULDBLOCK) {printf(WSARecv() failed with error %d\n, WSAGetLastError());FreeSocketContext(wParam);return 0;}} else {printf(WSARecv() is OK!\n);SocketContext-BytesRECV RecvBytes;}}// 客户端可以写入数据之前的数据已经全部发送或者连接刚刚建立case FD_WRITE:SocketContext GetSocketContext(wParam);if (SocketContext-BytesRECV SocketContext-BytesSEND) {SocketContext-DataBuf.buf SocketContext-Buffer SocketContext-BytesSEND;SocketContext-DataBuf.len SocketContext-BytesRECV - SocketContext-BytesSEND;if (WSASend(SocketContext-Socket, (SocketContext-DataBuf), 1, SendBytes, 0, NULL, NULL) SOCKET_ERROR) {if (WSAGetLastError() ! WSAEWOULDBLOCK) {printf(WSASend() failed with error %d\n, WSAGetLastError());FreeSocketContext(wParam);return 0;}} else { // No error so update the byte countprintf(WSASend() is OK!\n);SocketContext-BytesSEND SendBytes;}}if (SocketContext-BytesSEND SocketContext-BytesRECV) {SocketContext-BytesSEND 0;SocketContext-BytesRECV 0;if (SocketContext-RecvPosted TRUE) {SocketContext-RecvPosted FALSE;// 这里我们通过 PostMessage 来发送 FD_READ 消息PostMessage(hwnd, WM_SOCKET, wParam, FD_READ);}}break;case FD_CLOSE:printf(Closing socket %d\n, wParam);FreeSocketContext(wParam);break;}}return 0;}return DefWindowProc(hwnd, uMsg, wParam, lParam);
}// 创建窗口句柄的函数
HWND MakeWorkerWindow(void) {WNDCLASS wndclass;CHAR *ProviderClass (CHAR*)AsyncSelect;HWND Window;wndclass.style CS_HREDRAW | CS_VREDRAW;// 这里非常重要我们的 WindowProc 注册到当前 WNDClass 上// 这样当我们将 SOCKET 和我们这个窗口类型的窗口句柄关联起来后// 我们便会在 WindowProc 中接受到对应的 SOCKET 消息wndclass.lpfnWndProc (WNDPROC)WindowProc;wndclass.cbClsExtra 0;wndclass.cbWndExtra 0;wndclass.hInstance NULL;wndclass.hIcon LoadIcon(NULL, IDI_APPLICATION);wndclass.hCursor LoadCursor(NULL, IDC_ARROW);wndclass.hbrBackground (HBRUSH) GetStockObject(WHITE_BRUSH);wndclass.lpszMenuName NULL;wndclass.lpszClassName (LPCSTR)ProviderClass;if (RegisterClass(wndclass) 0) {printf(RegisterClass() failed with error %d\n, GetLastError());return NULL;}// Create a windowWindow CreateWindowEx (0, // Optional window styles.(LPCSTR)ProviderClass, // Window classTEST, // Window textWS_OVERLAPPEDWINDOW, // Window styleCW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,NULL, NULL,NULL,NULL);if (Window NULL) {printf(CreateWindow() failed with error %d\n, GetLastError());return NULL;}return Window;
}void CreateSocketContext(SOCKET s) {LPSOCKET_CONTEXT SocketContxt;if ((SocketContxt (LPSOCKET_CONTEXT) GlobalAlloc(GPTR, sizeof(SOCKET_CONTEXT))) NULL) {printf(GlobalAlloc() failed with error %d\n, GetLastError());return;}// Prepare SocketInfo structure for useSocketContxt-Socket s;SocketContxt-RecvPosted FALSE;SocketContxt-BytesSEND 0;SocketContxt-BytesRECV 0;SocketContxt-Next SocketContexts;SocketContexts SocketContxt;
}LPSOCKET_CONTEXT GetSocketContext(SOCKET s) {SOCKET_CONTEXT *SocketContext SocketContexts;while(SocketContext) {if (SocketContext-Socket s) return SocketContext;SocketContext SocketContext-Next;}return NULL;
}void FreeSocketContext(SOCKET s) {SOCKET_CONTEXT *SocketContext SocketContexts;SOCKET_CONTEXT *PrevSocketContext NULL;while(SocketContext) {if (SocketContext-Socket s) {if (PrevSocketContext) PrevSocketContext-Next SocketContext-Next;else SocketContexts SocketContext-Next;closesocket(SocketContext-Socket);GlobalFree(SocketContext);return;}PrevSocketContext SocketContext;SocketContext SocketContext-Next;}
}END ! ! !