==> 学习汇总(持续更新)
==> 从零搭建后端基础设施系列(一)-- 背景介绍
WSAEventSelect模型和WSAAsyncSelect模型
相同点:
1.都是由系统通知应用程序处理网络事件
2.都是异步的
不同点:通知机制不一样,WSAAsyncSelect模型是以windows的消息机制来通知应用程序的。WSAEventSelect模型是以事件形式通知的。所以一个需要窗口,一个则不需要。
建立WSAEventSelect模型步骤:
##1.为server socket创建网络事件对象和注册网络事件
用到的函数及原型如下:
创建网络事件对象
WSAEVENT WSACreateEvent (void);
注册网络事件
int WSAEventSelect( //成功返回0
SOCKET s, //要注册的socket
WSAEVENT hEventObject,//网络事件对象
long lNetworkEvents //网络事件,FD_开头的
);
例如:
g_hServerEvent = WSACreateEvent(); //创建网络事件对象
WSAEventSelect(g_sServer, g_hServerEvent, FD_ACCEPT);//为server socket注册网络事件
##2.接受client请求线程
用到的结构、函数及原型如下:
网络事件结构体
typedef struct _WSANETWORKEVENTS {
long lNetworkEvents; //网络事件,FD_开头的
int iErrorCode[FD_MAX_EVENTS]; //错误代码
} WSANETWORKEVENTS, *LPWSANETWORKEVENTS;
查找给定socket上发生的网络事件
int WSAEnumNetworkEvents( //成功返回0
SOCKET s,
WSAEVENT hEventObject, //该socket的网络事件对象
LPWSANETWORKEVENTS lpNetworkEvents //网络结构体指针
);
判断发生了哪个网络事件
networkEvents.lNetworkEvents & FD_ACCEPT 如果等于1 则表示发生了FD_ACCEPT事件
判断有无网络错误
networkEvents.iErrorCode[FD_ACCEPT_BIT] 等于0表示无错误
若无错误,则接受client请求,为新的client创建网络事件对象和注册网络事件。
##3.接收数据线程函数
用到的结构、函数及原型如下:
等待网络事件的到来
DWORD WSAAPI WSAWaitForMultipleEvents(
DWORD cEvents, //等待的事件数
const WSAEVENT FAR* lphEvents,//事件对象数组
BOOL fWaitAll, //是否等待全部socket都有事件才返回
DWORD dwTimeout, //等待时间,毫秒为单位,WSA_INFINE表示无限期等待
BOOL fAlertable //先置为FALSE
);
这个函数会返回一个index,表示是第几个网络事件对象,根据这个index也可以索引到socket,然后下面调用WSAEnumNetworkEvents来判断该socket上发生了什么事件。
示例代码:
#include
#include
#include
#pragma comment(lib,"ws2_32.lib")
SOCKET g_sClient[WSA_MAXIMUM_WAIT_EVENTS] = {INVALID_SOCKET}; //client socket数组
WSAEVENT g_event[WSA_MAXIMUM_WAIT_EVENTS]; //网络事件对象数组
SOCKET g_sServer = INVALID_SOCKET; //server socket
WSAEVENT g_hServerEvent; //server 网络事件对象
int iTotal = 0; //client个数
/*
@function OpenTCPServer 打开TCP服务器
@param _In_ unsigned short Port 服务器端口
@param _Out_ DWORD* dwError 错误代码
@return 成功返回TRUE 失败返回FALSE
*/
BOOL OpenTCPServer( _In_ unsigned short Port, _Out_ DWORD* dwError)
{
BOOL bRet = FALSE;
WSADATA wsaData = { 0 };
SOCKADDR_IN ServerAddr = { 0 };
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(Port);
ServerAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
do
{
if (!WSAStartup(MAKEWORD(2, 2), &wsaData))
{
if (LOBYTE(wsaData.wVersion) == 2 || HIBYTE(wsaData.wVersion) == 2)
{
g_sServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
g_hServerEvent = WSACreateEvent(); //创建网络事件对象
WSAEventSelect(g_sServer, g_hServerEvent, FD_ACCEPT);//为server socket注册网络事件
if (g_sServer != INVALID_SOCKET)
{
if (SOCKET_ERROR != bind(g_sServer, (SOCKADDR*)&ServerAddr, sizeof(ServerAddr)))
{
if (SOCKET_ERROR != listen(g_sServer, SOMAXCONN))
{
bRet = TRUE;
break;
}
*dwError = WSAGetLastError();
closesocket(g_sServer);
}
*dwError = WSAGetLastError();
closesocket(g_sServer);
}
*dwError = WSAGetLastError();
}
*dwError = WSAGetLastError();
}
*dwError = WSAGetLastError();
} while (FALSE);
return bRet;
}
//接受client请求线程
unsigned int __stdcall ThreadAccept(void* lparam)
{
WSANETWORKEVENTS networkEvents; //网络事件结构
while (iTotal < WSA_MAXIMUM_WAIT_EVENTS) //这个值是64
{
if (0 == WSAEnumNetworkEvents(g_sServer, g_hServerEvent, &networkEvents))
{
if (networkEvents.lNetworkEvents & FD_ACCEPT) //如果等于FD_ACCEPT,相与就为1
{
if (0 == networkEvents.iErrorCode[FD_ACCEPT_BIT]) //检查有无网络错误
{
//接受请求
SOCKADDR_IN addrServer = { 0 };
int iaddrLen = sizeof(addrServer);
g_sClient[iTotal] = accept(g_sServer, (SOCKADDR*)&addrServer, &iaddrLen);
if (g_sClient[iTotal] == INVALID_SOCKET)
{
printf("accept failed with error code: %d\n", WSAGetLastError());
return 1;
}
//为新的client注册网络事件
g_event[iTotal] = WSACreateEvent();
WSAEventSelect(g_sClient[iTotal], g_event[iTotal], FD_READ | FD_WRITE | FD_CLOSE);
iTotal++;
printf("accept a connection from IP: %s,Port: %d\n", inet_ntoa(addrServer.sin_addr), htons(addrServer.sin_port));
}
else //错误处理
{
int iError = networkEvents.iErrorCode[FD_ACCEPT_BIT];
printf("WSAEnumNetworkEvents failed with error code: %d\n", iError);
return 1;
}
}
}
Sleep(100);
}
return 0;
}
//接收数据
unsigned int __stdcall ThreadRecv(void* lparam)
{
char* buf = (char*)malloc(sizeof(char) * 128);
while (1)
{
if (iTotal == 0)
{
Sleep(100);
continue;
}
//等待网络事件
DWORD dwIndex = WSAWaitForMultipleEvents(iTotal, g_event, FALSE, 1000, FALSE);
//当前的事件对象
WSAEVENT curEvent = g_event[dwIndex];
//当前的套接字
SOCKET sCur = g_sClient[dwIndex];
//网络事件结构
WSANETWORKEVENTS networkEvents;
if (0 == WSAEnumNetworkEvents(sCur, curEvent, &networkEvents))
{
if (networkEvents.lNetworkEvents & FD_READ) //有数据可读
{
if (0 == networkEvents.iErrorCode[FD_READ_BIT])
{
memset(buf, 0, sizeof(buf));
int iRet = recv(sCur, buf, sizeof(buf), 0); //接收数据
if (iRet != SOCKET_ERROR)
{
if (strlen(buf) != 0)
printf("Recv: %s\n", buf);
}
}
else //错误处理
{
int iError = networkEvents.iErrorCode[FD_ACCEPT_BIT];
printf("WSAEnumNetworkEvents failed with error code: %d\n", iError);
break;
}
}
else if (networkEvents.lNetworkEvents & FD_CLOSE) //client关闭
printf("%d downline\n", sCur);
}
Sleep(100);
}
if (buf)
free(buf);
return 0;
}
int main()
{
DWORD dwError = 0;
if (OpenTCPServer(18000, &dwError))
{
_beginthreadex(NULL, 0, ThreadAccept, NULL, 0, NULL);
_beginthreadex(NULL, 0, ThreadRecv, NULL, 0, NULL);
}
Sleep(100000000);
closesocket(g_sServer);
WSACleanup();
return 0;
}
这里有个问题要说一下,就是WSAWaitForMultipleEvents可以无限期阻塞,但是最后不要这样,因为如果它阻塞在一个状态后,有新的client加入,它不知道,得不到更新,就算新的client发送消息,也不会返回,要等原来的client发送消息让它返回后才会处理新的client消息。所以我建议timeout时间设置为1s或几s都行,这样轮询消耗的CPU可以忽略不计。这个问题在Select模型的时候也有,Select也会阻塞在那里.