单线程实现同时监听多个端口(windows平台c++代码)
前言
多年前开发了一套网络库,底层实现采用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++代码)的更多相关文章
- Memcached监听多个端口_同一台Windows机器中启动多个Memcached服务
下载Memcached服务器 假设你解压在"C:\Program Files\memcached\memcached.exe" 那么可以如下创建多个服务监听不同的端口啦 监听第一个 ...
- wriesharek同时监听多个端口
之前的文章<wireshark解析自定义的protobuf协议> ,当时只监听了一个端口,而如果游戏同时有二个 socket 连接,比如一个是网关另外一个是其它的,怎么办呢? for i, ...
- linux下启动多个php,分别监听不同的端口。
在工作中,我们可能会遇到,服务器集群的搭建. 这个时候,我们不可能,每一台服务器都是lnmp的环境,我们会把nmp分别放在不同的服务器上,不同的服务器负责不同的功能.比如我们下面要说的php 加入ng ...
- prometheus 监控 jar应用服务 + 修改监听IP和端口
1.修改服务的启动脚本 [root@do1cloud01 init.d]# vim learn-school nohup ${JAVA_HOME}/bin/java -javaagent:/usr/l ...
- apache反向代理和监听多个端口设置
修改apache配置文件httpd.conf 一.监听多个端口 在Listen 80后添加监听端口,如 Listen 80 Listen 8080 Listen 8008 二.反向代理设置 1.取消一 ...
- 同一台机器oralce11g和12c公用一个监听器监听多个端口
启动数据库服务 (这里还没有启动监听器) 如上图OracleServiceORCL为11g的服务,服务名为orcl,OracleServiceWX为12c的服务名,服务名为wx,两个服务均已正常启动, ...
- ssh 监听多个端口
修改sshd的配置文件 默认位置:/etc/ssh/sshd_config 注释掉 Port 这行 然后添加 ListenAddress 行 e.g: ListenAddress 192.168 ...
- CentOS7 配置 SSH监听多个端口方法
一.修改ssh默认端口,防止暴力破解,让系统安全多一点点: i. 在配置文件/etc/ssh/sshd_config文件中修改 Port #AddressFamily any #ListenAddre ...
- 本地启动服务,两个进程分别监听两个端口,导致两个 URL 不同
问题描述: 本地启了两个服务:A(http://localhost:8001) B(http://localhost:8000),A 项目要怎么才能关联到 B 项目,也就是 A 项目请求怎么跳到 B ...
随机推荐
- JAVA-部署-摘
一.下载 J2SDK下载http://java.sun.com/j2se/1.4.2/download.html 下载版本是j2sdk-1_4_2_08 ECLIPSE下载http://www.ec ...
- day32(表单校验js和jquery表单校验)
校验用户名.密码.密码一直性. <style> .error { color: red } .success { color: green } </style> <scr ...
- day18(javaEE三大组件之一servlet(简介(一)))
Servlet servlet是小型服务器语言,使用它可以处理前台传递来的信息,servlet进行处理后在响应给前台,其中servlet起到了关键性的作用.前端输入的信息可以持久化的存储在数据库中,并 ...
- _编程语言_C++_setw()
C++ 中使用setw(int n) 来控制输出间隔. 例如: cout<<)<<'a'<<endl;//s与a之间有7个空格,setw()只对后面紧跟的输出产生作 ...
- 第83讲:Scala中List的实现内幕源码揭秘
今天我们来学习一下scala的List的方法的内部源码的一些知识. 首先,take方法.take方法就是取列表的从第一个元素开始的前N个元素.如list.take(3),就是取list的前3个元素,返 ...
- CSS 基础 例子 display属性:block、inline和inline-block的区别
HTML中块级元素(block)和行级元素(inline):比如div就是常见的块级元素,span就是常见的行级元素. 可以通过css的display属性来设置一个元素到底是块级,还是行级元素:dis ...
- Winform文件上传
近期在做了一个winform的项目的附件上传的需求 最初项目选型的时候有三种 1.使用webservice.webapi上传文件 2,直接保存在数据库中 3.使用共享目录+dos命令 第一种有文件大小 ...
- 《AngularJS深度剖析与最佳实践》笔记: 第二章 概念介绍
第二章 概念介绍 2.1 什么是UI? 用户界面包括内容(静态信息+动态信息), 外观, 交互. 在前端技术栈中分别由HTML, CSS和JS负责. 进一步抽象, 分别对应于MVC三个主要部分: Mo ...
- Android RelativeLayout wrap_content 而且 child view 使用 layout_alignParentBottom 时 RelativeLayout 高度会占满屏幕
Android RelativeLayout wrap_content 而且 child view 使用 layout_alignParentBottom 时 RelativeLayout 高度会占满 ...
- C/C++掌握技能(一)
1.在编译器中输入代码并将其保存为.cpp文件(C语言的文件扩展名.c,但为了使用C++中的一些好用的特性,请把文件扩展名改为C++的.cpp)2.等价头文件:#include<stdio.h& ...