您的位置:首页 > 理论基础 > 计算机网络

基于事件通知的重叠I/O网络模型

2017-11-03 20:20 423 查看
这里就补充一个API就足够了,其余的在基于完成例程的重叠IO网络模型中有详尽的解释,这里且就不一一赘述

重要API

WSAGetOverlappedResult

BOOL WSAAPI WSAGetOverlappedResult(
_In_   SOCKET s,
_In_   LPWSAOVERLAPPED lpOverlapped,
_Out_  LPDWORD lpcbTransfer,
_In_   BOOL fWait,
_Out_  LPDWORD lpdwFlags
);


s [in]

标识套接字的描述符。

lpOverlapped [in]

指向重叠操作开始时指定的WSAOVERLAPPED结构的指针。这个参数不能是一个NULL指针。

lpcbTransfer [out]

指向32位变量的指针,该变量存储经过接收操作、发送操作或WSAIoctl函数实际传输的字节数。这个参数不能是一个NULL指针。

fWait [in]

一个标志,指定函数是否应等待挂起的重叠操作完成。如果为TRUE,则在操作完成之前,该功能不会返回。如果FALSE且该操作仍处于挂起状态,则函数返回FALSE,WSAGetLastError函数返回WSA_IO_INCOMPLETE。仅当选择为基于事件通知的重叠操作时,fWait参数才可以设置为TRUE。

lpdwFlags [out]

指向一个32位变量的指针,它将接收一个或多个补充完成状态的标志。如果重叠操作是通过WSARecv或WSARecvFrom启动的,则此参数将包含lpFlags参数的结果值。这个参数不能是一个NULL指针。

功能

该函数返回指定套接字上重叠操作的结果。

返回值

如果WSAGetOverlappedResult成功,则返回值为TRUE。 这意味着重叠的操作已经成功完成,并且lpcbTransfer指向的值已被更新。

如果WSAGetOverlappedResult返回FALSE,则表示重叠操作未完成,重叠操作完成但出错,或者由于WSAGetOverlappedResult的一个或多个参数错误,无法确定重叠操作的完成状态。 失败时,lpcbTransfer指向的值不会被更新。 使用WSAGetLastError来确定失败的原因(通过WSAGetOverlappedResult函数或关联的重叠操作)。

错误码及含意

WSANOTINITIALISED:在使用这个函数之前,一个成功的WSAStartup调用必须发生。

WSAENETDOWN:网络子系统失败。

WSA_INVALID_HANDLE:WSAOVERLAPPED结构的hEvent参数不包含有效的事件对象句柄。

WSA_INVALID_PARAMETER:其中一个参数是不可接受的。

WSA_IO_INCOMPLETE:fWait参数为FALSE时产生该错误,表示I / O操作尚未完成。

WSAEFAULT:一个或多个lpOverlapped,lpcbTransfer或lpdwFlags参数不在用户地址空间的有效部分中。 如果lpOverlapped,lpcbTransfer或lpdwFlags参数在Windows Server 2003和更早版本上是空指针,则会返回此错误。

源码分析

1.实现流程图



2.源码

OverlapIOModel.h

#pragma once
#include "Socket.h"

#define MSGSIZE 1024

/************************************************************************/
/* 注意,此处的 WSAOVERLAPPED 不一定要在首部,区分和完成例程的区别(完成例程中有解释)
/************************************************************************/
typedef struct
{
WSAOVERLAPPED overlap;      // 这里保存的一个事件对象...缓冲区的位置
WSABUF wsaBuf;              // 指明缓冲区的成员
char szMessage[MSGSIZE];    // 真正的缓冲区
DWORD NumberOfBytesRecvd;   // 接收到的字节
DWORD Flags;                // 是否成功接收
SOCKADDR_IN addr;           // 客户端信息
SOCKET sock;                // 套接字
}PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA;

typedef void(*NetCallBack) (DWORD id, void* param, int len); // 定义消息处理函数指针

/************************************************************************/
/* 基于事件通知的重叠IO模型类
/* Socket为自己封装的socket类
/************************************************************************/
class OverlapIOModel : public Socket
{
private:
int g_iTotalConn;                                   // 总的连接数
WSAEVENT g_CliEventArr[MAXIMUM_WAIT_OBJECTS];       // 事件对象数组
LPPER_IO_OPERATION_DATA g_pPerIODataArr[MAXIMUM_WAIT_OBJECTS];  // 重叠结构数组

protected:
// 消息处理函数
NetCallBack NetFunc;

//begin accept
void _BeginAccept();

//clean client socket
void Cleanup(int index);

//service proc
static DWORD WINAPI ServiceProc(LPARAM lparam);

public:
OverlapIOModel();
~OverlapIOModel();

//init net
BOOL InitNet(NetCallBack func, UINT nSocketPOrt, LPCSTR lpszSocketAddr = ADDR_ANY);

//wsa send to client
bool WSASendToClient(DWORD id, void* lparam);
};


