单线程实现同时监听多个端口(windows平台c++代码)

前言网络

  多年前开发了一套网络库,底层实现采用IOCP(完成端口)。该库已在公司多个程序中应用;通过屡次修改,长时间检验,已经很是稳定高效。socket

最近把之前的代码梳理了一下,又加进了一些新的思路。代码结构更加合理,性能也有所提高。打算将该库一些的知识点写出来,以供参考。函数

服务端要在多个端口监听,这种场合并很少见。但做为一个完善的网络库,彷佛有必要支持此功能的。性能

传统实现方法优化

  若是监听端口个数不多,也能够采用传统的方法。由于accept函数是阻塞的,因此要实如今n个端口监听,就须要n个线程。若是监听端口个数很少,这也不是多大问题。若是监听端口多达几十个,这种方法就有些不妥。线程也是一种资源,线程过多占用资源会增长;也会致使系统负担加剧。spa

更可行的实现方法.net

  实现方法有些曲折,须要一步一步分析;基本的原理就是将socket句柄与事件(event)相关联。Windows有相关的函数能够对多个事件监听,当某个事件被触发,就知道相应的socket有事件到达。能够对该socket作accept,由于已经肯定该socket有事件了,因此accept函数会当即返回。这样就达到对多个端口同时监听的目的。线程

1)生成socket,并与某个端口绑定指针

struct LISTEN_SOCKET_INFO { UINT16 listenPort; //监听端口
    SOCKET   listenSocket;//句柄
    WSAEVENT netEvent;    //socket对应事件
}; int IocpAccept::CreateListenInfo() { //m_listListenPort存储要监听的端口;总个数不超过64个
    std::vector<UINT16>::iterator pos = m_listListenPort.begin(); for (;pos != m_listListenPort.end();++pos) { //生成socket
        UINT16 listenPort = *pos; LISTEN_SOCKET_INFO socketInfo; socketInfo.listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); socketInfo.listenPort = listenPort; //绑定端口
 sockaddr_in InetAddr; InetAddr.sin_family = AF_INET; InetAddr.sin_addr.s_addr = htonl(INADDR_ANY); InetAddr.sin_port = htons(listenPort); int ret = bind(socketInfo.listenSocket, (SOCKADDR *)&InetAddr, sizeof(InetAddr)); if (SOCKET_ERROR == ret) { ::closesocket(socketInfo.listenSocket); //绑定失败
            continue; } //生成事件
        socketInfo.netEvent = WSACreateEvent(); //将socket句柄与事件关联起来。只监视socket的accept和close消息
        ret = WSAEventSelect(socketInfo.listenSocket, socketInfo.netEvent, FD_ACCEPT | FD_CLOSE); if (SOCKET_ERROR == ret) { ::closesocket(socketInfo.listenSocket); continue; } // 启动监听
        ret = listen(socketInfo.listenSocket, 1000); if (SOCKET_ERROR == ret) { ::closesocket(socketInfo.listenSocket); continue; } m_listListenInfo.push_back(socketInfo); } return 0; }

该函数已将须要的数据存储在列表m_listListenInfo中。code

2)启动监听线程,对多个事件监听

  对多个事件监听用到以下函数:

DWORD WSAAPI WSAWaitForMultipleEvents( DWORD cEvents, const WSAEVENT *lphEvents, BOOL fWaitAll, DWORD dwTimeout, BOOL fAlertable );该函数最多能够对64个事件作跟踪,因此一个线程最多能够对64个端口作监听。(同时对超过64个端口监听的场合很是少见。本文不考虑。)

//生成事件地址指针
    int nEventTotal; WSAEVENT* pEventArray = CreateNetEventArray(&nEventTotal); if (nEventTotal == 0) return 0; assert(nEventTotal <= WSA_MAXIMUM_WAIT_EVENTS); MSG msg; while (m_bServerStart) { // 同时对多个事件作监听
        DWORD index = WSAWaitForMultipleEvents(nEventTotal, pEventArray, FALSE, 10000, FALSE); if (!m_bServerStart) return 0; //查看是哪一个事件触发函数返回
        index = index - WSA_WAIT_EVENT_0; //客户端链接事件
        if ((index != WSA_WAIT_FAILED) && (index != WSA_WAIT_TIMEOUT)) { //pEventArray排序与m_listListenInfo同样,因此能够根据index找到对应的socket。 //就是该socket致使函数返回
            LISTEN_SOCKET_INFO socketInfo = m_listListenInfo[index]; //查看具体是什么事件致使函数返回
 WSANETWORKEVENTS NetworkEvents; WSAEnumNetworkEvents(socketInfo.listenSocket, pEventArray[index], &NetworkEvents); //若是是accept事件,说明有客户端链接此端口
            if (NetworkEvents.lNetworkEvents == FD_ACCEPT && NetworkEvents.iErrorCode[FD_ACCEPT_BIT] == 0) { //这时调用accept函数,会当即返回
 AcceptListenPort(socketInfo.listenSocket, socketInfo.listenPort); } if (NetworkEvents.lNetworkEvents == FD_CLOSE && NetworkEvents.iErrorCode[FD_CLOSE_BIT] == 0) { assert(false); } } else { //由于超时等其余缘由引发函数返回
 } }

下文accept函数调用,并不会阻塞。

UINT IocpAccept::AcceptListenPort(SOCKET hListenSocket, UINT16 nListenPort) { SOCKET hClient = 0; SOCKADDR_IN localAddr; int iaddrSize = sizeof(SOCKADDR_IN); hClient = accept(hListenSocket, (struct sockaddr *)&localAddr, &iaddrSize); if (INVALID_SOCKET == hClient) { int nAccepetError = WSAGetLastError(); if (nAccepetError == WSAECONNRESET) { return 1; } else { return 0; } } else { //获取了一个客户端链接
 OnAcceptClient(hClient, nListenPort); } return 0; }

后记:同时对多个端口作监听,可能还有更好的方法。若是对几百个以上端口作监听,此方法可能就不太合适。一般状况下,对多个端口监听的场景比较少见,因此对更优化的处理方法也没深究。

代码下载地址: https://download.csdn.net/download/qq_29939347/10691921

相关文章
相关标签/搜索