前言

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

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

服务端要在多个端口监听,这种场合并不多见。但作为一个完善的网络库,似乎有必要支持此功能的。

传统实现方法

  如果监听端口个数很少,也可以采用传统的方法。因为accept函数是阻塞的,所以要实现在n个端口监听,就需要n个线程。如果监听端口个数不多,这也不是多大问题。如果监听端口多达几十个,这种方法就有些不妥。线程也是一种资源,线程过多占用资源会增加;也会导致系统负担加重。

更可行的实现方法

  实现方法有些曲折,需要一步一步分析;基本的原理就是将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, );
if (SOCKET_ERROR == ret)
{
::closesocket(socketInfo.listenSocket);
continue;
} m_listListenInfo.push_back(socketInfo);
}
return ;
}

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

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 == )
return ;
assert(nEventTotal <= WSA_MAXIMUM_WAIT_EVENTS); MSG msg;
while (m_bServerStart)
{
// 同时对多个事件做监听
DWORD index = WSAWaitForMultipleEvents(nEventTotal,
pEventArray,
FALSE,
,
FALSE);
if (!m_bServerStart)
return ; //查看是哪个事件触发函数返回
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] == )
{
//这时调用accept函数,会立即返回
AcceptListenPort(socketInfo.listenSocket, socketInfo.listenPort);
}
if (NetworkEvents.lNetworkEvents == FD_CLOSE
&& NetworkEvents.iErrorCode[FD_CLOSE_BIT] == )
{
assert(false);
} }
else
{
//因为超时等其他原因引起函数返回
}
}

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

UINT IocpAccept::AcceptListenPort(SOCKET hListenSocket, UINT16 nListenPort)
{
SOCKET hClient = ;
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 ;
}
else
{
return ;
}
}
else
{
//获取了一个客户端连接
OnAcceptClient(hClient, nListenPort);
}
return ;
}

后记:同时对多个端口做监听,可能还有更好的方法。如果对几百个以上端口做监听,此方法可能就不太合适。通常情况下,对多个端口监听的场景比较少见,所以对更优化的处理方法也没深究。

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

单线程实现同时监听多个端口(windows平台c++代码)的更多相关文章

  1. Memcached监听多个端口_同一台Windows机器中启动多个Memcached服务

    下载Memcached服务器 假设你解压在"C:\Program Files\memcached\memcached.exe" 那么可以如下创建多个服务监听不同的端口啦 监听第一个 ...

  2. wriesharek同时监听多个端口

    之前的文章<wireshark解析自定义的protobuf协议> ,当时只监听了一个端口,而如果游戏同时有二个 socket 连接,比如一个是网关另外一个是其它的,怎么办呢? for i, ...

  3. linux下启动多个php,分别监听不同的端口。

    在工作中,我们可能会遇到,服务器集群的搭建. 这个时候,我们不可能,每一台服务器都是lnmp的环境,我们会把nmp分别放在不同的服务器上,不同的服务器负责不同的功能.比如我们下面要说的php 加入ng ...

  4. prometheus 监控 jar应用服务 + 修改监听IP和端口

    1.修改服务的启动脚本 [root@do1cloud01 init.d]# vim learn-school nohup ${JAVA_HOME}/bin/java -javaagent:/usr/l ...

  5. apache反向代理和监听多个端口设置

    修改apache配置文件httpd.conf 一.监听多个端口 在Listen 80后添加监听端口,如 Listen 80 Listen 8080 Listen 8008 二.反向代理设置 1.取消一 ...

  6. 同一台机器oralce11g和12c公用一个监听器监听多个端口

    启动数据库服务 (这里还没有启动监听器) 如上图OracleServiceORCL为11g的服务,服务名为orcl,OracleServiceWX为12c的服务名,服务名为wx,两个服务均已正常启动, ...

  7. ssh 监听多个端口

    修改sshd的配置文件 默认位置:/etc/ssh/sshd_config 注释掉 Port 这行   然后添加 ListenAddress 行 e.g:  ListenAddress 192.168 ...

  8. CentOS7 配置 SSH监听多个端口方法

    一.修改ssh默认端口,防止暴力破解,让系统安全多一点点: i. 在配置文件/etc/ssh/sshd_config文件中修改 Port #AddressFamily any #ListenAddre ...

  9. 本地启动服务,两个进程分别监听两个端口,导致两个 URL 不同

    问题描述: 本地启了两个服务:A(http://localhost:8001) B(http://localhost:8000),A 项目要怎么才能关联到 B 项目,也就是 A 项目请求怎么跳到 B ...

随机推荐

  1. JS 对象(Object)和字符串(String)互转方法

    利用原生JSON对象,将对象转为字符串 1 2 3 4 5 6 var jsObj = {}; jsObj.testArray = [1,2,3,4,5]; jsObj.name = 'CSS3'; ...

  2. shell工具-sort

    sort sort命令是在Linux里非常有用,它将文件进行排序,并将排序结果标准输出 基本语法 sort [选项] [参数] 选项说明 选项 说明 -n 依照数值大小排序 -r 以相反的顺序排序 - ...

  3. 修复msvcp120.dll

    https://www.microsoft.com/zh-CN/download/details.aspx?id=40784

  4. Visualise the Argyris basis functions

    """ Author: kinnala Visualise the Argyris basis functions. """ from sk ...

  5. 《javascript高级程序设计》 touch事件的一个小错误

    最近一段时候都在拜读尼古拉斯大神的<javascript高级程序设计>,真的是一本好书,通俗易懂,条理比<javascript权威指南>好理解一些,当然<javascri ...

  6. 由异常掉电问题---谈xfs文件系统

    由异常掉电问题---谈xfs文件系统 本文皆是作者自己的学习总结或感悟(linux环境),如有不对,欢迎提出一起探讨!! 目录结构 一.相关知识 二.问题提出 三.处理方法 四.最终结果 一.相关知识 ...

  7. 如何利用JUnit开展一个简单的单元测试(测试控制台输出是否正确)

    待测类(CreateString)如下: public class CreateString { public void createString() { //Output the following ...

  8. Windows核心编程:第14章 探索虚拟内存

    Github https://github.com/gongluck/Windows-Core-Program.git //第14章 探索虚拟内存.cpp: 定义应用程序的入口点. // #inclu ...

  9. 记录一次错误处理 (xml序列化和反序列化相关)

    XML序列化后,反序列化时出现错误 报错现象 System.InvalidOperationException: XML 文档(40, 11)中有错误. ---> System.Xml.XmlE ...

  10. Python3.5 学习二十二

    回顾: 发送请求时:发送请求头和请求数据 request.META和request.request.body 响应请求时:响应头和响应返回数据 response.HEADER和response.bod ...