WinSock I/O 模型 -- WSAEventSelect 模型

简介

WSAEventSelect 模型也是 WinSock 中最常见的异步 I/O 模型。

这篇文章我们就来看看如何使用 WSAEventSelect api 来实现一个简单的 TCP 服务器.

API 基础

WSAEventSelect

WSAEventSelect 用来把一个 SOCKET 对象和一个 WSAEVENT 对象关联起来。 lNetworkEvents 表示我们关心的 FD_XXX 网络事件. 如果关心多个 SOCKET 事件,可以使用 OR 的方式指定多个 FD_XXX 标志。

int WSAAPI WSAEventSelect(SOCKET   s,WSAEVENT hEventObject,long     lNetworkEvents
);

当特定的事件发生在相应的 SOCKET 上,该 SOCKET 上接下来的事件将会被阻塞,直到当前的事件被应用处理. 该事件被处理之后,接下来的事件将可以被进一步触发.

事件处理函数
FD_READrecv, recvFrom, WSARecv, WSARecvEx, WSARecvFrom
FD_WRITEsend, sendTo, WSASend, WSASentTo
FD_OOBrecv, recvFrom, WSARecv, WSARecvEx, WSARecvFrom
FD_ACCEPTaccept, AcceptEx, WSAAccept
FD_CONNECTNone
FD_CLOSENone
FD_QOSWSAIoctl (with SIO_GET_QOS)
FD_GROUP_QOSReserved
FD_ROUTINE_INTERFACE_CHANGEWSAIoctl (with SIO_ROUTINE_INTERFACE_CHANGE)
FD_ADDRESS_LIST_CHANGEWSAIoctl (with SIO_ADDRESS_LIST_CHANGE)
WSAEvent

WSACreateEvent 方法用来创建一个 WSAEvent 对象

WSAEVENT WSAAPI WSACreateEvent();

WSAWaitForMultipleEvents 用于等待一组事件中的一个或全部被触发。

DWORD WSAAPI WSAWaitForMultipleEvents(DWORD          cEvents,const WSAEVENT *lphEvents,BOOL           fWaitAll,DWORD          dwTimeout,BOOL           fAlertable
);
  • cEvents:指定 lphEvents 数组中事件对象的数量。 该参数的最大值是 WSA_MAXIMUM_WAIT_EVENTS (64)
  • lphEvents:事件对象的集合
  • fWaitAll: 指定等待 lphEvents 中所有事件被触发或者其中之一被触发。 如果指定为 TRUE, 那么该函数只有在所有事件对象都被触发之后才会返回。 如果指定为 FALSE, 当事件集合中任何一个事件被触发之后,该方法就会返回。如果在这种情况下有多个事件对象被触发,那个返回值将会返回该事件集合中索引值最小的索引值. 索引值减去 WSA_WAIT_EVENT_0 便是指向 lphEvents 中被触发的事件的索引值.
  • dwTimeout: 如果在 timeout 事件间隔内,没有事件被触发,函数不会一直阻塞,而是在等待 timeout 毫秒后返回。 指定该参数为 WSA_INFINITE, 该函数会一直等待,直到有事件被触发. 指定该参数为 0, 该函数会立即返回.
  • fAlertable: 略

WSAEnumNetworkEvents 用于查询当前 SOCKET 上触发事件对象 hEventObject 的对应 socket 事件(FD_READ, FD_WRITE 等).

int WSAAPI WSAEnumNetworkEvents(SOCKET             s,WSAEVENT           hEventObject,LPWSANETWORKEVENTS lpNetworkEvents
);

实现思路

  1. 创建一个 socket 作为监听 socket
  2. 使用 WSAEventSelect 监听该 SOCKET 上的网络事件
  3. 使用 WSAWaitForMultipleEvents 等待 SOCKET 事件
  4. 当 SOCKET 上有事件被触发,使用 WSAEnumNetworkEvents 查询具体的 SOCKET 事件,并使用相应的 API 处理事件.
  5. 当有新的 SOCKET 连接到来,接收该连接,重复 2-4 步骤.

实例

接下来我们通过一个实例来看看如何实现.

