上一篇文章我们讲了EventSelect网络模型,它已经解决了等待数据到来的这一大部分时间,但是它还有一小部分时间没有节省下来。那就是把数据从网卡的缓冲区拷贝到我们应用程序的缓冲区里边。而这一篇的重叠IO模型就是将这一小部分的时间也给节省了下来。
首先,我们在主线程里边初始化网络环境,然后创建监听的socket,接下来,执行绑定,监听的操作,然后,创建一个工作者线程来对客户进行服务。执行以上操作之后呢,是一个死循环。在这个循环里边,我们首先调用accept函数来对一个客户进行连接操作。然后将该函数返回的客户端的socket保存到我们定义的一个全局socket数组里边进去。然后对我们自定义的结构体单IO操作分配一个空间,其声明如下:
typedef struct
{
WSAOVERLAPPED overlap; //OVERLAPPED结构,该结构里边有一个event事件对象
WSABUF Buffer; //WSABUF结构,里边有一个buf的大小,还有一个指针指向buf
char szMessage[MSGSIZE]; //消息数据
DWORD NumberOfBytesRecvd; //保存接收到的字节数
DWORD Flags; //标志位
}PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA;
注意,该结构体是我们自己定义的,其中第一个参数overlapped结构体是最重要的,必须把它放在第一个声明处。因为我们要的是overlapped的地址,而其它的则是我们自己进行扩展的,大家也可以进行自己的扩展。那什么叫做单IO操作呢,比如说你请了一个人来专门打理你的店铺,你每天呢都会发一个信息给这个人,信息的内容就是叫他每天干一些指定的的事情,而这个信息就是我们这个单IO操作这个数据结构所指定的内容。
我们在堆上给我们的单IO结构分配完空间之后呢,我们来对它进行初始化,我们把Buffer里面的指针指向szMessage,把里面的大小指定为szMessage的大小。然后,调用WSACreateEvent创建一个事件对象,将这个事件对象赋予给overlappped里边的事件对象。
最后,在循环的末尾我们调用WSARecv,来对数据进行接收,其声明如下:
int WSARecv(
SOCKET s, //客户连接的socket
LPWSABUF lpBuffers, //WSABUFFER指针
DWORD dwBufferCount, //Buffer的个数,一般这里给个1
LPDWORD lpNumberOfBytesRecvd, //接收的字节数
LPDWORD lpFlags, //标志位
LPWSAOVERLAPPED lpOverlapped, //overlaopped结构地址
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine //没啥用,为NULL
);
注意,该函数是异步的,也就是说它调用完就直接进行返回了,不用等待。所以整个循环,只有刚开始接收到一个客户的连接之后,我们就畅通无阻的往下执行一遍,然后再回到循环的开始继续等待客户的连接。接下来,看下工作者线程。
工作者线程,也是一个死循环,它和我们的EventSelect一样,刚开始也是调用了WSAWaitForMultipleEvents,来监控我们的socket数组哪一个有信号了,由于这个函数最多只能监控64个socket,所以我们的服务端只能同时进行64个客户的数据收发。调用完该函数之后,它会返回一个索引值,我们将调用WSAResetEvent,将我们全局event数组里边那个有信号的手动重置为无信号状态。因为我们用WSACreateEvent创建的事件对象是以手动重置的方式创建的。如果,不重置成无信号的状态,那么就像上面我们举得那个例子一样,我们请的那个人,他第二天查看信息的时候,还会继续的执行昨天的工作。
接下来,是我们重叠IO模型里边和EventSelect里边最大的不同点,我们会调用WSAGetOverlappedResult,来判断重叠IO调用是否成功,其声明如下:
BOOL WSAGetOverlappedResult(
SOCKET s, //有信号的那个socket
LPWSAOVERLAPPED lpOverlapped, //overlapped结构地址
LPDWORD lpcbTransfer, //接收的字节数
BOOL fWait, //TRUE表示操作完成就返回
LPDWORD lpdwFlags //标志位
);
这个函数的第三个参数和我们的WSARecv的第四个参数是一样的,操作系统会改写这个值,若该值为0表示客户端断开连接或该数据传输失败了。如果没有失败,我们就将数据保存到我们那个单IO结构里边的szMessage数组里边。然后再次调用WSARecv,告诉操作系统继续帮我们监控这个socket。
#include
#include
#define PORT 6000
#define MSGSIZE 1024
#pragma comment (lib, "Ws2_32.lib")
BOOL WinSockInit()
{
WSADATA data = {0};
if(WSAStartup(MAKEWORD(2, 2), &data))
return FALSE;
if ( LOBYTE(data.wVersion) !=2 || HIBYTE(data.wVersion) != 2 ){
WSACleanup();
return FALSE;
}
return TRUE;
}
typedef struct
{
WSAOVERLAPPED overlap;
WSABUF Buffer;
char szMessage[MSGSIZE];
DWORD NumberOfBytesRecvd;
DWORD Flags;
}PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA;
int g_iTotalConn = 0;
SOCKET g_CliSocketArr[MAXIMUM_WAIT_OBJECTS];
WSAEVENT g_CliEventArr[MAXIMUM_WAIT_OBJECTS];
LPPER_IO_OPERATION_DATA g_pPerIODataArr[MAXIMUM_WAIT_OBJECTS];
DWORD WINAPI WorkerThread(LPVOID);
void Cleanup(int);
int main()
{
SOCKET sListen, sClient;
SOCKADDR_IN local, client;
DWORD dwThreadId;
int iaddrSize = sizeof(SOCKADDR_IN);
// 初始化环境
WinSockInit();
// 创建监听socket
sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// 绑定
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(sListen, 3);
// 创建工作者线程
CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);
while (TRUE)
{
// 接受连接
sClient = accept(sListen, (struct sockaddr *)&client, &iaddrSize);
printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
g_CliSocketArr[g_iTotalConn] = sClient;
// 分配一个单io操作数据结构
g_pPerIODataArr[g_iTotalConn] = (LPPER_IO_OPERATION_DATA)HeapAlloc(
GetProcessHeap(),
HEAP_ZERO_MEMORY,
sizeof(PER_IO_OPERATION_DATA));
//初始化单io结构
g_pPerIODataArr[g_iTotalConn]->Buffer.len = MSGSIZE;
g_pPerIODataArr[g_iTotalConn]->Buffer.buf = g_pPerIODataArr[g_iTotalConn]->szMessage;
g_CliEventArr[g_iTotalConn] = g_pPerIODataArr[g_iTotalConn]->overlap.hEvent = WSACreateEvent();
// 开始一个异步操作
WSARecv(
g_CliSocketArr[g_iTotalConn],
&g_pPerIODataArr[g_iTotalConn]->Buffer,
1,
&g_pPerIODataArr[g_iTotalConn]->NumberOfBytesRecvd,
&g_pPerIODataArr[g_iTotalConn]->Flags,&g_pPerIODataArr[g_iTotalConn]->overlap,
NULL);
g_iTotalConn++;
}
closesocket(sListen);
WSACleanup();
return 0;
}
DWORD WINAPI WorkerThread(LPVOID lpParam)
{
int ret, index;
DWORD cbTransferred;
while (TRUE)
{
//判断是否有信号
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;
//手动设置为无信号
WSAResetEvent(g_CliEventArr[index]);
//判断该重叠调用到底是成功,还是失败
WSAGetOverlappedResult(
g_CliSocketArr[index],
&g_pPerIODataArr[index]->overlap,
&cbTransferred,
TRUE,
&g_pPerIODataArr[g_iTotalConn]->Flags);
//若调用失败
if (cbTransferred == 0)
Cleanup(index);//关闭客户端连接
else
{
//将数据保存到szMessage里边
g_pPerIODataArr[index]->szMessage[cbTransferred] = '\0';
//这里直接就转发回去了
send(g_CliSocketArr[index], g_pPerIODataArr[index]->szMessage,cbTransferred, 0);
// 进行另一个异步操作
WSARecv(g_CliSocketArr[index],
&g_pPerIODataArr[index]->Buffer,
1,
&g_pPerIODataArr[index]->NumberOfBytesRecvd,
&g_pPerIODataArr[index]->Flags,
&g_pPerIODataArr[index]->overlap,
NULL);
}
}
return 0;
}
void Cleanup(int index)
{
closesocket(g_CliSocketArr[index]);
WSACloseEvent(g_CliEventArr[index]);
HeapFree(GetProcessHeap(), 0, g_pPerIODataArr[index]);
if (index < g_iTotalConn - 1)
{
g_CliSocketArr[index] = g_CliSocketArr[g_iTotalConn - 1];
g_CliEventArr[index] = g_CliEventArr[g_iTotalConn - 1];
g_pPerIODataArr[index] = g_pPerIODataArr[g_iTotalConn - 1];
}
g_pPerIODataArr[--g_iTotalConn] = NULL;
}