目录
事件选择模型(Event Select Model)是Windows网络编程中一种重要的异步IO模型,它允许应用程序在一个或多个套接字上接收以事件为基础的网络事件通知。与select模型相比,事件选择模型在处理大量连接时具有更高的效率和更好的性能。以下是事件选择模型的详细解析:
一、基本概念
事件选择模型通过为用户的特定操作(如socket的读写、连接等)绑定事件,并利用系统来监视这些事件的状态变化。当事件发生时,系统会将事件通知给应用程序,应用程序再对事件进行相应的处理。
二、工作原理
- 创建事件对象:使用WSACreateEvent函数为每个需要监视的套接字创建一个事件对象。
- 绑定事件对象与套接字:通过WSAEventSelect函数将事件对象与套接字以及感兴趣的网络事件类型(如FD_READ、FD_WRITE、FD_ACCEPT、FD_CONNECT、FD_CLOSE等)绑定在一起。
- 等待事件:使用WSAWaitForMultipleEvents函数等待一个或多个事件对象的状态变化。当事件发生时,该函数会返回相应的索引值,表示哪个事件对象的状态发生了变化。
- 处理事件:根据WSAWaitForMultipleEvents函数的返回值,使用WSAEnumNetworkEvents函数查询具体的网络事件类型,并对事件进行相应的处理。
三、关键函数
- WSACreateEvent:创建一个事件对象,并返回该对象的句柄。如果创建失败,则返回WSA_INVALID_EVENT。
- WSAEventSelect:将事件对象与套接字以及网络事件类型绑定在一起。如果绑定成功,则返回0;如果失败,则返回SOCKET_ERROR。
- WSAWaitForMultipleEvents:等待一个或多个事件对象的状态变化。该函数会阻塞当前线程,直到有事件发生或超时。返回值用于指示哪个事件对象的状态发生了变化。
- WSAEnumNetworkEvents:查询具体的网络事件类型,并将事件对象上的信号量重置。该函数会返回事件的类型以及可能的错误码。
四、EventSelect Qt代码
tcpserver.h
#ifndef TCPSERVER_H
#define TCPSERVER_H
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <list>
#include <map>
#define MAXNUM 64
// //sock--包大小,缓冲区,偏移量
struct stru_pack{
int m_nPackSize;
char *m_pszbuf;
int m_noffset;
};
class TCPServer
{
public:
TCPServer();
~TCPServer();
public:
bool initNetWork(const char* szip ="127.0.0.1",short nport = 1234);
void unInitNetWork(const char *szerr = "");
bool sendData(SOCKET sock,const char* szbuf,int nlen);
public:
static DWORD WINAPI ThreadProc(LPVOID lpvoid);
private:
SOCKET m_socklisten;
std::list<HANDLE> m_lstThread;
bool m_bFlagQuit;
public:
std::list<SOCKET> m_lstSocket;
std::map<SOCKET,stru_pack*> m_mapSocketToPack;
SOCKET m_arySocket[MAXNUM];
HANDLE m_aryEvent[MAXNUM];
int m_nEventNum;
};
#endif // TCPSERVER_H
tcpserver.cpp
#include "tcpserver.h"
TCPServer::TCPServer()
{
m_socklisten = 0;
m_bFlagQuit = true;
// ZeroMemory(m_arySocket,sizeof(m_arySocket));
// ZeroMemory(m_aryEvent,sizeof(m_aryEvent));
m_nEventNum = 0;
}
TCPServer::~TCPServer()
{
}
bool TCPServer::initNetWork(const char *szip, short nport)
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
/* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0) {
/* Tell the user that we could not find a usable */
/* Winsock DLL. */
printf("WSAStartup failed with error: %d\n", err);
return false;
}
/* Confirm that the WinSock DLL supports 2.2.*/
/* Note that if the DLL supports versions greater */
/* than 2.2 in addition to 2.2, it will still return */
/* 2.2 in wVersion since that is the version we */
/* requested. */
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
/* Tell the user that we could not find a usable */
/* WinSock DLL. */
unInitNetWork("Could not find a usable version of Winsock.dll\n");
return false;
}
else
printf("The Winsock 2.2 dll was found okay\n");
m_socklisten = socket(AF_INET,SOCK_STREAM,0);
if(INVALID_SOCKET == m_socklisten){
unInitNetWork("socket err\n");
return false;
}
sockaddr_in addrserver;
addrserver.sin_family = AF_INET;
addrserver.sin_port = htons(nport);
addrserver.sin_addr.s_addr = 0;
if(SOCKET_ERROR == bind(m_socklisten,(const struct sockaddr*)&addrserver,sizeof(addrserver))){
unInitNetWork("bind err\n");
return false;
}
if(SOCKET_ERROR == listen(m_socklisten,10)){
unInitNetWork("listen err\n");
return false;
}
//向Windows注册
HANDLE hEvent = WSACreateEvent(); //匿名的 无信号的 人工的事件
if(!WSAEventSelect(m_socklisten,hEvent,FD_ACCEPT)){
m_aryEvent[m_nEventNum] = hEvent;
m_arySocket[m_nEventNum] = m_socklisten;
++m_nEventNum;
}
// //创建线程
HANDLE hThread = CreateThread(0,0,&ThreadProc,this,0,0);
if(hThread)
m_lstThread.push_back(hThread);
return true;
}
DWORD TCPServer::ThreadProc(LPVOID lpvoid)
{
TCPServer *pthis = (TCPServer*)lpvoid;
DWORD dwIndex;
WSANETWORKEVENTS we;
sockaddr_in addrclient;
int nsize = sizeof(addrclient);
int nPackSize;
int nRecvNum;
while(pthis->m_bFlagQuit){
//监听事件的状态
dwIndex = WSAWaitForMultipleEvents(pthis->m_nEventNum, //事件的个数
pthis->m_aryEvent, //事件的数组
FALSE, //任意一个有信号 就返回
WSA_INFINITE, //阻塞的时间
0 //忽略
);
dwIndex -=WSA_WAIT_EVENT_0;
//判断发生什么事情了
if(WSAEnumNetworkEvents(pthis->m_arySocket[dwIndex],
pthis->m_aryEvent[dwIndex],
&we))
continue;
//处理
if (we.lNetworkEvents & FD_ACCEPT){
//接收链接
SOCKET sockWaiter = accept(pthis->m_arySocket[dwIndex],(struct sockaddr*)&addrclient,&nsize);
printf("client ip:%s nport:%d\n",inet_ntoa(addrclient.sin_addr),addrclient.sin_port);
//向windows注册
HANDLE hEvent = WSACreateEvent(); //匿名的 无信号的 人工的事件
if(!WSAEventSelect(sockWaiter,hEvent,FD_READ|FD_CLOSE)){
pthis->m_aryEvent[pthis->m_nEventNum] = hEvent;
pthis->m_arySocket[pthis->m_nEventNum] = sockWaiter;
++pthis->m_nEventNum;
}
}
if (we.lNetworkEvents & FD_READ){
//接收数据
stru_pack *p = pthis->m_mapSocketToPack[pthis->m_arySocket[dwIndex]];
if(!p){
//第一次接收 --接收包大小
nRecvNum = recv(pthis->m_arySocket[dwIndex],(char*)&nPackSize,sizeof(int),0);
p = new stru_pack;
p->m_nPackSize = nPackSize;
p->m_pszbuf = new char[nPackSize];
p->m_noffset = 0;
pthis->m_mapSocketToPack[pthis->m_arySocket[dwIndex]] = p;
}else{
//第n次接收的包内容
nRecvNum = recv(pthis->m_arySocket[dwIndex],p->m_pszbuf+p->m_noffset,p->m_nPackSize,0);
p->m_noffset += nRecvNum;
p->m_nPackSize-=nRecvNum;
if(p->m_nPackSize ==0){
printf("%s\n",p->m_pszbuf);//处理数据
delete []p->m_pszbuf;
delete p;
pthis->m_mapSocketToPack[pthis->m_arySocket[dwIndex]] = NULL;
}
}
}
if (we.lNetworkEvents & FD_CLOSE){
closesocket(pthis->m_arySocket[dwIndex]);
WSACloseEvent(pthis->m_aryEvent[dwIndex]);
//删除
if(pthis->m_nEventNum >1){
pthis->m_aryEvent[dwIndex] = pthis->m_aryEvent[pthis->m_nEventNum-1];
pthis->m_arySocket[dwIndex] = pthis->m_arySocket[pthis->m_nEventNum-1];
}
--pthis->m_nEventNum;
}
}
return 0;
}
void TCPServer::unInitNetWork(const char *szerr )
{
m_bFlagQuit = false;
for (auto& ite:m_lstThread) {
if(WAIT_TIMEOUT == WaitForSingleObject(ite,100))
TerminateThread(ite,-1);
if(ite){
CloseHandle(ite);
ite = NULL;
}
}
m_lstThread.clear();
printf(szerr);
if(m_socklisten){
closesocket(m_socklisten);
m_socklisten = 0;
}
WSACleanup();
}
bool TCPServer::sendData(SOCKET sock, const char *szbuf, int nlen)
{
if(!szbuf || nlen <=0)
return false;
//粘包
//发送包大小
if(send(sock,(char*)&nlen,sizeof(int),0)<=0)
return false;
//发送包内容
if(send(sock,szbuf,nlen,0)<=0)
return false;
return true;
}
main.cpp
#include <QCoreApplication>
#include "network/tcpserver.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
TCPServer ts;
if(ts.initNetWork())
printf("server is running......\n");
else
printf("server err\n");
return a.exec();
}
五、优缺点
优点:
- 高效性:与select模型相比,事件选择模型在处理大量连接时更加高效,因为它避免了不必要的轮询和等待。
- 灵活性:事件选择模型允许应用程序根据需要为不同的套接字绑定不同的事件,从而提高了程序的灵活性。
缺点:
- 资源限制:每个线程通过WSAWaitForMultipleEvents函数最多只能监视64个套接字(由WSA_MAXIMUM_WAIT_EVENTS定义)。如果需要监视更多的套接字,可能需要创建多个线程或使用其他技术。
- 复杂性:与select模型相比,事件选择模型的实现更加复杂,需要更多的API调用和错误处理代码。
六、应用场景
事件选择模型适用于需要处理大量连接的网络服务器或客户端程序。它可以有效地减少线程或进程的数量,降低系统资源的消耗,并提高程序的响应速度和吞吐量。
综上所述,事件选择模型是一种高效、灵活的异步IO模型,适用于需要处理大量连接的网络应用程序。然而,在使用时也需要注意其资源限制和复杂性。