网络编程(52)—— Windows下使用WSAEventSelect实现异步通知IO
2017-01-04 09:02
627 查看
一、同步IO和异步IO
同步IO是指发生IO事件的时间点和相关函数返回的时间点一致。如使用send函数发送数据时,所有的数据发送到输出缓冲区后,send函数才会返回,这种IO方式就是同步IO。异步IO指函数先于IO事件返回。还是以send函数为例,调用send函数后其马上返回,而数据传送到输出缓冲区交给操作系统完成。这种IO方式就是同步IO。什么是异步IO通知?
异步IO通知是指每当发生了IO事件——有数据需要写或读,操作系统就会产生一个事件,而我们可以根据这个事件进行相应的处理。
二、使用WSAEventSelect函数监视套接字
WSAEventSelect函数可以监视一个套接字,当套接字发生IO事件后,它会产生一个异步事件,并将该事件传到WSAEventSelect函数的参数中。WSAEventSelect的函数原型如下:int WSAEventSelect( __in SOCKET s, __in WSAEVENT hEventObject, __in long lNetworkEvents );
s —— 需要监视的socket文件描述符。
hEventObject —— 异步事件句柄。当发生lNetworkEvents所指定的的事件时,WSAEventSelect会将该句柄所指的内核对象改为signaled状态。
lNetworkEvents —— 注册的需要监视的事件,按位表示。支持的事件列表如下:
事件宏 | 描述 |
FD_READ | 是否存在需要读取的数据 |
FD_WRITE | 是否有需要传递的数据 |
FD_ACCEPT | 是否有新的连接请求 |
FD_CLOSE | 是否有需要断开的连接 |
三、使用WSACreateEvent创建non-signaled事件
现在,我们需要创建一个man-reset的non-signaled事件,来传递给WSAEventSelect的第二个参数。我们可以选择使用CreateEvent函数,它可以选择创建的事件是man-reset还是auto-reset、signaled还是non-signaled。但是使用WSACreateEvent函数会更加方便,因为它直接就可以创建一个man-reset的non-signaled事件,而无需任何参数。WSACreateEvent的函数原型如下:HANDLE WSACreateEvent(void);如果需要关闭事件,则调用WSACloseEvent函数:
BOOL WSACloseEvent( __in WSAEVENT hEvent );
四、使用WSAWaitForMultipleEvents验证是否发生事件
WSAWaitForMultipleEvents用来验证是否发生了相关的异步事件,其原型如下:DWORD WSAWaitForMultipleEvents( __in DWORD cEvents, __in const WSAEVENT *lphEvents, __in BOOL fWaitAll, __in DWORD dwTimeout, __in BOOL fAlertable );
cEvents —— 需要验证是否转变为signaled事件的总的个数。
lphEvents —— 存放事件句柄的数组。
fWaitAll —— 置为True时,所有事件变成Signaled状态时返回;置为False时,只要发生一个事件变成signaled状态就返回。
dwTimeout —— 设置等待超时,如果设为WSA_INFINITE则一直等待,直到事件变为signaled状态。
fAlertable —— 传递为True时进入alertable wait状态。
返回值 —— 返回值减去WSA_WAIT_EVENT_0时,得到是发生变成signaled状态的事件在lphEvents的索引值(是索引最小的那个事件的索引值)。
五、使用WSAEnumNetworkEvents区分事件类型
当我们通过使用WSAWaitForMultipleEvents等待到一个事件所指的内核对象变成singnaled状态之后,我们可以使用WSAEnumNetworkEvents来验证该事件的类型,是FD_READ事件、FD_WRITE事件还是FD_ACCEPT事件?WSAEnumNetworkEvents的原型如下:
int WSAEnumNetworkEvents( __in SOCKET s, __in WSAEVENT hEventObject, __out LPWSANETWORKEVENTS lpNetworkEvents );
s —— 是监视的socket描述符。
hEventObject——
是创建的异步通知IO事件。
lpNetworkEvents —— 是WSANETWORKEVENTS结构体对象,该结构体的定义如下:
typedef struct _WSANETWORKEVENTS { long lNetworkEvents; int iErrorCode[FD_MAX_EVENTS]; } WSANETWORKEVENTS, *LPWSANETWORKEVENTS;
WSANETWORKEVENTS里的lNetworkEvents用来保存发生的事件类型,我们可以通过位与操作判断是否是发生了某事件:
If((netEvents.lNetworkEvents &FD_ACCEPT) { …… }
iErrorCode保存的是发生的错误码的位数组,通过数组成员判断发生的错误类型:
if(netEvents.iErrorCode[FD_CLOSE_BIT]!=0) { puts("close error"); }
六、代码示例
以下代码,是使用异步IO通知事件实现的服务端代码:// WSAEventSelectServ.cpp : 定¡§义°?控?制?台¬¡§应®|用®?程¨¬序¨°的Ì?入¨?口¨²点Ì?。¡ê // #include "stdafx.h" #include<stdio.h> #include<stdlib.h> #include<winsock2.h> #pragma comment(lib,"ws2_32.lib") #define BUF_SIZE 30 #define EVENT_SIZE 64 void ErrorHandler(const char* message); void CompressEvents(HANDLE* events,int pos,int size); void CompressSocks(SOCKET* socks,int pos,int size); int _tmain(int argc, _TCHAR* argv[]) { WSADATAwsaData; SOCKETservSock,clntSock; SOCKADDR_INservAddr,clntAddr; int clntAddrSz; SOCKETsocks[EVENT_SIZE]; int strLen; int eventNum = 0; char buf[BUF_SIZE]; HANDLEhEvent; HANDLEevents[EVENT_SIZE]; int minPos; WSANETWORKEVENTSnetEvents; if(WSAStartup(MAKEWORD(2,2),&wsaData)==SOCKET_ERROR) ErrorHandler("WSAStartUp Error"); servSock=socket(AF_INET,SOCK_STREAM,0); if(servSock==INVALID_SOCKET) ErrorHandler("socket error"); memset(&servAddr,0,sizeof(servAddr)); servAddr.sin_family=AF_INET; servAddr.sin_addr.s_addr=INADDR_ANY; servAddr.sin_port=htons(atoi("8888")); if(bind(servSock,(constsockaddr*)&servAddr,sizeof(servAddr))==SOCKET_ERROR) ErrorHandler("bind error"); if(listen(servSock,5)==SOCKET_ERROR) ErrorHandler("listen error"); hEvent=WSACreateEvent(); //将?用®?来¤¡ä监¨¤视º¨®servSock的Ì?事º?件t放¤?到Ì?第̨²一°?个?位?置? events[eventNum]=hEvent; socks[eventNum]=servSock; //注Á¡é册¨¢servSock WSAEventSelect(servSock,hEvent,FD_ACCEPT); while(1) { //调Ì¡Â用®?WSAWaitForMultipleEvents等̨¨待äy事º?件t变À?成¨¦signaled状Á¡ä态¬?,ê?设¦¨¨置?等̨¨待äy一°?个?事º?件t即¡ä返¤¦Ì回? //且¨°不?设¦¨¨置?超?时º¡À,ê?永®¨¤远?等̨¨待äy minPos=WSAWaitForMultipleEvents(eventNum+ 1,events,false,WSA_INFINITE,false); //得Ì?到Ì?索¡Â引°y值¦Ì minPos=minPos-WSA_WAIT_EVENT_0; //遍À¨¦历¤¨²数ºy组Á¨¦其?他?元a素?,ê?调Ì¡Â用®?WSAWaitForMultipleEvents,ê?验¨¦证¡è其?他?元a素?对?应®|的Ì?内¨²核?对?象¨®是º?否¤?进?入¨? //signaled状Á¡ä态¬? for (inti=minPos;i<eventNum + 1;i++) { int otherPos = minPos; if(i!=minPos) otherPos=WSAWaitForMultipleEvents(1,events,true,0,false); //排?除y未¡ä编À¨¤程¨¬signaled状Á¡ä态¬?的Ì?事º?件t if(otherPos==WSA_WAIT_FAILED||otherPos==WSA_WAIT_TIMEOUT) continue; WSAEnumNetworkEvents(socks[i],events[i],&netEvents); if(netEvents.lNetworkEvents & FD_ACCEPT) { if(netEvents.iErrorCode[FD_ACCEPT_BIT]!=0) { puts("accept error"); break; } clntAddrSz=sizeof(clntAddr); clntSock=accept(socks[i],(SOCKADDR*)&clntAddr,&clntAddrSz); eventNum++; socks[eventNum]=clntSock; events[eventNum]=WSACreateEvent(); WSAEventSelect(socks[eventNum],events[eventNum],FD_READ|FD_CLOSE); puts("New Client Connected ..."); } if(netEvents.lNetworkEvents & FD_READ) { if(netEvents.iErrorCode[FD_READ_BIT]!=0) { puts("recv error"); break; } strLen=recv(socks[i],buf,BUF_SIZE,0); send(socks[i],buf,strLen,0); } if(netEvents.lNetworkEvents & FD_CLOSE) { if(netEvents.iErrorCode[FD_CLOSE_BIT]!=0) { puts("close error"); break; } WSACloseEvent(events[i]); closesocket(socks[i]); CompressEvents(events,eventNum,EVENT_SIZE); CompressSocks(socks,eventNum,EVENT_SIZE); eventNum--; } } } WSACleanup(); return 0; } void ErrorHandler(const char* message) { fputs(message,stderr); fputc('\n',stderr); exit(1); } void CompressEvents(HANDLE* events,int pos,int size) { while(pos<size-1) { events[pos]=events[pos+1]; } } void CompressSocks(SOCKET* socks,int pos,int size) { while(pos<size-1) { socks[pos]=socks[pos+1]; } }
Github位置:
https://github.com/HymanLiuTS/NetDevelopment
克隆本项目:
git clone git@github.com:HymanLiuTS/NetDevelopment.git
获取本文源代码:
git checkout NL52
相关文章推荐
- 网络编程(51)—— Windows下使用select进行IO复用
- 网络编程五种IO模型之WSAEventSelect模型
- [转]在C#中使用异步Socket编程实现TCP网络服务的C/S的通讯构架(二)
- 在C#中使用异步Socket编程实现TCP网络服务的C/S的通讯构架(二)----使用方法
- C#中使用异步Socket编程实现TCP网络服务的C/S的通讯构架(一)
- windows下的网络编程——Select模型实例,一款ECHO服务的实现代码
- 翻译:使用Libevent的快速可移植非阻塞网络编程:异步IO简介
- 在C#中使用异步Socket编程实现TCP网络服务的C/S的通讯构架(二)----使用方法
- C#中使用异步Socket编程实现TCP网络服务的CS的通讯构架(一)----基础类库部分
- windows下的网络编程——Select模型实例,一款ECHO服务的实现代码
- 【转】 在C#中使用异步Socket编程实现TCP网络服务的C/S的通讯构架2
- 在C#中使用异步Socket编程实现TCP网络服务的C/S的通讯构架(一)----基础类库部分
- 在C#中使用异步Socket编程实现TCP网络服务的C/S的通讯构架
- 在C#中使用异步Socket编程实现TCP网络服务
- C#中使用异步Socket编程实现TCP网络服务的C/S的通讯构架(二)
- 在C#中使用异步Socket编程实现TCP网络服务的C/S的通讯构架(二)----使用方法
- 在C#中使用异步Socket编程实现TCP网络服务的C/S的通讯构架
- 在C#中使用异步Socket编程实现TCP网络服务的C/S的通讯构架
- 在C#中使用异步Socket编程实现TCP网络服务的C/S的通讯构架(一)----基础类库部分
- 在C#中使用异步Socket编程实现TCP网络服务的C/S的通讯构架(二)----使用方法