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

来源:互联网 发布:c 高级编程 第7版 pdf 编辑:程序博客网 时间:2024/05/22 03:08

这里就补充一个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_port));        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

原创粉丝点击