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

本文介绍了一个基于事件通知的重叠IO网络模型实现,详细解释了如何使用WSAGetOverlappedResult等API来处理异步I/O操作,包括客户端连接处理、数据接收和发送流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值