WinSock I/O 模型 -- WSAAsyncSelect 模型
简介
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 class"TEST", // 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 ! ! !
WinSock I/O 模型 -- WSAAsyncSelect 模型相关推荐
- WSAAsyncSelect模型
Winsock提供了一个有用的异步I / O模型.利用这个模型,应用程序可在一个套接字上, 接收以Windows消息为基础的网络事件通知.具体的做法是在建好一个套接字后,调用 WSAAsyncSele ...
- WinSock五种I/O模型的性能分析
原文地址:http://club.topsage.com/thread-735498-1-1.html 五种I/O模型的性能分析 重叠I/O模型的另外几个优点在于,微软针对重叠I/O模型提供了一些特有 ...
- 详细解析WSAAsyncSelect模型
介绍 WinSock是Windows提供的包含了一系列网络编程接口的套接字程序库.在这篇文章中,我们将介绍如何把它的非阻塞模式引入到应用程序中. 阻塞模式WinSock.下述伪代码给出了阻塞模式下Wi ...
- 基于WSAAsyncSelect模型实现的聊天室图形客户端
对应的Linux服务器为:http://blog.csdn.net/microtong/archive/2009/12/12/4989902.aspx 头文件ClientDlg.h [cpp] vie ...
- C++中WSAAsyncSelect模型的用法例程
本文实例讲述了C++中WSAAsyncSelect模型的用法.分享给大家供大家参考.具体实现方法如下: TCPServer.cpp源文件如下: 复制代码 代码如下: #include " ...
- R语言vtreat包的mkCrossFrameCExperiment函数交叉验证构建数据处理计划并进行模型训练、通过显著性进行变量筛选(删除相关性较强的变量)、构建多变量模型、转化为分类模型、模型评估
R语言vtreat包的mkCrossFrameCExperiment函数交叉验证构建数据处理计划并进行模型训练.通过显著性进行变量筛选(删除相关性较强的变量).构建多变量模型.转化为分类模型.模型评估 ...
- 为多模型寻找模型最优参数、多模型交叉验证、可视化、指标计算、多模型对比可视化(系数图、误差图、混淆矩阵、校正曲线、ROC曲线、AUC、Accuracy、特异度、灵敏度、PPV、NPV)、结果数据保存
使用randomsearchcv为多个模型寻找模型最优参数.多模型交叉验证.可视化.指标计算.多模型对比可视化(系数图.误差图.classification_report.混淆矩阵.校正曲线.ROC曲 ...
- R语言构建logistic回归模型:构建模型公式、拟合logistic回归模型、模型评估,通过混淆矩阵计算precision、enrichment、recall指标
R语言构建logistic回归模型:构建模型公式.拟合logistic回归模型.模型评估,通过混淆矩阵计算precision.enrichment.recall指标 目录
- R语言glm拟合logistic回归模型:模型评估(模型预测概率的分组密度图、混淆矩阵、准确率、精确度、召回率、ROC、AUC)、PRTPlot函数获取logistic模型最优阈值(改变阈值以优化)
R语言glm拟合logistic回归模型:模型评估(模型预测概率的分组密度图.混淆矩阵.Accuray.Precision.Recall.ROC.AUC).PRTPlot函数可视化获取logistic ...
最新文章
- ueditor上传组件显示乱码_最全面的移动端 UI组件设计详解:中篇
- pandas使用groupby函数计算dataframe数据中每个分组的N个数值的滚动计数个数(rolling count)、例如,计算某公司的多个店铺每N天(5天)的滚动销售额计数个数
- loadrunner socket协议问题归纳(5)
- javaScript中变量作用域
- Endpoint 理解
- VTK:图片之ImageMandelbrotSource
- mysql多源复制相同数据库名称_mysql数据库多源复制方案
- 排列组合思维导图_排列组合——排列数专题
- 字符串url获取参数_如何从URL查询字符串获取示例参数或将其附加到URL查询字符串(示例)?...
- linux 常用操作指令(随时更新)
- 前端基础-git(二):轻松搞定git创建仓库,操作仓库内容
- 别让算法和数据结构拖你职业生涯的后腿
- python中unitest_基于Python的unitest框架介绍
- API函数简介 转自洪恩在线
- java 6个逆向工程软件
- [百度空间] [转]内存屏障 - MemoryBarrier
- CSS基础--absolute与overflow
- 华为机试od社招刷题攻略-目录
- visual studio 2019 + WinDDK 7600.16385.0编写驱动
- HFDS命令行操作(开发重点)