【面试题】网络IO多路复用模型之异步事件

目录

异步事件模型的概念

工作流程:

WSAEventSelect模型的优势和不足

代码:


异步事件模型的概念

WSAEventSelect模型是WindowsSockets提供的另外一个有用的异步I/O模型。该模型允许一个或多个套接字上接收以事件为基础的网络事件通知。Windows Sockets应用程序在创建套接字后,调用WSAEventSlect()函数,将一个事件对象与网络事件集合关联在一起。当网络事件发生时,应用程序以事件的形式接收网络事件通知。

工作流程:

1.通过WSAEventSelect()函数 向windows注册

2.监测事件什么时候有信号WSAWaitForMultipleEvents ()

3.判断发生的是什么网络事件WSAEnumNetworkEvents()

4.根据发生的事件进行相应的处理

WSAEventSelect模型的优势和不足

优势:可以在一个非窗口的Windows Sockets程序中,实现多个套接字的管理。性能较优

不足:

1.每个WSAEventSelect模型最多只能管理64个套接字。当应用程序中需要管理多于64个套接字时,就需要额外创建线程。

2.由于使用该模型开发套接字应用程序需要调用几个相关函数才能完成。因此,该模型增加了开发的难度,增加了开发人员的编码量。从这个角度讲,该模型不如WSAAysnceSelect模型方便。

代码:

TCPServer.h

#ifndef TCPSERVER_H
#define TCPSERVER_H
#include <winsock2.h>
#include <stdio.h>
#include <stdlib.h>
#include <list>
#include <windows.h>
#include <map>
//#define MAXNUM  64
const int MAXNUM = 64;
 //socket----包大小、缓冲区、偏移量
struct SocketInfo{
    int nPackSize;
    char *pszbuf;
    int offset;
};
class TCPServer
{
public:
    TCPServer();
    ~TCPServer();
public:
    //1.初始化网络--加载、创建socket\bind\listen
    bool initNetWork(const char* szip = "127.0.0.1",unsigned short nport = 1234);
    void unInitNetWork(const char* szerr = "null"); //卸载网络
    bool sendData(SOCKET sockWaiter,const char* szbuf,int nlen); //向指定客户端发送数据
    void recvData(); //接收数据

    static DWORD WINAPI threadProc(LPVOID lpvoid);
private:
    SOCKET m_socklisten;
    std::list<HANDLE> m_lstThread;
    bool  m_bFlagQuit;
    std::map<DWORD,SOCKET> m_mapThreadIdToSocket;
    SOCKET m_arysocket[MAXNUM];
    HANDLE m_aryEvent[MAXNUM];
    int    m_nEventNum;
    std::map<SOCKET,SocketInfo*> m_mapSocketToInfo;
};

#endif // TCPSERVER_H

TCPServer.cpp

#include "tcpserver.h"

TCPServer::TCPServer()
{
    m_socklisten = 0;
    m_bFlagQuit = true;
    m_nEventNum = 0;
    ZeroMemory(m_aryEvent,sizeof(m_aryEvent));
    ZeroMemory(m_arysocket,sizeof(m_arysocket));
}

TCPServer::~TCPServer()
{

}

bool TCPServer::initNetWork(const char *szip, unsigned short nport)
{
    //1.选择种类  韩餐 火锅  串串香   川菜 -- 加载库
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;

    /* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
    wVersionRequested = MAKEWORD(2, 2);

    err = WSAStartup(wVersionRequested, &wsaData);
    if (err != 0) {
        /* Tell the user that we could not find a usable */
        /* Winsock DLL.                                  */
        printf("WSAStartup failed with error: %d\n", err);
        return false;
    }

    /* Confirm that the WinSock DLL supports 2.2.*/
    /* Note that if the DLL supports versions greater    */
    /* than 2.2 in addition to 2.2, it will still return */
    /* 2.2 in wVersion since that is the version we      */
    /* requested.                                        */

    if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
        /* Tell the user that we could not find a usable */
        /* WinSock DLL.                                  */
        printf("Could not find a usable version of Winsock.dll\n");
        unInitNetWork();
        return false;
    }
    else
        printf("The Winsock 2.2 dll was found okay\n");
    //2.雇人-店长--
    m_socklisten = socket(AF_INET,SOCK_STREAM,0);
    if(m_socklisten == INVALID_SOCKET){
        unInitNetWork("socket err");
        return false;
    }
    //3.选择地址--
    sockaddr_in addrserver;
    addrserver.sin_family = AF_INET;
    addrserver.sin_addr.S_un.S_addr = inet_addr(szip);
    addrserver.sin_port = htons(nport);
    if( SOCKET_ERROR == bind(m_socklisten,(sockaddr*)&addrserver,sizeof(addrserver))){
        unInitNetWork("bind err");
        return false;
    }
    //4.宣传--
    if( SOCKET_ERROR == listen(m_socklisten,1000)){ //  1_1_1_1______
        unInitNetWork("listen err");
        return false;
    }
    HANDLE  hevent = WSACreateEvent(); //默认人工 匿名无信号事件
    if(0 ==WSAEventSelect(m_socklisten,hevent,FD_ACCEPT)){
        //注册成功
        m_arysocket[m_nEventNum] = m_socklisten;
        m_aryEvent[m_nEventNum] = hevent;
        ++m_nEventNum;
    }

