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注入漏洞,也可以通过整数溢出来判断&…...
uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖
在前面的练习中,每个页面需要使用ref,onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入,需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...
Nuxt.js 中的路由配置详解
Nuxt.js 通过其内置的路由系统简化了应用的路由配置,使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...
leetcodeSQL解题:3564. 季节性销售分析
leetcodeSQL解题:3564. 季节性销售分析 题目: 表:sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...
代理篇12|深入理解 Vite中的Proxy接口代理配置
在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...
Java线上CPU飙高问题排查全指南
一、引言 在Java应用的线上运行环境中,CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时,通常会导致应用响应缓慢,甚至服务不可用,严重影响用户体验和业务运行。因此,掌握一套科学有效的CPU飙高问题排查方法&…...
React---day11
14.4 react-redux第三方库 提供connect、thunk之类的函数 以获取一个banner数据为例子 store: 我们在使用异步的时候理应是要使用中间件的,但是configureStore 已经自动集成了 redux-thunk,注意action里面要返回函数 import { configureS…...
七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...
【Elasticsearch】Elasticsearch 在大数据生态圈的地位 实践经验
Elasticsearch 在大数据生态圈的地位 & 实践经验 1.Elasticsearch 的优势1.1 Elasticsearch 解决的核心问题1.1.1 传统方案的短板1.1.2 Elasticsearch 的解决方案 1.2 与大数据组件的对比优势1.3 关键优势技术支撑1.4 Elasticsearch 的竞品1.4.1 全文搜索领域1.4.2 日志分析…...
DBLP数据库是什么?
DBLP(Digital Bibliography & Library Project)Computer Science Bibliography是全球著名的计算机科学出版物的开放书目数据库。DBLP所收录的期刊和会议论文质量较高,数据库文献更新速度很快,很好地反映了国际计算机科学学术研…...
怎么开发一个网络协议模块(C语言框架)之(六) ——通用对象池总结(核心)
+---------------------------+ | operEntryTbl[] | ← 操作对象池 (对象数组) +---------------------------+ | 0 | 1 | 2 | ... | N-1 | +---------------------------+↓ 初始化时全部加入 +------------------------+ +-------------------------+ | …...
