一、同步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的函数原型如下:
如果需要关闭事件,则调用WSACloseEvent函数:HANDLE WSACreateEvent(void);
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