#include <winsock2.h>
#include <windows.h>
#include <stdio.h>#pragma comment(lib,"ws2_32.lib")#define PORT 8080
#define DATA_BUFSIZE 8192typedef struct _SOCKET_CONTEXT {CHAR   Buffer[DATA_BUFSIZE];WSABUF DataBuf;SOCKET Socket;DWORD  BytesSEND;DWORD  BytesRECV;
} SOCKET_CONTEXT, * LPSOCKET_CONTEXT;BOOL CreateSocketInformation(SOCKET s);
void FreeSocketInformation(DWORD Event);// 这里我们维护了如下数据结构:
//     EeventArray: 我们为每个 SOCKET 对象创建一个对应的事件对象,以便我们能监听该 SOCKET 上的网络事件
//     SocketArray: 毫无疑问,我们也需要维护所有SOCKET连接的的数组。其中包含 Listen SocketDWORD            EventTotal = 0;
WSAEVENT         EventArray[WSA_MAXIMUM_WAIT_EVENTS];
LPSOCKET_CONTEXT SocketArray[WSA_MAXIMUM_WAIT_EVENTS];int main() {SOCKET           ListenSocket;SOCKET           AcceptSocket;SOCKADDR_IN      Addr;LPSOCKET_CONTEXT SocketContext;WSANETWORKEVENTS NetworkEvents;DWORD            Event;WSADATA          wsaData;DWORD            Flags;DWORD            RecvBytes;DWORD            SendBytes;// 初始化 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;}if (CreateSocketInformation(ListenSocket) == FALSE) {printf("CreateSocketInformation() failed!\n");return 1;}// 在调用 listen api 之前,我们需要使用 WSAEventSelect 需要将 ListenSocket 与一个 WSAEvent 对象关联起来,这里我们仅仅关系 FD_ACCEPT 和 FS_CLOSE 事件.// 当这两个事件之一被触发,我们编译可以从 EventArray[0] 上查询到这些实际,以便进行处理if (WSAEventSelect(ListenSocket, EventArray[EventTotal - 1], FD_ACCEPT | FD_CLOSE) == SOCKET_ERROR) {printf("WSAEventSelect() failed with error %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;}while(TRUE) {// 等待当前所有 socket 上的网络事件被触发//// 这里我们 fWait = FALSE, 也就是说任何一个 SOCKET 上有网络之间,// 我们便停止等待,开始处理该事件//// dwTimeout = WSA_INFINITE, 如果没有网络事件发生,我们就一直等待,// 直到有网络事件发生//// 这里 EventTotal 会随着客户端连接的到来增加,同时我们会创建对应的 Event对象,// 并放入 EventArrayif ((Event = WSAWaitForMultipleWSAWaitForEvents(EventTotal, EventArray, FALSE, WSA_INFINITE, FALSE)) == WSA_WAIT_FAILED) {printf("WSAWaitForMultipleEvents() failed with error %d\n", WSAGetLastError());return 1;}// 程序运行到这里,已经有网络事件发生了,我们使用WSAEnumNetworkEvents 查询到底是什么网络事件, // 查询结果保存在 NetworkEvents 对象上// 注意,在 API 章节我们已经说明,WSAWaitForMultipleWSAWaitForEvents 的返回值减去 WSA_WAIT_EVENT_0 才是对应的 EventArray 中被触发的事件的索引值if (WSAEnumNetworkEvents(SocketArray[Event - WSA_WAIT_EVENT_0]->Socket,EventArray[Event - WSA_WAIT_EVENT_0], &NetworkEvents) == SOCKET_ERROR) {printf("WSAEnumNetworkEvents() failed with error %d\n", WSAGetLastError());return 1;}// 检查当前事件是否是 FD_ACCEPT// 如果是 FD_ACCEPT事件,使用 accept 接收新的连接。if (NetworkEvents.lNetworkEvents & FD_ACCEPT) {if (NetworkEvents.iErrorCode[FD_ACCEPT_BIT] != 0) {printf("FD_ACCEPT failed with error %d\n", NetworkEvents.iErrorCode[FD_ACCEPT_BIT]);break;}if ((AcceptSocket = accept(SocketArray[Event - WSA_WAIT_EVENT_0]->Socket, NULL, NULL)) == INVALID_SOCKET) {printf("accept() failed with error %d\n", WSAGetLastError());break;}if (EventTotal > WSA_MAXIMUM_WAIT_EVENTS) {printf("Too many connections - closing socket...\n");closesocket(AcceptSocket);break;}// 接收新的连接之后,为该 SOCKET 创建 WSAEvent对象(在CreateSocketInformation 实现)// 然后监听该 SOCKET 的 FD_READ, FD_WRITE, FD_CLOSE 事件CreateSocketInformation(AcceptSocket);if (WSAEventSelect(AcceptSocket, EventArray[EventTotal - 1], FD_READ|FD_WRITE|FD_CLOSE) == SOCKET_ERROR) {printf("WSAEventSelect() failed with error %d\n", WSAGetLastError());return 1;}printf("Socket %d got connected...\n", AcceptSocket);}// 检查当前事件是否是 FD_READ 或者 FD_WRITEif (NetworkEvents.lNetworkEvents & FD_READ || NetworkEvents.lNetworkEvents & FD_WRITE) {// 检查是不是发生了读错误if (NetworkEvents.lNetworkEvents & FD_READ && NetworkEvents.iErrorCode[FD_READ_BIT] != 0) {printf("FD_READ failed with error %d\n", NetworkEvents.iErrorCode[FD_READ_BIT]);break;}// 检查是不是发生了写错误if (NetworkEvents.lNetworkEvents & FD_WRITE && NetworkEvents.iErrorCode[FD_WRITE_BIT] != 0) {printf("FD_WRITE failed with error %d\n", NetworkEvents.iErrorCode[FD_WRITE_BIT]);break;}SocketContext = SocketArray[Event - WSA_WAIT_EVENT_0];// Read data only if the receive buffer is emptyif (SocketContext->BytesRECV == 0) {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());FreeSocketInformation(Event - WSA_WAIT_EVENT_0);return 1;}} else {printf("WSARecv() is working!\n");SocketContext->BytesRECV = RecvBytes;}}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());FreeSocketInformation(Event - WSA_WAIT_EVENT_0);return 1;}// A WSAEWOULDBLOCK error has occurred. An FD_WRITE event will be posted// when more buffer space becomes available} else {printf("WSASend() is fine! Thank you...\n");SocketContext->BytesSEND += SendBytes;if (SocketContext->BytesSEND == SocketContext->BytesRECV) {SocketContext->BytesSEND = 0;SocketContext->BytesRECV = 0;}}}}// 检查当前事件是否是 FD_CLOSEif (NetworkEvents.lNetworkEvents & FD_CLOSE) {// 检查是否发生了错误if (NetworkEvents.iErrorCode[FD_CLOSE_BIT] != 0) {printf("FD_CLOSE failed with error %d\n", NetworkEvents.iErrorCode[FD_CLOSE_BIT]);break;} else {// socket 正常关闭printf("FD_CLOSE is OK!\n");}printf("Closing socket information %d\n", SocketArray[Event - WSA_WAIT_EVENT_0]->Socket);FreeSocketInformation(Event - WSA_WAIT_EVENT_0);}}return 0;
}BOOL CreateSocketInformation(SOCKET s) {LPSOCKET_CONTEXT SocketContext;if ((EventArray[EventTotal] = WSACreateEvent()) == WSA_INVALID_EVENT) {printf("WSACreateEvent() failed with error %d\n", WSAGetLastError());return FALSE;}if ((SocketContext = (LPSOCKET_CONTEXT) GlobalAlloc(GPTR, sizeof(SOCKET_CONTEXT))) == NULL) {printf("GlobalAlloc() failed with error %d\n", GetLastError());return FALSE;}// Prepare SocketInfo structure for useSocketContext->Socket = s;SocketContext->BytesSEND = 0;SocketContext->BytesRECV = 0;SocketArray[EventTotal] = SocketContext;EventTotal++;return TRUE;
}void FreeSocketInformation(DWORD Event) {LPSOCKET_CONTEXT SocketContext = SocketArray[Event];DWORD i;closesocket(SocketContext->Socket);GlobalFree(SocketContext);if(WSACloseEvent(EventArray[Event]) == TRUE) {printf("WSACloseEvent() is OK!\n\n");}// Squash the socket and event arraysfor (i = Event; i < EventTotal; i++) {EventArray[i] = EventArray[i + 1];SocketArray[i] = SocketArray[i + 1];}EventTotal--;
}

END!!!

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

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

相关文章

WinSock I/O 模型 -- WSAAsyncSelect 模型

简介 WSAAsyncSelect 模型也是 WinSock 中常见的异步 I/O 模型。 使用这个模型&#xff0c;网络应用程序通过接收以 Windows 消息为基础的网络事件通知来处理网络请求。 这篇文章我们就来看看如何使用 WSAAsyncSelect api 来实现一个简单的 TCP 服务器. API 基础 要使用 W…

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;不然会非常慢