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

实现思路

  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 ! ! !

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/499668.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

WinSock I/O 模型 -- OVERLAPPED I/O 模型

简介 OVERLAPPED I/O 模型也是 WinSock 中常见的异步 I/O 模型&#xff0c;相比于我们之前提到的 Select 模型&#xff0c;WSAAsyncSelect 模型 和 WSAEventSelect 模型有更好的性能. 为了方便描述&#xff0c;下文我们将称 Overlapped I/O 模型为 “重叠模型”. 重叠模型的…

WinSock I/O 模型 -- IOCP 模型

前言 IOCP 全称 Input/Ouput Completion Ports&#xff0c;中文中翻译一般为“完成端口”&#xff0c;本文中我们使用 IOCP 简写. IOCP 模型是迄今为止最为复杂的一种 I/O 模型&#xff0c;但是同时通过使用 IOCP 我们往往可以达到最佳的系统性能. 当你的网络应用程序需要管理…

GTank iOS App Technical Support

GTank iOS App Technical Support For All Email: z253951598outlook.com TEL: 86-17782749061 App Screen Shoots ​​

证书体系: CSR 解析

原文同时发布于本人个人博客&#xff1a; https//kutank.com/blog/cert-csr/ 简介 CSR 全称 “证书签名请求”(Certificate Signing Request). 本文我们将来详细的学习 CSR 的知识&#xff0c;重点集中在 CSR 所包含的信息&#xff0c;及其意义。 CSR 的作用: CSR 通常由想要获…

胡思乱想

学了一段时间的OGRE,才知道什么才称得上"建筑师",而我们只不过是"砌墙匠" 他们在算法算法,我们在Coding Coding,怎样才能有所改观呢~~~想当初还不如选数学专业再来学计算机可能好些, 但是既然选择了先学计算机这条路,那就先Coding,边Coding边提高数学能力…

关于数据库备份的问题

首先我们来看数据库帮助上面的sql语句&#xff1a; BACKUP DATABASE Northwind TO DISK c:/Northwind.bakRESTORE FILELISTONLY FROM DISK c:/Northwind.bakRESTORE DATABASE TestDB FROM DISK c:/Northwind.bak WITH MOVE Northwind TO c:/test/testdb.mdf, MOVE N…

关于函数指针调用C++非静态成员

当在类里面定义函数指针&#xff0c;而函数指针会指向类里面的成员的时候&#xff0c;这个时候成员需要定义为静态成员。实例代码如下&#xff1a; //.h#define SCMD_REGISTER 0class CCallFuctionList{public:CCallFuctionList();virtual ~CCallFuctionList(void);typedef…

重构心得

重构入手&#xff1a; 1. 找到牵连最广模块。 2. 找到上述模块中需要重构的相关的子类。 3. 原来代码不删除&#xff0c;保证编译运行。 4. 陆续重构其他模块 再列出我觉得可以借鉴的重构方法。【摘自代码大全】 1.保存初始代码。用你的版本控制系统保存一个初始版本&#x…

跨模块中的分配内存问题

现在有dll模块A,dll模块B以及静态库C, C中有个全局Create()函数负责创建对象M,当第二次调用Create()的时候会增加引用计数&#xff0c;并返回原来创建的对象M。Relase()会减少引用计数。当引用计数为0时&#xff0c;销毁对象M。现在在模块A中创建的初始化对象M&#xff0c;模块…

CListControl的OnMouseMove和OnNcHitTest

实际案例如下&#xff1a; 将CListCtrl做成菜单样式。需要处理当鼠标移到ClistCtrl上的事件。 处理逻辑这样&#xff1a;当鼠标移动到CListCtrl区域时候&#xff0c;将CListCtrl上所有ITem置为非选中状态&#xff0c;然后调用HitTest得到行数再将所选行置为选中状态。当鼠标移…

关于函数指针续

前面有提到过关于函数指针调用C非静态成员&#xff0c;解决了在类内调用函数指针的问题。 class CCallFuctionList { public: CCallFuctionList(); virtual ~CCallFuctionList(void); typedef void (CCallFuctionList::*FUNCPTR)(); typedef std::multimap<unsi…

关于函数指针续二

前篇文章解决了关于函数指针的不同类成员函数传递的问题。不知道细心的朋友看见我上篇文章后&#xff0c;是否发现参数必须被限制为特定的参数。 原来改进的代码如下&#xff1a; class CCallFuctionList { public:CCallFuctionList(); virtual ~CCallFuctionL…

HGE2D引擎按键消息分析

我们自己先动手来模仿HGE其键盘特殊按键消息响应&#xff0c;其中所涉及到的数据操作含义稍后再介绍。 首先创建基于对话框CGetKeyBoardTestDlg的程序&#xff0c;创建一个STATIC控件ID为IDC_STATIC_CONTENT在对话框上面。添加成员 unsigned char kbstate[256]; 和int flag; 在…

HGE引擎适用于MFC的修改

打开hge181/src/core/system.cpp 找到System_Initiate()函数&#xff0c;可以看见里面有段代码是用于创建窗口。 // Register window classwinclass.style CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW;winclass.lpfnWndProc WindowProc;winclass.cbClsExtra 0;wincl…

关于CString

昨天重构代码的时候&#xff0c;这样一段代码&#xff1a; CString str _T("bbbbbbbb");LVITEM item GetItem(str);LVITEM CLVIItemTestDlg::GetItem(CString text){LVITEM item;item.iItem 0;item.iSubItem 0;item.mask LVIF_TEXT;item.pszText text.GetBuffer…

HGE2D引擎按键消息分析(续)

继续上一章对其按键消息处理抽丝剥茧。看BuildEvent()函数里面 我们先来分析其这段代码 if(typeINPUT_KEYDOWN){if((flags & HGEINP_REPEAT) 0) keyz[key] | 1;ToAscii(key, scan, kbstate, (unsigned short *)&eptr->event.chr, 0);}if(typeINPUT_KEYUP){keyz[key…

动态链接MFC引发的血案

首先简单描述下程序运行的步骤&#xff0c; 我们要去加载两个DLL&#xff0c;先加载的称为A,后加载的称为B&#xff0c;加载A在里面做的事情是动态创建一个全局对象&#xff0c;加载B在里面做的事情是取得这个全局对象&#xff0c;然后干其他事情。 我们机子上运行的非常完美。…

规避软件架构风险之反模式

在QCON大会上&#xff0c;Michael Nygard&#xff0c;以及 李伟专家都提到了一个概念&#xff0c;容错能力。 衡量软件架构最佳的一个很重要的因素就是看软件的容错能力。没有容错能力的软件&#xff0c;哪怕你QA都非常优秀&#xff0c;但一发生故障就出现集联失效&#xff0c;…

使用DC

timer里面尽量避免使用DC&#xff0c;不然会非常慢

wxPython做界面的适用性

wxpython 优点&#xff1a; 1. 提高了程序的可维护性 2. 界面布局可由专业人员设计 3. 更好的支持GUI布局设计工具 4. 作为wxWidgets的规范&#xff0c;XRC资源可通用于C、Python等语言 5. PYTHON脚本语言上手很快。 6. 分离界面布局和程序逻辑 7. 跨平台 缺点&…