//    //创建接收连接的线程
    HANDLE hThread = CreateThread(0,0,&threadProc,this,0,0);
    if(hThread)
        m_lstThread.push_back(hThread);
    return true;
}


DWORD TCPServer::threadProc(LPVOID lpvoid)
{
    TCPServer *pthis = (TCPServer*)lpvoid;
    DWORD dwIndex;
    WSANETWORKEVENTS wwe;
    sockaddr_in addrclient;
    int nsize = sizeof(addrclient);
    while(pthis->m_bFlagQuit){
         //等事件,判断哪个事件有信号
        dwIndex =WSAWaitForMultipleEvents(pthis->m_nEventNum, //事件的个数
                                 pthis->m_aryEvent, //监听事件的数组
                                 FALSE,//任意一个有信号就返回
                                 WSA_INFINITE,//等待时间
                                 0
                                 );
        dwIndex -=WSA_WAIT_EVENT_0;
         //判断发生什么事了
        if(WSAEnumNetworkEvents(pthis->m_arysocket[dwIndex],
                             pthis->m_aryEvent[dwIndex],
                                 &wwe))
            continue;
        // 处理
        if(wwe.lNetworkEvents & FD_ACCEPT){

            printf("wait client connect......\n");
            SOCKET sockWaiter = accept(pthis->m_socklisten,(sockaddr*)&addrclient,&nsize);
            printf("client ip:%s port:%d\n",inet_ntoa(addrclient.sin_addr),addrclient.sin_port );
            //向windows注册
            HANDLE  hEvent = WSACreateEvent();
            if( 0==WSAEventSelect(sockWaiter,hEvent,FD_READ|FD_CLOSE)){
                pthis->m_aryEvent[pthis->m_nEventNum] =hEvent;
                pthis->m_arysocket[pthis->m_nEventNum] = sockWaiter;
                ++pthis->m_nEventNum;
            }
        }
        if(wwe.lNetworkEvents & FD_READ){

            SocketInfo *p =   pthis->m_mapSocketToInfo[pthis->m_arysocket[dwIndex]];
            if(p == NULL){
                p = new SocketInfo;
                p->nPackSize =0;
                p->offset =0;
                p->pszbuf = NULL;
                 //接收包大小
                int nReadNum = recv(pthis->m_arysocket[dwIndex],(char*)&p->nPackSize,sizeof(int),0);
                if(nReadNum >0){
                    p->pszbuf = new char[p->nPackSize];

                }
                pthis->m_mapSocketToInfo[pthis->m_arysocket[dwIndex]] = p;

            }else{
                  //接收包数据
                int nReadNum = recv(pthis->m_arysocket[dwIndex],p->pszbuf+p->offset,p->nPackSize,0);
                if(nReadNum>0){
                    p->offset+=nReadNum;
                    p->nPackSize-=nReadNum;
                    if(p->nPackSize ==0){
                        //todo
                        printf("client say:%s\n",p->pszbuf);
                        delete []p->pszbuf;
                        pthis->m_mapSocketToInfo[pthis->m_arysocket[dwIndex]] = NULL;
                    }
                }

            }




        }

        if(wwe.lNetworkEvents & FD_CLOSE){

            closesocket(pthis->m_arysocket[dwIndex]);
            WSACloseEvent(pthis->m_aryEvent[dwIndex]);
            if(pthis->m_nEventNum >1){
                pthis->m_aryEvent[dwIndex] = pthis->m_aryEvent[pthis->m_nEventNum-1];
                pthis->m_arysocket[dwIndex] = pthis->m_arysocket[pthis->m_nEventNum-1];

            }
            --pthis->m_nEventNum;
        }

    }
    return 0;
}






void TCPServer::unInitNetWork(const char* szerr )
{
    printf(szerr);
    if(m_socklisten){
        closesocket(m_socklisten);
        m_socklisten = 0;
    }
    WSACleanup();
    //结束所有的线程
    m_bFlagQuit = false;
    auto ite = m_lstThread.begin();
    while(ite !=m_lstThread.end()){
        //判断线程状态
        if(WAIT_TIMEOUT == WaitForSingleObject(*ite,100))
            TerminateThread(*ite,-1);

        CloseHandle(*ite);
        *ite = NULL;
        ++ite;
    }

    m_lstThread.clear();
}

bool TCPServer::sendData(SOCKET sockWaiter, const char *szbuf, int nlen)
{
    //发送的包大小
    if(send(sockWaiter,(const char*)&nlen,sizeof(int),0) <=0)
        return false;
    //发送包内容
    if(send(sockWaiter,szbuf,nlen,0) <=0)
        return false;
    return true;
}





  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱编程的小猴

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值