InitNet

BOOL OverlapIOModel::InitNet(NetCallBack func, UINT nSocketPOrt, LPCSTR lpszSocketAddr /*= ADDR_ANY*/)
{
NetFunc = func;
if (!Create(nSocketPOrt, SOCK_STREAM, lpszSocketAddr))
return false;
if (!Listen(5))
return false;
CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)ServiceProc, this, NULL, NULL);
_BeginAccept();
return true;
}


初始化网络的一切信息,包括初始化网络环境,绑定,监听,并调用_BeginAccept开始接受客户端的连接

_BeginAccept

void OverlapIOModel::_BeginAccept()
{
SOCKET sClient = 0;
SOCKADDR_IN addrClient = { 0 };
int nLen = sizeof(SOCKADDR_IN);
int err = 0;

while (true)
{
sClient = accept(m_hSocket, (sockaddr*)&addrClient, &nLen);
if (sClient == INVALID_SOCKET)
continue;

printf("[%s:%d]->log on\n", inet_ntoa(addrClient.sin_addr), ntohs(addrClient.sin_por
e501
t));

g_pPerIODataArr[g_iTotalConn] = (LPPER_IO_OPERATION_DATA)HeapAlloc(// 在程序的默认堆上申请内存
GetProcessHeap(),
HEAP_ZERO_MEMORY,
sizeof(PER_IO_OPERATION_DATA));
g_pPerIODataArr[g_iTotalConn]->sock = sClient;
g_pPerIODataArr[g_iTotalConn]->addr = addrClient;
g_pPerIODataArr[g_iTotalConn]->NumberOfBytesRecvd = 0;
g_pPerIODataArr[g_iTotalConn]->wsaBuf.len = MSGSIZE;
g_pPerIODataArr[g_iTotalConn]->wsaBuf.buf = g_pPerIODataArr[g_iTotalConn]->szMessage;
//create net event
g_CliEventArr[g_iTotalConn] = g_pPerIODataArr[g_iTotalConn]->overlap.hEvent = WSACreateEvent();

//begin wsa recv(only can recv one time)
if (SOCKET_ERROR == WSARecv(
g_pPerIODataArr[g_iTotalConn]->sock,
&(g_pPerIODataArr[g_iTotalConn]->wsaBuf),
1,
&(g_pPerIODataArr[g_iTotalConn]->NumberOfBytesRecvd),
&(g_pPerIODataArr[g_iTotalConn]->Flags),
&(g_pPerIODataArr[g_iTotalConn]->overlap),
NULL))
{
err = WSAGetLastError();
if (WSA_IO_PENDING != err) // WSA_IO_PENDING 重叠IO操作成功,等待稍后完成
{
HeapFree(GetProcessHeap(), 0, g_pPerIODataArr[g_iTotalConn]);
printf("WSARecv failed:%d\n", err);
continue;
};
}
g_iTotalConn++;
}
}


接受到客户端的连接后,将客户端的连接信息保存到结构体PER_IO_OPERATION_DATA全局数组变量g_pPerIODataArr中保存好信息,然后开始投递WSARecv异步操作,告诉操作系统帮我们接受该套接字的消息。

ServiceProc

