简介

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
);

实现思路

  1. 创建一个窗口对象,指定对应窗口的 WinProc 函数
  2. 创建 SOCKET 对象,作为监听的 SOCKET
  3. 使用 WSAAsyncSelect 函数将窗口与 SOCKET 关联起来. 同时指定SOCKET消息的消息值和 关心的 SOCKET 事件
  4. 调用 listen,开始接收客户端连接
  5. 使用 GetMessage 函数来从消息队列中获取可用的消息
  6. 获取到消息后,使用TranslateMessage 处理消息,然后调用 DispatchMessage 来分发 SOCKET 消息到我们步骤1中指定的窗口 WinProc 函数中。
  7. 循环 5-6 步骤
  8. 再 WinProc 函数中使用 WSAGETSELECTEVENT 来判断具体的 SOCKET 消息,并进行处理
  9. 如果有新的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 模型相关推荐

  1. WSAAsyncSelect模型

    Winsock提供了一个有用的异步I / O模型.利用这个模型,应用程序可在一个套接字上, 接收以Windows消息为基础的网络事件通知.具体的做法是在建好一个套接字后,调用 WSAAsyncSele ...

  2. WinSock五种I/O模型的性能分析

    原文地址:http://club.topsage.com/thread-735498-1-1.html 五种I/O模型的性能分析 重叠I/O模型的另外几个优点在于,微软针对重叠I/O模型提供了一些特有 ...

  3. 详细解析WSAAsyncSelect模型

    介绍 WinSock是Windows提供的包含了一系列网络编程接口的套接字程序库.在这篇文章中,我们将介绍如何把它的非阻塞模式引入到应用程序中. 阻塞模式WinSock.下述伪代码给出了阻塞模式下Wi ...

  4. 基于WSAAsyncSelect模型实现的聊天室图形客户端

    对应的Linux服务器为:http://blog.csdn.net/microtong/archive/2009/12/12/4989902.aspx 头文件ClientDlg.h [cpp] vie ...

  5. C++中WSAAsyncSelect模型的用法例程

     本文实例讲述了C++中WSAAsyncSelect模型的用法.分享给大家供大家参考.具体实现方法如下: TCPServer.cpp源文件如下: 复制代码 代码如下: #include " ...

  6. R语言vtreat包的mkCrossFrameCExperiment函数交叉验证构建数据处理计划并进行模型训练、通过显著性进行变量筛选(删除相关性较强的变量)、构建多变量模型、转化为分类模型、模型评估

    R语言vtreat包的mkCrossFrameCExperiment函数交叉验证构建数据处理计划并进行模型训练.通过显著性进行变量筛选(删除相关性较强的变量).构建多变量模型.转化为分类模型.模型评估 ...

  7. 为多模型寻找模型最优参数、多模型交叉验证、可视化、指标计算、多模型对比可视化(系数图、误差图、混淆矩阵、校正曲线、ROC曲线、AUC、Accuracy、特异度、灵敏度、PPV、NPV)、结果数据保存

    使用randomsearchcv为多个模型寻找模型最优参数.多模型交叉验证.可视化.指标计算.多模型对比可视化(系数图.误差图.classification_report.混淆矩阵.校正曲线.ROC曲 ...

  8. R语言构建logistic回归模型:构建模型公式、拟合logistic回归模型、模型评估,通过混淆矩阵计算precision、enrichment、recall指标

    R语言构建logistic回归模型:构建模型公式.拟合logistic回归模型.模型评估,通过混淆矩阵计算precision.enrichment.recall指标 目录

  9. R语言glm拟合logistic回归模型:模型评估(模型预测概率的分组密度图、混淆矩阵、准确率、精确度、召回率、ROC、AUC)、PRTPlot函数获取logistic模型最优阈值(改变阈值以优化)

    R语言glm拟合logistic回归模型:模型评估(模型预测概率的分组密度图.混淆矩阵.Accuray.Precision.Recall.ROC.AUC).PRTPlot函数可视化获取logistic ...

最新文章

  1. ueditor上传组件显示乱码_最全面的移动端 UI组件设计详解:中篇
  2. pandas使用groupby函数计算dataframe数据中每个分组的N个数值的滚动计数个数(rolling count)、例如,计算某公司的多个店铺每N天(5天)的滚动销售额计数个数
  3. loadrunner socket协议问题归纳(5)
  4. javaScript中变量作用域
  5. Endpoint 理解
  6. VTK:图片之ImageMandelbrotSource
  7. mysql多源复制相同数据库名称_mysql数据库多源复制方案
  8. 排列组合思维导图_排列组合——排列数专题
  9. 字符串url获取参数_如何从URL查询字符串获取示例参数或将其附加到URL查询字符串(示例)?...
  10. linux 常用操作指令(随时更新)
  11. 前端基础-git(二):轻松搞定git创建仓库,操作仓库内容
  12. 别让算法和数据结构拖你职业生涯的后腿
  13. python中unitest_基于Python的unitest框架介绍
  14. API函数简介 转自洪恩在线
  15. java 6个逆向工程软件
  16. [百度空间] [转]内存屏障 - MemoryBarrier
  17. CSS基础--absolute与overflow
  18. 华为机试od社招刷题攻略-目录
  19. visual studio 2019 + WinDDK 7600.16385.0编写驱动
  20. HFDS命令行操作(开发重点)

热门文章

  1. PHP逐行解析文件,并写入数据库
  2. 动态规划 所有题型的总结
  3. Java9都快发布了,Java8的十大新特性你了解多少呢?
  4. 熟悉 ASP.NET MVC 类
  5. StreamWriter类的一般使用方法
  6. Source Server + Symbol Server
  7. AlldayTest 产品使用--文件
  8. jstl与EL表达式处理字符串
  9. 数据库连接 未将对象引用到实例
  10. linux——线程通信(2)