初步认识事件选择模型
select网络模型与WSAEventSelect网络模型的差别:
select网络模型是I/O复用模型,占用的是程序自身的时间片。WSAEventSelect网络模型是以事件驱动的网络异步I/O模型,检测数据准备好的事情交由操作系统完成,不占用程序自己的时间片。
(I/O复用与I/O异步的区别?复用指的是,select使用自身时间片开启一个线程来检测多个套接字的信号,同步阻塞本来需要多个线程,现在只需要一个线程,这就是复用;而异步指的是操作系统帮我们完成检测数据到来的任务,程序不用管这个过程,也就不浪费程序自身的时间片做事,当我们接收到有信号的时候,直接去recv就可以了)
select网络模型与WSAEventSelect网络模型的相同点:
1、 select和WSAEventSelect中的WSAWaitForMultipleEvents一次都最多只能检测64个客户端
2、 优化的都是recv等待数据到来的事件,从内核拷贝到程序缓冲区都需要占用自己程序的时间片来完成。
API详解
WSAEVENT WSACreateEvent (void);
Call this function to create an event object that is manually reset. This function creates the event object with an initial nonsignaled state.
功能:创建的一个手工重置的事件对象,并且初始化为无信号状态。
那么问题来了,我们在线程函数里面并没有使用WSAResetEvent,相应的事件对象会不会一直处于有信号状态?但是我们实际运行中并没有出现事件信号一直存在的情况,这让我困惑好久,在WSAEnumNetworkEvents给出答案
返回值:失败返回NULL,成功返回一个网络事件对象
#define WSAEVENT HANDLE
F12跟进去看到,WSAEVENT其实就是一个内核对象句柄
int WSAEventSelect(
_In_ SOCKET s, // 绑定到事件对象上的套接字
_In_ WSAEVENT hEventObject, // 网络事件对象
_In_ long lNetworkEvents // 绑定网络事件监听的消息类型FD_XXX
);
功能:将指定的网络套接字和需要检测的信号绑定到网络事件上,交由操作系统帮我们监测套接字信号的到来。
返回值:成功返回0,否则返回SOCKET_ERROR
DWORD WSAWaitForMultipleEvents(
_In_ DWORD cEvents, //等待的事件总数
_In_ const WSAEVENT *lphEvents, //事件数组
_In_ BOOL fWaitAll, //是否等待所有事件发生
_In_ DWORD dwTimeout, //超时时间(毫秒)
_In_ BOOL fAlertable //指定线程是否为alertable等待状态,一般设置为FALSE
);
The WSAWaitForMultipleEvents function returns when one or all of the specified event objects are in the signaled state, when the time-out interval expires, or when an I/O completion routine has executed.
功能:当一个或所有指定的事件对象都处于信号状态、超时、或完成I/O时就返回。
返回值:成功返回产生的事件在事件数组中的索引值,超时返回WSA_WAIT_TIMEOUT,失败返回WSA_WAIT_FAILED,通过WSAGetLastError获取最后的错误码
注意:WSAWaitForMultipleEvents只能将自动重置信号的事件对象设置为无信号状态,所以并不是这里将前面提到的网络事件对象重置为无信号的。WaitForSingleObject、WaitForMultipleObjects都是同理。
fWaitAll为false时,只要有一个事件信号的到来,就进行返回,处理相应事件。如果有并发的事件信号,将会根据事件数组中的顺序遍历,每次返回一个事件处理(类似for的遍历)依次处理完并发的事件。
fWaitAll为true时(通常为false),必须要等到所有的事件对象都发生的时候才会返回,此时如果服务器在处理套接字的时候,有一个套接字失效,但是没有及时消除这个套接字,此时该套接字将永远此时不会有信号到来,就会导致WSAWaitForMultipleEvents总会检测到该套接字无信号,服务器就会在这里一直阻塞。
dwTimeout通常不设置为WSA_INFINITE,否则WSAWaitForMultipleEvents会一直检测多个事件是否有信号到来,此时将大量占用CPU资源,降低服务器的性能。
typedef struct _WSANETWORKEVENTS {
long lNetworkEvents; // 网络事件的类型 FD_XXX
int iErrorCode[FD_MAX_EVENTS]; // 错误码
} WSANETWORKEVENTS, *LPWSANETWORKEVENTS;
This structure is used to store a socket’s internal information about network events.
功能:保存套接字发生的网络事件信息和相关错误码
判断哪个网络事件的方法:通过&运算,判断是lNetworkEvents中的哪一位bit有1,就标志这相应的事件有信号到来…以下是事例
#define FD_READ_BIT 0
#define FD_READ (1 << FD_READ_BIT)
lNetworkEvents :0000 0000 0000 0000 0000 0000 0000 0001
#define FD_WRITE_BIT 1
#define FD_WRITE (1 << FD_WRITE_BIT)
lNetworkEvents :0000 0000 0000 0000 0000 0000 0000 0010
#define FD_ACCEPT_BIT 3
#define FD_ACCEPT (1 << FD_ACCEPT_BIT)
lNetworkEvents :0000 0000 0000 0000 0000 0000 0000 1000
#define FD_CONNECT_BIT 4
#define FD_CONNECT (1 << FD_CONNECT_BIT)
lNetworkEvents :0000 0000 0000 0000 0000 0000 0001 0000
#define FD_CLOSE_BIT 5
#define FD_CLOSE (1 << FD_CLOSE_BIT)
lNetworkEvents :0000 0000 0000 0000 0000 0000 0010 0000
int WSAEnumNetworkEvents(
_In_ SOCKET s, // 套接字
_In_ WSAEVENT hEventObject, // 事件对象
_Out_ LPWSANETWORKEVENTS lpNetworkEvents // WSANETWORKEVENTS结构体地址
);
The WSAEnumNetworkEvents function is used to discover which network events have occurred for the indicated socket since the last invocation of this function.
功能:枚举出与事件对象相关联的套接字发生了哪些信号,结果放在WSANETWORKEVENTS结构体中
返回值:成功返回0,否则返回SOCKET_ERROR,错误码通过WSAGetlastError获得
这里我们来解释,为什么前面说的手工重置事件没有WSAResetEvent就变为了无信号的情况。下面是WSAEnumNetworkEvents的MSDN的一段解释:
The Windows Sockets provider guarantees that the operations of copying the network event record, clearing it and resetting any associated event object are automatic, such that the next occurrence of a nominated network event will cause the event object to become set. In the case of this function returning SOCKET_ERROR, the associated event object is not reset and the record of network events is not cleared.
大概意思是说,WSAEnumNetworkEvents复制出套接字中的网络事件记录(存放到WSANETWORKEVENTS结构体中),并且自动的清理掉相应套接字的网络事件信息,并且reset(重置)绑定的事件对象,防止下次发生网络事件的时候该网络事件是set(有信号)的状态。如果失败,则返回SOCK_ERROR。
到这里就解释了为什么没有WSAResetEvent也变成了无信号状态,原来WSAEnumNetworkEvents具有自用重置的功能,底层应该实现了WSAResetEvent。
事件选择模型实现流程图
_BeginThread()
// listen socket begin accept
void EventSelectModel::_BeginAccept()
{
SOCKET sockClient = 0;
while (true)
{
sockClient = accept(m_hSocket, NULL, NULL);
if (sockClient == INVALID_SOCKET)
continue;
WSAEVENT newEvent = WSACreateEvent();
if (SOCKET_ERROR == WSAEventSelect(sockClient, newEvent, FD_READ | FD_CLOSE | FD_WRITE))
{
closesocket(sockClient);
WSACloseEvent(newEvent);
continue;
}
m_eventArray[dwEventTotal] = newEvent;
m_sockArray[dwEventTotal] = sockClient;
++dwEventTotal;
++dwClientNums;
}
}
ServiceProc()
DWORD WINAPI EventSelectModel::ServiceProc(LPARAM lparam)
{
EventSelectModel* pEventModel = (EventSelectModel*)lparam;
DWORD dwIndex = 0;
char buf[1024] = { 0 };
SOCKET sockClient = INVALID_SOCKET;
SOCKADDR_IN clientAddr;
WSANETWORKEVENTS NetWorkEvent = { 0 };
while (true)
{
dwIndex = ::WSAWaitForMultipleEvents(pEventModel->dwEventTotal, pEventModel->m_eventArray, FALSE, INFINITE, FALSE);
if(dwIndex == WSA_WAIT_FAILED || dwIndex == WSA_WAIT_TIMEOUT)
continue;
if (SOCKET_ERROR == ::WSAEnumNetworkEvents(
pEventModel->m_sockArray[dwIndex - WSA_WAIT_EVENT_0],
pEventModel->m_eventArray[dwIndex - WSA_WAIT_EVENT_0],
&NetWorkEvent))
continue;
if (NetWorkEvent.lNetworkEvents & FD_READ)//有读的信号
{
if (NetWorkEvent.iErrorCode[FD_READ_BIT] != 0)
continue;
int nLen = recv(pEventModel->m_sockArray[dwIndex - WSA_WAIT_EVENT_0], buf, sizeof(buf), 0);
if (nLen == SOCKET_ERROR)
continue;
pEventModel->NetFunc(pEventModel->m_sockArray[dwIndex - WSA_WAIT_EVENT_0], buf, strlen(buf));
}
if (NetWorkEvent.lNetworkEvents & FD_CLOSE)//有断开的信号
{
if (NetWorkEvent.iErrorCode[FD_CLOSE_BIT] != 0 )
continue;
closesocket(pEventModel->m_sockArray[dwIndex - WSA_WAIT_EVENT_0]);
WSACloseEvent(pEventModel->m_eventArray[dwIndex]);
for (int i = dwIndex; i < pEventModel->dwEventTotal - 1; ++i)
{
pEventModel->m_eventArray[i] = pEventModel->m_eventArray[i + 1];
pEventModel->m_sockArray[i] = pEventModel->m_sockArray[i + 1];
}
pEventModel->dwEventTotal--;
pEventModel->dwClientNums--;
}
if (NetWorkEvent.lNetworkEvents & FD_WRITE)//写的信号
{
if (NetWorkEvent.iErrorCode[FD_WRITE_BIT] != 0)
continue;
//其余操作
}
}
return 0;
}
完整的代码实现:http://pan.baidu.com/s/1nvQgV9n
希望大家指出不足,共同进步