Abstract Keywords Citation Yao Qing-sheng.单线程实现同时监听多个端口(Windows 平台 C++ 代码).FUTURE & CIVILIZATION Natural/Social Philosophy & Infomation Sciences,20240808. https://yaoqs.github.io/20240808/dan-xian-cheng-shi-xian-tong-shi-jian-ting-duo-ge-duan-kou-windows-ping-tai-c-dai-ma/ 转载自 单线程实现同时监听多个端口(windows 平台 c++ 代码)
前言 多年前开发了一套网络库,底层实现采用 IOCP(完成端口)。该库已在公司多个程序中应用;经过多次修改,长时间检验,已经非常稳定高效。
最近把以前的代码梳理了一下,又加进了一些新的思路。代码结构更加合理,性能也有所提升。打算将该库一些的知识点写出来,以供参考。
服务端要在多个端口监听,这种场合并不多见。但作为一个完善的网络库,似乎有必要支持此功能的。
传统实现方法 如果监听端口个数很少,也可以采用传统的方法。因为 accept 函数是阻塞的,所以要实现在 n 个端口监听,就需要 n 个线程。如果监听端口个数不多,这也不是多大问题。如果监听端口多达几十个,这种方法就有些不妥。线程也是一种资源,线程过多占用资源会增加;也会导致系统负担加重。
更可行的实现方法 实现方法有些曲折,需要一步一步分析;基本的原理就是将 socket 句柄与事件(event)相关联。Windows 有相关的函数可以对多个事件监听,当某个事件被触发,就知道相应的 socket 有事件到达。可以对该 socket 做 accept,因为已经确定该 socket 有事件了,所以 accept 函数会立即返回。这样就达到对多个端口同时监听的目的。
生成 socket,并与某个端口绑定 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 struct LISTEN_SOCKET_INFO { UINT16 listenPort; SOCKET listenSocket; WSAEVENT netEvent; }; int IocpAccept::CreateListenInfo () { std::vector<UINT16>::iterator pos = m_listListenPort.begin (); for (;pos != m_listListenPort.end ();++pos) { 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 (); 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 中。
启动监听线程,对多个事件监听 对多个事件监听用到如下函数:
DWORD WSAAPI WSAWaitForMultipleEvents( DWORD cEvents, const WSAEVENT *lphEvents, BOOL fWaitAll, DWORD dwTimeout, BOOL fAlertable );
该函数最多可以对 64 个事件做跟踪,所以一个线程最多可以对 64 个端口做监听。(同时对超过 64 个端口监听的场合非常少见。本文不考虑。)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 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)) { LISTEN_SOCKET_INFO socketInfo = m_listListenInfo[index]; WSANETWORKEVENTS NetworkEvents; WSAEnumNetworkEvents (socketInfo.listenSocket, pEventArray[index], &NetworkEvents); if (NetworkEvents.lNetworkEvents == FD_ACCEPT && NetworkEvents.iErrorCode[FD_ACCEPT_BIT] == 0 ) { AcceptListenPort (socketInfo.listenSocket, socketInfo.listenPort); } if (NetworkEvents.lNetworkEvents == FD_CLOSE && NetworkEvents.iErrorCode[FD_CLOSE_BIT] == 0 ) { assert (false ); } } else { } }
下文 accept 函数调用,并不会阻塞。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 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
转载或引用本文时请遵守许可协议,注明出处、不得用于商业用途!