C++网络编程之IO多路复用(一)
概述
在C++网络编程中,处理并发连接是一个非常关键的核心问题。为了有效管理来自多个客户端的请求,服务器需要能够同时监听多个套接字上的事件,这通常通过IO多路复用来实现。
IO多路复用是一种工作机制,它可以让程序监视多个文件描述符(通常是套接字),等待其中一个或多个文件描述符变为就绪状态。一旦某个文件描述符就绪,即该文件描述符上可以进行无阻塞读写操作,操作系统就会通知应用程序。然后,应用程序就可以对该文件描述符进行相应的读写操作。
使用IO多路复用后,不需要为每个连接创建一个独立的线程,节省了资源。同时,也避免了频繁的上下文切换开销,提高了效率。常见的IO多路复用技术主要有三种,分别为:select、poll、epoll。在本篇中,我们将重点介绍select,后续文章将介绍poll和epoll。
select
select是最古老的IO多路复用API,几乎支持所有类型的Unix系统(包括:Windows、Linux、Mac等)。它的基本工作原理是:用户态进程将一组文件描述符传递给内核,由内核来检查这些文件描述符的状态变化;当调用返回时,会告诉用户哪些文件描述符已经准备好了读写操作。
select函数的接口原型如下。
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
各个参数和返回值的含义如下。
nfds:指定要监视的最大文件描述符值加1。
readfds:指向一个集合,该集合包含了想要监视读就绪状态的文件描述符。
writefds:指向一个集合,该集合包含了想要监视写就绪状态的文件描述符。
exceptfds:指向一个集合,该集合包含了想要监视异常条件的文件描述符。
timeout:一个时间结构体,指定了等待超时的时间。如果设置为NULL,则会一直阻塞,直到至少有一个文件描述符准备好。如果设置了具体时间,则在指定时间内没有事件发生将返回0。
返回值:如果返回正数,这意味着至少有一个文件描述符已经准备好执行请求的操作(读、写或异常条件)。返回的具体数值,表示有多少个文件描述符已就绪。如果返回 0,这意味着在指定的超时时间内没有任何文件描述符准备好,并且超时时间已过。如果返回负数,这意味着调用过程中发生了错误。常见的原因包括传入了无效的参数或者系统本身遇到了问题。
在Windows系统和Linux系统下使用select函数时,它们之间存在一些细微的差异,具体如下。
1、头文件和库不同。
Windows:需要包含winsock2.h头文件,且链接到Winsock库ws2_32.lib。
Linux:需要包含sys/select.h头文件,不需要链接额外的库,因为select是标准POSIX库的一部分。
2、类型不同。
Windows:Socket类型为套接字句柄。
Linux:Socket类型为文件描述符,这是所有IO操作的基础。
3、FD_SETSIZE的定义不同。
Windows:默认的FD_SETSIZE是64。
Linux:默认的FD_SETSIZE是1024。
4、错误码不同。
Windows:返回SOCKET_ERROR,表示发生了错误。可通过WSAGetLastError函数获取更具体的错误值,比如:WSAEINTR(被信号中断)、WSAEINVAL(无效参数)等。
Linux:返回-1,表示发生了错误。可通过errno获取更具体的错误值,比如:EINTR(被信号中断)、EINVAL(无效参数)等。
注意:select只能处理有限数量的文件描述符,通常这个数量由上面介绍的FD_SETSIZE定义。当监视大量的文件描述符时,select的性能会显著下降。每次调用select都需要复制所有文件描述符集合到内核空间,然后在内核中进行线性扫描。这种机制对于少量的文件描述符是可行的,但对于大规模并发应用则效率低下。
实战代码
在Windows系统下如何使用select进行IO多路复用,可参考下面的TCP服务器的示例代码。
首先,我们使用WSAStartup初始化Winsock库,并检查是否成功。接着,创建一个监听套接字,将其绑定到指定的端口8888,并调用listen开始监听连接请求。
然后,我们使用fd_set类型的变量masterSet来存储所有需要监控的套接字,包括:监听套接字、所有已连接的客户端套接字;workingSet则用于临时存储select检查的结果。
紧接着,在无限循环中,我们使用select函数来等待IO事件的发生。如果select返回新的连接请求,接受新连接,并将新连接的套接字添加到masterSet和activeSockets向量中。对于每个已连接的客户端套接字,检查是否有数据可读。如果有,则读取数据并回显给客户端。如果客户端断开连接,则关闭相应的套接字,并从masterSet和activeSockets中移除该套接字。
最后,当程序退出时,关闭所有打开的套接字,并清理Winsock库。
#include <iostream>
#include <winsock2.h>
#include <vector>
using namespace std;#pragma comment(lib, "ws2_32.lib")#define TCP_LISTEN_PORT 8888
#define BUFFER_SIZE 1024int main()
{// 初始化Winsock库WSADATA wsaData;int result = WSAStartup(MAKEWORD(2, 2), &wsaData);if (result != 0){cout << "WSAStartup failed: " << result << endl;return 1;}SOCKET listenSocket = INVALID_SOCKET;struct sockaddr_in serverAddr;// 创建监听 socketlistenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (listenSocket == INVALID_SOCKET){cout << "Socket creation failed: " << WSAGetLastError() << endl;WSACleanup();return 1;}// 设置服务器地址serverAddr.sin_family = AF_INET;serverAddr.sin_addr.s_addr = INADDR_ANY;serverAddr.sin_port = htons(TCP_LISTEN_PORT);if (bind(listenSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR){cout << "Bind failed: " << WSAGetLastError() << endl;closesocket(listenSocket);WSACleanup();return 1;}// 开始监听if (listen(listenSocket, SOMAXCONN) == SOCKET_ERROR){cout << "Listen failed: " << WSAGetLastError() << endl;closesocket(listenSocket);WSACleanup();return 1;}cout << "Server listening on port " << TCP_LISTEN_PORT << endl;fd_set masterSet;FD_ZERO(&masterSet);FD_SET(listenSocket, &masterSet);fd_set workingSet;// 存储新连接的套接字句柄vector<SOCKET> activeSockets;activeSockets.push_back(listenSocket);while (true){workingSet = masterSet;// 调用selectint selectResult = select(0, &workingSet, NULL, NULL, NULL);if (selectResult == SOCKET_ERROR){cout << "Select failed: " << WSAGetLastError() << endl;break;}// 检查是否有新的连接请求if (FD_ISSET(listenSocket, &workingSet)){SOCKET clientSocket = accept(listenSocket, NULL, NULL);if (clientSocket == INVALID_SOCKET){cout << "Accept failed: " << WSAGetLastError() << endl;continue;}cout << "New connection: " << clientSocket << endl;FD_SET(clientSocket, &masterSet);activeSockets.push_back(clientSocket);}// 检查其他已连接的客户端是否有数据可读for (const auto &sock : activeSockets){// 跳过监听Socketif (sock == listenSocket){continue;}if (FD_ISSET(sock, &workingSet)){char buffer[BUFFER_SIZE] = { 0 };int bytesReceived = recv(sock, buffer, BUFFER_SIZE, 0);if (bytesReceived > 0){cout << "Received from client " << sock << ": " << buffer << endl;// 回显消息给客户端send(sock, buffer, bytesReceived, 0);}else if (bytesReceived == 0){cout << "Client disconnected: " << sock << endl;closesocket(sock);FD_CLR(sock, &masterSet);// 移除断开的连接auto it = find(activeSockets.begin(), activeSockets.end(), sock);if (it != activeSockets.end()){activeSockets.erase(it);}}else{cout << "Recv failed: " << WSAGetLastError() << endl;closesocket(sock);FD_CLR(sock, &masterSet);// 移除断开的连接auto it = find(activeSockets.begin(), activeSockets.end(), sock);if (it != activeSockets.end()){activeSockets.erase(it);}}}}}// 释放资源for (const auto &sock : activeSockets){closesocket(sock);}closesocket(listenSocket);WSACleanup();return 0;
}
相关文章:
C++网络编程之IO多路复用(一)
概述 在C网络编程中,处理并发连接是一个非常关键的核心问题。为了有效管理来自多个客户端的请求,服务器需要能够同时监听多个套接字上的事件,这通常通过IO多路复用来实现。 IO多路复用是一种工作机制,它可以让程序监视多个文件描述…...
vscode在windows和linux如何使用cmake构建项目并make生成可执行文件,两者有什么区别
vscode在windows和linux如何使用cmake构建项目并make生成可执行文件,两者有什么区别 windows默认使用的是最新的visual studio,而linux默认就是cmake 文章目录 vscode在windows和linux如何使用cmake构建项目并make生成可执行文件,两者有什么…...
Antd Vue中使用table组件把相同名称的合并单元格---只需两步
当前效果: 想要的效果: 第一步:在获取table数据的地方处理数据 function getTableList () {getDataList().then(res > {if (res.code 200 && res.data) {const list res.datalet columnIndex 0 //第一条数据let rowSpan …...
cmake中execute_process详解
execute_process 是 CMake 中一个非常强大的命令,用于在构建过程中执行外部程序或脚本。它提供了丰富的选项来控制执行过程,并可以捕获输出、错误和返回码。以下是 execute_process 的详细解析: 基本语法 execute_process(COMMAND <comm…...
搜维尔科技:使用Sensglove Nova2触觉反馈手套遥操作机器人操作
使用Sensglove Nova2触觉反馈手套遥操作机器人操作 搜维尔科技:使用Sensglove Nova2触觉反馈手套遥操作机器人操作...
企业HR如何选对一款智能招聘软件?
随着招聘市场的竞争加剧和求职者期望的提升,传统的招聘方式已经难以满足企业的需求。智能招聘软件应运而生,成为企业HR提升招聘效率、优化招聘流程、增强雇主品牌吸引力的关键工具。然而,市场上的智能招聘软件琳琅满目,如何选择一…...
任务中心全新升级,新增分享接口文档功能,MeterSphere开源持续测试工具v3.4版本发布
2024年11月5日,MeterSphere开源持续测试工具正式发布v3.4版本。 在这一版本中,系统设置方面,任务中心支持实时查看系统即时任务与系统后台任务;接口测试方面,新增接口文档分享功能、接口场景导入导出功能,…...
书生大模型第三关Git 基础知识
关卡编号:L0G3000 任务一 破冰行动 fork仓库,注意这里不要勾选Copy branch Only!!!,因为后面课程中会使用到class分支: 克隆仓库: 移动分支: 创建自己的分支: 创建id.md文档,…...
WordPress 中最佳的维护服务:入门级用户指南
如果你是WordPress网站管理员,一定知道网站维护既耗时又复杂。然而,保持网站的正常运行和安全却至关重要。为了让你轻松应对这个挑战,我们总结了一些适合新手和小型网站的维护服务。本文将介绍两款适合初学者的维护服务:FixMySite…...
前端使用Luckysheet把返回的base64或二进制文件流格式,实现xlsx文件预览
xlsx文件预览 Luckysheet是什么?代码实现xlsx文件预览引入luckysheet的相关依赖安装luckyexcel指定一个表格容器实现逻辑 Luckysheet是什么? Luckysheet ,一款纯前端类似excel的在线表格,功能强大、配置简单、完全开源。 Luckys…...
腾讯混元宣布大语言模型和3D模型正式开源
腾讯混元大模型正在加快开源步伐。 11月5日,腾讯混元宣布最新的MoE模型“混元Large“以及混元3D生成大模型“ Hunyuan3D-1.0”正式开源,支持企业及开发者精调、部署等不同场景的使用需求,可在HuggingFace、Github等技术社区直接下载ÿ…...
提示工程指南 笔记
诸神缄默不语-个人CSDN博文目录 课程网站:提示工程指南 | Prompt Engineering Guide 原版是英文:https://www.promptingguide.ai/ 特别基础的内容我就不写了,只写一些值得记录的内容。 文章目录 1. 常用术语(LLM特供版ÿ…...
WordPress站点网站名称、logo设置
WordPress网站名称设置 后台打开查看站点自定义设置 点击网站名称修改 上传logo和站点图标...
本地缓存与 Redis:为什么我们仍然需要本地缓存?
文章目录 本地缓存与 Redis:为何仍需本地缓存?为什么需要本地缓存?多级缓存架构多级缓存的实现 本地缓存的实现方式使用 cachetools 实现 LRUCache使用 diskcache 实现持久化缓存 缓存装饰器实现进一步优化:缓存失效与更新 小结 好…...
要在微信小程序中让一个 `view` 元素内部的文字水平垂直居中,可以使用 Flexbox 布局
文章目录 主要特点:基本用法:常用属性: 要在微信小程序中让一个 view 元素内部的文字水平垂直居中,可以使用 Flexbox 布局。以下是如何设置样式的示例: .scan-button {display: flex; /* 启用 Flexbox 布局 */justify…...
图像超分辨率、DPSRGAN
图像超分辨率(Image Super-Resolution, ISR)是一种通过增加图像的分辨率来提高其细节和清晰度的技术。这项技术在多个领域都有广泛的应用,比如视频监控、医学诊断、遥感应用等。根据搜索结果,图像超分辨率算法主要可以分为以下几类…...
124.WEB渗透测试-信息收集-ARL(15)
免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动! 内容参考于: 易锦网校会员专享课 上一个内容:123.WEB渗透测试-信息收集-ARL(14) 点击fofa任务下发(…...
@Async注解提升Spring Boot项目中API接口并发能力
文章目录 同步调用异步调用1: 启用异步支持2: 修改 Task 类异步回调基本概念使用 Future<String>使用 CompletableFuture<String>Future<String> 和 CompletableFuture<String>区别1. 基本概念2. 主要区别同步调用 同步调用是最直接的调用方式,调用方…...
SpringBoot集成Flink-CDC
Flink CDC CDC相关介绍 CDC是什么? CDC是Change Data Capture(变更数据获取)的简称。核心思想是,监测并捕获数据库的变动(包括数据或数据表的插入、更新以及删除等),将这些变更按发生的顺序完整记录下来,写入到MQ以…...
SQL报错注入检测方法与攻击方法
报错注入 即是注入检测方法,又是注入读取数据的方法 攻击者在判断一个参数是否存在SQL注入漏洞时,会拼接单引号,反斜杠字符,如果显示语法报错,证明这个位置具有SQL注入漏洞,也可以通过整数溢出来判断&…...
FastAPI JWT刷新令牌:安全存储的完整指南
FastAPI JWT刷新令牌:安全存储的完整指南 【免费下载链接】fastapi FastAPI framework, high performance, easy to learn, fast to code, ready for production 项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi 在前100个字内,FastAP…...
AI智能体开发全解析:从需求到部署,打造下一代智能应用!
AI智能体(AI Agent)的开发流程已从传统的软件开发生命周期(SDLC)演进为智能体开发生命周期(ADLC, Agentic Development Lifecycle)。其核心逻辑不再是编写确定的逻辑代码,而是构建具备感知、规划…...
Docker+宝塔双方案:Nextcloud私有云盘从入门到企业级部署全攻略
Nextcloud企业级私有云部署双轨方案:Docker敏捷开发与宝塔生产环境实战指南 在数字化转型浪潮中,企业数据主权意识正在觉醒。Nextcloud作为开源的私有云解决方案,不仅提供了媲美商业云盘的功能体验,更让组织完全掌控数据流向。本文…...
C++和C语言中填充字符、宽度的语法差异
本人因为昨天参加学校天梯赛,后惊讶发现天梯赛题目输出要求答案有格式需求,无奈落榜,仅以此文来告诫自身 (绷不住了)。C语言一、C 语言(printf)基本格式:%[flags][width][.precision…...
VMware Unlocker:跨平台部署macOS虚拟机的创新方法 - 开发者实战指南
VMware Unlocker:跨平台部署macOS虚拟机的创新方法 - 开发者实战指南 【免费下载链接】unlocker 项目地址: https://gitcode.com/gh_mirrors/unloc/unlocker 一、价值定位:突破虚拟化技术壁垒 在x86架构硬件上运行macOS系统长期面临兼容性限制&…...
MCP2518FD屏蔽寄存器自动配置算法(11bit标准帧多ID接收场景)
1. 为什么需要自动配置屏蔽寄存器? 在CAN总线通信中,MCP2518FD作为一款常用的CAN控制器,经常需要处理多ID接收的场景。想象一下你正在开发一个汽车电子控制单元(ECU),需要同时接收来自发动机、变速箱、ABS等多个模块的数据。每个…...
万字拆解OpenClaw,从Gateway到多Agent,揭秘Agent系统的完整运行密码
很多技术文章拆解框架时,总爱按模块逐一罗列,最后落得个“各说各的,毫无关联”的尴尬。与其这样,不如我们回归最本质的问题:当用户真的发来一条消息时,OpenClaw内部到底在发生什么?这条消息从输…...
终极指南:3分钟实现Figma完整中文界面本地化
终极指南:3分钟实现Figma完整中文界面本地化 【免费下载链接】figmaCN 中文 Figma 插件,设计师人工翻译校验 项目地址: https://gitcode.com/gh_mirrors/fi/figmaCN FigmaCN是一款专为中文设计师打造的浏览器插件,通过3800条人工校验的…...
3分钟搞定Windows启动盘制作:WinDiskWriter让macOS用户告别复杂命令行
3分钟搞定Windows启动盘制作:WinDiskWriter让macOS用户告别复杂命令行 【免费下载链接】windiskwriter 🖥 A macOS app that creates bootable USB drives for Windows. 🛠 Patches Windows 11 to bypass TPM and Secure Boot requirements. …...
duilib应用部署实战:基于NSIS的轻量化安装包制作
1. 为什么选择NSIS打包duilib应用 当你用duilib完成了一个漂亮的Windows桌面应用,接下来最头疼的问题就是:怎么让用户能像安装QQ那样一键安装你的程序?这就是我们今天要解决的"最后一公里"问题。 我经历过用zip压缩包发给客户&am…...