/************************************************************************/
/* 基于事件通知的重叠IO网络模型的主要实现(工作线程)                        */
/************************************************************************/
DWORD WINAPI OverlapIOModel::ServiceProc(LPARAM lparam)
{
OverlapIOModel* pOverLapIO = (OverlapIOModel*)lparam;
int ret = 0;
int index = 0;
DWORD err = 0;
while (TRUE)
{
//////////////////////////////////////////////////////////////////////////
// 等待多个事件对象有信号发送
ret = WSAWaitForMultipleEvents(pOverLapIO->g_iTotalConn, pOverLapIO->g_CliEventArr, FALSE, 1000, FALSE);
if (ret == WSA_WAIT_FAILED || ret == WSA_WAIT_TIMEOUT)
continue;

index = ret - WSA_WAIT_EVENT_0; // 获取索引值,此处 WSA_WAIT_EVENT_0 其实就等于0...

//////////////////////////////////////////////////////////////////////////
// 将事件对象设为无信号状态
if (FALSE == WSAResetEvent(pOverLapIO->g_CliEventArr[index]))
{
pOverLapIO->Cleanup(index);
continue;
}

//////////////////////////////////////////////////////////////////////////
// 获取重叠操作的结果,如果返回FALSE,表示重叠操作未完成
if (FALSE == WSAGetOverlappedResult(
pOverLapIO->g_pPerIODataArr[index]->sock,
&(pOverLapIO->g_pPerIODataArr[index]->overlap),
&(pOverLapIO->g_pPerIODataArr[index]->NumberOfBytesRecvd),
TRUE,
&(pOverLapIO->g_pPerIODataArr[index]->Flags)))
{
printf("WSAGetOverlappedResult failed:%d\n", WSAGetLastError());
continue;
}

//////////////////////////////////////////////////////////////////////////
// 接收到的重叠结果的字节数为0是,表示客户端断开连接
if (pOverLapIO->g_pPerIODataArr[index]->NumberOfBytesRecvd == 0)
{
printf("[%s:%d]->log off\n",
inet_ntoa(pOverLapIO->g_pPerIODataArr[index]->addr.sin_addr),
ntohs(pOverLapIO->g_pPerIODataArr[index]->addr.sin_port));
pOverLapIO->Cleanup(index);
}
else
{
//////////////////////////////////////////////////////////////////////////
// 有数据接收到,对数据进行处理(NetFunc),并再投递一个异步请求(WSARecv)
// 因为 WSARecv 只有一次生效的机会,如果我们在一次操作完之后,还有接下来的操作就还要继续投递一个WSARecv
pOverLapIO->NetFunc(pOverLapIO->g_pPerIODataArr[index]->sock,
pOverLapIO->g_pPerIODataArr[index]->szMessage,
pOverLapIO->g_pPerIODataArr[index]->NumberOfBytesRecvd);

ZeroMemory(pOverLapIO->g_pPerIODataArr[index]->szMessage,
sizeof(pOverLapIO->g_pPerIODataArr[index]->szMessage));
pOverLapIO->g_pPerIODataArr[index]->Flags = 0;

if (SOCKET_ERROR == WSARecv( //create new WSARecv
pOverLapIO->g_pPerIODataArr[index]->sock,
&(pOverLapIO->g_pPerIODataArr[index]->wsaBuf),
1,
&(pOverLapIO->g_pPerIODataArr[index]->NumberOfBytesRecvd),
&(pOverLapIO->g_pPerIODataArr[index]->Flags),
&(pOverLapIO->g_pPerIODataArr[index]->overlap),
NULL))
{
err = WSAGetLastError();
if (WSA_IO_PENDING != err) // WSA_IO_PENDING 重叠IO操作成功,等待稍后完成
{
HeapFree(GetProcessHeap(), 0, pOverLapIO->g_pPerIODataArr[index]);
printf("WSARecv failed:%d\n", err);
};
}
}
}
}


WSAWaitForMultipleEvents开始监听我们在接受套接字时绑定的网络事件对象是否有消息到来,如果客户端发来了数据并且事件对象有信号,此时必定是内核已经将数据拷贝到了我们WSABUF的缓冲区中(也就是PER_IO_OPERATION_DATA中的szMessage中),不必我们自己再去内核拷贝数据到用户空间中来。

接下来,我们对用户进行一些判断是否断线后,开始进行对数据进行处理,这里的处理是我们的处理函数指针NetFunc,函数指针的定义在头文件中有。

处理完数据后,我们再投递一次WSARecv。这样我们就完成了一次数据的收

WSASendToClient

/************************************************************************/
/* 进行消息发送的异步投递                                                 */
/************************************************************************/
bool OverlapIOModel::WSASendToClient(DWORD id, void* lparam)
{
char* buf = (char*)lparam;
int dwRet = 0;
WSABUF wsaBuf;
wsaBuf.len = strlen(buf);
wsaBuf.buf = buf;
DWORD dwSendBytes = 0;
DWORD Flags = 0;
WSAOVERLAPPED overlap;
overlap.hEvent = WSACreateEvent();

// 投递一个WSASend给操作系统,让它帮助我们完成消息的发送
if (SOCKET_ERROR == WSASend(id, &wsaBuf, 1, &dwSendBytes, Flags, &overlap, NULL))
{
dwRet = WSAGetLastError();
if (dwRet != WSA_IO_PENDING)
{
printf("WSASend failed:%d\n", dwRet);
return false;
}
}
WSACloseEvent(overlap.hEvent);// 对发送的消息没有事件的检测,所以清除掉内核资源

return true;
}


对数据进行处理之后,我们再通过WSASendToClient将数据处理之后结果发送给客户端,投递一个WSASend,让操作系统帮我们发送数据,同样是不占用自己程序的时间片,让用户程序做更多事情。

基于事件通知的重叠IO网络模型源码:http://pan.baidu.com/s/1o87OoHk
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息