TCPServ-SelectEvent基于事件选择:
Winsock提供了另一个有用的异步I/O模型。和WSAAsyncSelect模型类似的是,它也允许应用程序在一个或多个套接字上,接收以事件为基 础的网络事件通知。
。对于表1总结的、由WSAAsyncSelect模型采用的网络事件来说,它们均可原封不动地移植到新模型。在用新模型开发的应用程序 中,
也能接收和处理所有那些事件。该模型最主要的差别在于网络事件会投递至一个事件对象句柄,而非投递至一个窗口例程。(节选自《Windows网络编 程》第八章)
还是让我们先看代码然后进行分析:
[cpp] view plain copy
#include <winsock2.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")
#define PORT 5150
#define MSGSIZE 1024
int g_iTotalConn = 0;
SOCKET g_CliSocketArr[MAXIMUM_WAIT_OBJECTS];//socket数组
WSAEVENT g_CliEventArr[MAXIMUM_WAIT_OBJECTS];//事件对象数组
DWORD WINAPI WorkerThread(LPVOID);//工作者线程
void Cleanup(int index);//清掉
int main()
{
WSADATA wsaData;
SOCKET sListen, sClient;
SOCKADDR_IN local, client;
DWORD dwThreadId;
int iaddrSize = sizeof(SOCKADDR_IN);
// Initialize Windows Socket library
//装载套接字库
WSAStartup(0x0202, &wsaData);
// Create listening socket
//创建
sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// Bind
//绑定
local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
local.sin_family = AF_INET;
local.sin_port = htons(PORT);
bind(sListen, (struct sockaddr *)&local, sizeof(SOCKADDR_IN));
// Listen
//监听
listen(sListen, 3);
// Create worker thread
//创建工作者线程,如果要分组则需要几个线程。一个线程处理64个
CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);
while (TRUE)
{
// Accept a connection
//接受连接
sClient = accept(sListen, (struct sockaddr *)&client, &iaddrSize);
printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
// Associate socket with network event
g_CliSocketArr[g_iTotalConn] = sClient;//保存到套接字数组
g_CliEventArr[g_iTotalConn] = WSACreateEvent();//创建一个事件对象并保存到数组
//可以在这里加入对数组大小的判断,从而用多个数组来分组。一组可以达到64个。
//*******************************************
//主要函数 事件选择
//分组的话应当也是在用几个事件对象才行
WSAEventSelect(g_CliSocketArr[g_iTotalConn],
g_CliEventArr[g_iTotalConn],
FD_READ | FD_CLOSE);
g_iTotalConn++;
}
}
DWORD WINAPI WorkerThread(LPVOID lpParam)//工作者线程
{
int ret, index;
WSANETWORKEVENTS NetworkEvents;//事件对象
char szMessage[MSGSIZE];
while (TRUE)
{
//关键API
ret = WSAWaitForMultipleEvents(g_iTotalConn, g_CliEventArr, FALSE, 1000, FALSE);
if (ret == WSA_WAIT_FAILED || ret == WSA_WAIT_TIMEOUT)
{
continue;//如果返回值是错误或是超时,那么继续
}
index = ret - WSA_WAIT_EVENT_0;//取出发生事件的对应项
WSAEnumNetworkEvents(g_CliSocketArr[index], g_CliEventArr[index], &NetworkEvents);
if (NetworkEvents.lNetworkEvents & FD_READ)//取得FD_READ的方法
{
// Receive message from client
ret = recv(g_CliSocketArr[index], szMessage, MSGSIZE, 0);//接收
if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))
{
Cleanup(index);//掉线\退出的错误则处理
}
else
{
szMessage[ret] = '\0';
send(g_CliSocketArr[index], szMessage, strlen(szMessage), 0);//返回客户端原信息
}
}
if (NetworkEvents.lNetworkEvents & FD_CLOSE)//客户端关闭
{
Cleanup(index);
}
}
return 0;
}
void Cleanup(int index)//关闭处理
{
closesocket(g_CliSocketArr[index]);//关闭对应数组下标的套接字
WSACloseEvent(g_CliEventArr[index]);//关闭对应的事件对象
if (index < g_iTotalConn - 1)//把数组中最后一位放到关闭了的数组的位置
{
g_CliSocketArr[index] = g_CliSocketArr[g_iTotalConn - 1];
g_CliEventArr[index] = g_CliEventArr[g_iTotalConn - 1];
}
g_iTotalConn--;//然后把数组下标的量减1,从而整个数组变少了。
}
事件选择模型也比较简单,实现起来也不是太复杂,它的基本思想是将每个套接字都和一个WSAEVENT对象对应起来,
并且在关联的时候指定需要关注的哪些 网络事件。一旦在某个套接字上发生了我们关注的事件(FD_READ和FD_CLOSE),
与之相关联的WSAEVENT对象被Signaled。程序定 义了两个全局数组,一个套接字数组,一个WSAEVENT对象数组,
其大小都是MAXIMUM_WAIT_OBJECTS(64),两个数组中的元素一一 对应。
同样的,这里的程序没有考虑两个问题,一是不能无条件的调用accept,因为我们支持的并发连接数有限。
解决方法是将套接字按MAXIMUM_WAIT_OBJECTS分组,每MAXIMUM_WAIT_OBJECTS个套接字一组,每一组分配一个工作者线 程;
或者采用WSAAccept代替accept,并回调自己定义的Condition Function。第二个问题是没有对连接数为0的情形做特殊处理,
程序在连接数为0的时候CPU占用率为100%。
Winsock提供了另一个有用的异步I/O模型。和WSAAsyncSelect模型类似的是,它也允许应用程序在一个或多个套接字上,接收以事件为基 础的网络事件通知。
。对于表1总结的、由WSAAsyncSelect模型采用的网络事件来说,它们均可原封不动地移植到新模型。在用新模型开发的应用程序 中,
也能接收和处理所有那些事件。该模型最主要的差别在于网络事件会投递至一个事件对象句柄,而非投递至一个窗口例程。(节选自《Windows网络编 程》第八章)
还是让我们先看代码然后进行分析:
[cpp] view plain copy
#include <winsock2.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")
#define PORT 5150
#define MSGSIZE 1024
int g_iTotalConn = 0;
SOCKET g_CliSocketArr[MAXIMUM_WAIT_OBJECTS];//socket数组
WSAEVENT g_CliEventArr[MAXIMUM_WAIT_OBJECTS];//事件对象数组
DWORD WINAPI WorkerThread(LPVOID);//工作者线程
void Cleanup(int index);//清掉
int main()
{
WSADATA wsaData;
SOCKET sListen, sClient;
SOCKADDR_IN local, client;
DWORD dwThreadId;
int iaddrSize = sizeof(SOCKADDR_IN);
// Initialize Windows Socket library
//装载套接字库
WSAStartup(0x0202, &wsaData);
// Create listening socket
//创建
sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// Bind
//绑定
local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
local.sin_family = AF_INET;
local.sin_port = htons(PORT);
bind(sListen, (struct sockaddr *)&local, sizeof(SOCKADDR_IN));
// Listen
//监听
listen(sListen, 3);
// Create worker thread
//创建工作者线程,如果要分组则需要几个线程。一个线程处理64个
CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);
while (TRUE)
{
// Accept a connection
//接受连接
sClient = accept(sListen, (struct sockaddr *)&client, &iaddrSize);
printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
// Associate socket with network event
g_CliSocketArr[g_iTotalConn] = sClient;//保存到套接字数组
g_CliEventArr[g_iTotalConn] = WSACreateEvent();//创建一个事件对象并保存到数组
//可以在这里加入对数组大小的判断,从而用多个数组来分组。一组可以达到64个。
//*******************************************
//主要函数 事件选择
//分组的话应当也是在用几个事件对象才行
WSAEventSelect(g_CliSocketArr[g_iTotalConn],
g_CliEventArr[g_iTotalConn],
FD_READ | FD_CLOSE);
g_iTotalConn++;
}
}
DWORD WINAPI WorkerThread(LPVOID lpParam)//工作者线程
{
int ret, index;
WSANETWORKEVENTS NetworkEvents;//事件对象
char szMessage[MSGSIZE];
while (TRUE)
{
//关键API
ret = WSAWaitForMultipleEvents(g_iTotalConn, g_CliEventArr, FALSE, 1000, FALSE);
if (ret == WSA_WAIT_FAILED || ret == WSA_WAIT_TIMEOUT)
{
continue;//如果返回值是错误或是超时,那么继续
}
index = ret - WSA_WAIT_EVENT_0;//取出发生事件的对应项
WSAEnumNetworkEvents(g_CliSocketArr[index], g_CliEventArr[index], &NetworkEvents);
if (NetworkEvents.lNetworkEvents & FD_READ)//取得FD_READ的方法
{
// Receive message from client
ret = recv(g_CliSocketArr[index], szMessage, MSGSIZE, 0);//接收
if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))
{
Cleanup(index);//掉线\退出的错误则处理
}
else
{
szMessage[ret] = '\0';
send(g_CliSocketArr[index], szMessage, strlen(szMessage), 0);//返回客户端原信息
}
}
if (NetworkEvents.lNetworkEvents & FD_CLOSE)//客户端关闭
{
Cleanup(index);
}
}
return 0;
}
void Cleanup(int index)//关闭处理
{
closesocket(g_CliSocketArr[index]);//关闭对应数组下标的套接字
WSACloseEvent(g_CliEventArr[index]);//关闭对应的事件对象
if (index < g_iTotalConn - 1)//把数组中最后一位放到关闭了的数组的位置
{
g_CliSocketArr[index] = g_CliSocketArr[g_iTotalConn - 1];
g_CliEventArr[index] = g_CliEventArr[g_iTotalConn - 1];
}
g_iTotalConn--;//然后把数组下标的量减1,从而整个数组变少了。
}
事件选择模型也比较简单,实现起来也不是太复杂,它的基本思想是将每个套接字都和一个WSAEVENT对象对应起来,
并且在关联的时候指定需要关注的哪些 网络事件。一旦在某个套接字上发生了我们关注的事件(FD_READ和FD_CLOSE),
与之相关联的WSAEVENT对象被Signaled。程序定 义了两个全局数组,一个套接字数组,一个WSAEVENT对象数组,
其大小都是MAXIMUM_WAIT_OBJECTS(64),两个数组中的元素一一 对应。
同样的,这里的程序没有考虑两个问题,一是不能无条件的调用accept,因为我们支持的并发连接数有限。
解决方法是将套接字按MAXIMUM_WAIT_OBJECTS分组,每MAXIMUM_WAIT_OBJECTS个套接字一组,每一组分配一个工作者线 程;
或者采用WSAAccept代替accept,并回调自己定义的Condition Function。第二个问题是没有对连接数为0的情形做特殊处理,
程序在连接数为0的时候CPU占用率为100%。