【C++高并发服务器WebServer】-14:Select详解及实现
本文目录
- 一、BIO模型
- 二、非阻塞NIO+忙轮询
- 三、IO多路复用
- 四、Select()多路复用实现
明确一下IO多路复用的概念:IO多路复用能够使得程序同时监听多个文件描述符(文件描述符fd对应的是内核读写缓冲区),能够提升程序的性能。
Linux下实现的I/O多路复用的系统调用主要有select、poll、epoll。
一、BIO模型
多进程服务器的缺点就是,线程或者进程会消耗资源(创建一个子进程会复制虚拟地址空间,占用的资源也就多了。线程来说相对来说比较好,因为共享了虚拟地址空间),然后线程或者进程调度消耗CPU资源。
没有引入多线程/多进程的时候,多个客户端来了,会在accept或者read/recv部分阻塞,导致其他的客户端不能进来。所以通过多线程/进程进行改进,在accept地方加入while循环,然后创建对应的线程,这样可以在线程内部进行读写。不会造成阻塞。
究其根本就是因为accept、read/recv是阻塞的,所以导致了要引入多线程进程解决阻塞的问题。并且在线程或者进程当中,read和recv也会阻塞。
二、非阻塞NIO+忙轮询
非阻塞+忙轮询的 这个模型就是设置accept/read不阻塞,但是需要一直轮询。缺点就是需要占用更多的CPU和系统资源。
非阻塞的模型如下图所示,所以需要某些数据结构来存储现有的client,那么每次进行read或者recv的时候就都得遍历,每次循环都得调用很多次的系统调用,那就是O(n)的复杂度。
为了解决这个问题,所以需要使用IO多路复用技术:select/poll/epoll.
三、IO多路复用
下图是select、poll的模式,就是设置一个代理来帮我们进行管理。委托内核来帮我们管理,检测对应的数据。就是假设有100个fd,那么需要内核需要帮我们管理这100个fd,内核其实检测fd中的读缓冲区是否有数据。有数据,就说明我们需要获取数据了。(底层是用二进制位的形式来检查,就是设置标志位是否为1)
缺点就是只会通知有多少个fd有动静,但是具体是哪个fd,需要我们挨个遍历一遍。
epoll相对于上面的优点就是能够通知有多少个fd有动静,然后还会说明具体是哪些fd。
四、Select()多路复用实现
select
的主要思想就是:
首先需要构造一个包含文件描述符的列表,并将需要监听的文件描述符加入其中。
接着调用一个系统函数,该函数会阻塞地监听列表中的文件描述符。这个监听过程是由内核完成的,只有当列表中的一个或多个文件描述符准备好进行I操作/O时,函数才会返回。
当函数返回时,它会告知进程有多少个以及是哪些文件描述符已经准备好进行I/O操作。
相关的头文件如下。
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
参数说明如下:
nfds
:委托内核检测的最大文件描述符的值+1。
readfds
:要检测的文件描述符中的读
的集合,委托内核检测哪些文件描述符的读属性(读缓冲区是否有数据)。一般只检测读操作,读是被动的接收数据,检测的就是读缓冲区。只有当对方发送来数据,才能检测到。fd_set
数据类型是整数,如果对其进行sizeof
,那么会获得一个整数。比如sizeof(fd_set)=128
,也就是128个字节,对应1024位,可以保存1024的标志位,每个位对应一个文件描述符,这是一个传入传出参数。(就是我们先置为哪些为1,然后把这个作为参数传给内核,内核只会对这个1进行检测。)
writefds
:是要检测的文件描述符的写
的集合,委托内核检测哪些文件描述符有写的属性。委托内核检测缓冲区是不是还可以写数据(不满的就可以写)。
exceptfds
:检测发生异常的文件描述符的集合。
timeout
:设置的超时时间。timeval
是一个结构体,有long tv_sec
和long tv_usec
两个属性,一个对应秒,一个对应微秒,设置超时时间。设置NULL,是永久阻塞,直到检测到了对应的文件描述符有变化。tv_sec = 0 ,tv_usec = 0
表示不阻塞。tv_sec > 0 ,tv_usec > 0
表示阻塞对应的时间。
select函数返回-1表示失败,返回n表示集合中检测到了有n个文件描述发生了变化。
// 将参数文件描述符fd对应的标志位设置为0
void FD_CLR(int fd, fd_set *set);// 判断fd对应的标志位是0还是1, 返回值 : fd对应的标志位的值,0,返回0, 1,返回1
int FD_ISSET(int fd, fd_set *set);// 将参数文件描述符fd 对应的标志位,设置为1
void FD_SET(int fd, fd_set *set);// fd_set一共有1024 bit, 全部初始化为0
void FD_ZERO(fd_set *set);
通过下面的示意图我们能够很清晰的看到select的一个作用过程。
fd_set
是一个结构体,用于 select 和 pselect 函数的数据结构,用于表示一组文件描述符(file descriptors)。它的实现基于位掩码(bitmask)
,通过将文件描述符的编号映射到位掩码中的特定位来管理文件描述符集合。
long int表示8个字节。typedef long int __fd_mask;
定义了 __fd_mask
为 long int
类型,用于表示位掩码。每个 __fd_mask
可以存储多个文件描述符的状态。
__FD_SETSIZE 和 __NFDBITS
:__FD_SETSIZE
是 fd_set 能够管理的最大文件描述符数量,默认值通常是 1024。
__NFDBITS
是每个 __fd_mask
可以表示的文件描述符数量。由于 __fd_mask
是 long int 类型,通常为 64 位(在 64 位系统上),因此 __NFDBITS
通常是 64。
fds_bits 或 __fds_bits
:fd_set
结构体中包含一个数组,数组的类型是 __fd_mask
,数组的大小是 __FD_SETSIZE / __NFDBITS
。这个数组用于存储文件描述符的状态。每个 __fd_mask
元素可以表示 __NFDBITS
个文件描述符。
这个数组的大小是 __FD_SETSIZE / __NFDBITS
,例如:如果 __FD_SETSIZE = 1024,__NFDBITS = 64
,则数组大小为 1024 / 64 = 16。每个 __fd_mask
元素可以表示 64 个文件描述符,因此整个数组可以表示 1024 个文件描述符。
我们来看一个简单的select
的服务端代码。
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>int main() {// 创建socketint lfd = socket(PF_INET, SOCK_STREAM, 0);struct sockaddr_in saddr;saddr.sin_port = htons(9999);saddr.sin_family = AF_INET;saddr.sin_addr.s_addr = INADDR_ANY;// 绑定bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));// 监听listen(lfd, 8);// 创建一个fd_set的集合,存放的是需要检测的文件描述符fd_set rdset, tmp;FD_ZERO(&rdset);FD_SET(lfd, &rdset);int maxfd = lfd;while(1) {tmp = rdset;// 调用select系统函数,让内核帮检测哪些文件描述符有数据int ret = select(maxfd + 1, &tmp, NULL, NULL, NULL);if(ret == -1) {perror("select");exit(-1);} else if(ret == 0) {continue;} else if(ret > 0) {// 说明检测到了有文件描述符的对应的缓冲区的数据发生了改变if(FD_ISSET(lfd, &tmp)) {// 表示有新的客户端连接进来了struct sockaddr_in cliaddr;int len = sizeof(cliaddr);int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len);// 将新的文件描述符加入到集合中FD_SET(cfd, &rdset);// 更新最大的文件描述符maxfd = maxfd > cfd ? maxfd : cfd;}for(int i = lfd + 1; i <= maxfd; i++) {if(FD_ISSET(i, &tmp)) {// 说明这个文件描述符对应的客户端发来了数据char buf[1024] = {0};int len = read(i, buf, sizeof(buf));if(len == -1) {perror("read");exit(-1);} else if(len == 0) {printf("client closed...\n");close(i);FD_CLR(i, &rdset);} else if(len > 0) {printf("read buf = %s\n", buf);write(i, buf, strlen(buf) + 1);}}}}}close(lfd);return 0;
}
client端对应代码如下。
#include <stdio.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>int main() {// 创建socketint fd = socket(PF_INET, SOCK_STREAM, 0);if(fd == -1) {perror("socket");return -1;}struct sockaddr_in seraddr;inet_pton(AF_INET, "127.0.0.1", &seraddr.sin_addr.s_addr);seraddr.sin_family = AF_INET;seraddr.sin_port = htons(9999);// 连接服务器int ret = connect(fd, (struct sockaddr *)&seraddr, sizeof(seraddr));if(ret == -1){perror("connect");return -1;}int num = 0;while(1) {char sendBuf[1024] = {0};sprintf(sendBuf, "send data %d", num++);write(fd, sendBuf, strlen(sendBuf) + 1);// 接收int len = read(fd, sendBuf, sizeof(sendBuf));if(len == -1) {perror("read");return -1;}else if(len > 0) {printf("read buf = %s\n", sendBuf);} else {printf("服务器已经断开连接...\n");break;}// sleep(1);usleep(1000);}close(fd);return 0;
}
相关文章:

【C++高并发服务器WebServer】-14:Select详解及实现
本文目录 一、BIO模型二、非阻塞NIO忙轮询三、IO多路复用四、Select()多路复用实现 明确一下IO多路复用的概念:IO多路复用能够使得程序同时监听多个文件描述符(文件描述符fd对应的是内核读写缓冲区),能够提升程序的性能。 Linux下…...

redis项目
短信登录 这一块我们会使用redis共享session来实现 商户查询缓存 通过本章节,我们会理解缓存击穿,缓存穿透,缓存雪崩等问题,让小伙伴的对于这些概念的理解不仅仅是停留在概念上,更是能在代码中看到对应的内容 优惠…...
Spring统一修改RequestBody
我们编写RestController时,有可能多个接口使用了相同的RequestBody,在一些场景下需求修改传入的RequestBody的值,如果是每个controller中都去修改,代码会比较繁琐,最好的方式是在一个地方统一修改,比如将he…...

NCV4275CDT50RKG 车规级LDO线性电压调节器芯片——专为新能源汽车设计的高可靠性电源解决方案
产品概述: NCV4275CDT50RKG 是一款符合 AEC-Q100 车规认证的高性能LDO(低压差线性稳压器),专为新能源汽车的严苛工作环境设计。该芯片支持 输出调节为 5.0 V 或 3.3 V,最大输出电流达 450mA,具备超低静态电流…...
前端开发架构师Prompt指令的最佳实践
前端开发架构师Prompt 提示词可作为系统提示词使用,可基于用户的需求输出对应的编码方案。 本次提示词偏向前端开发的使用,如有需要可适当修改关键词和示例。 推荐使用 Cursor 中作为自定义指令使用Cline 插件中作为自定义指令使用在力所能及的范围内使…...

【AI实践】Windsurf AI编程voice对话应用
Android Studio新建一个安卓 hello world 应用,使用gitee插件,推送到个人gitee仓库。 本文要写一个基于GLM4-voice的一个语音对话应用,参考 bigmodel.cn平台和开发文档:智谱AI开放平台 第一轮 打开cursor,model切换到…...
【自学笔记】文言一心的基础知识点总览-持续更新
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 文心一言知识点总览一、文心一言简介二、文心一言的核心功能三、文心一言的技术特点四、文心一言的应用场景五、文心一言的使用技巧六、文心一言的未来发展 总结 文…...

kafka消费端之消费者协调器和组协调器
文章目录 概述回顾历史老版本获取消费者变更老版本存在的问题 消费者协调器和组协调器新版如何解决老版本问题再均衡过程**第一阶段CFIND COORDINATOR****第二阶段(JOINGROUP)**选举消费组的lcader选举分区分配策略 第三阶段(SYNC GROUP&…...
线上hbase rs 读写请求个数指标重置问题分析
问题描述: 客户想通过调用hbase的jmx接口获取hbase的读写请求个数,以此来分析HBase读写请求每日增量。 但是发现生产,测试多个集群,Hbase服务指标regionserver读写请求个数存在突然下降到0或者大幅度下降情况。 需要排查原因: 某个Region的读写请求数:会发现经常会重置为…...

DeepSeek-R1 本地电脑部署 Windows系统 【轻松简易】
本文分享在自己的本地电脑部署 DeepSeek,而且轻松简易,快速上手。 这里借助Ollama工具,在Windows系统中进行大模型部署~ 1、安装Ollama 来到官网地址:Download Ollama on macOS 点击“Download for Windows”下载安装包&#x…...

数据库,数据表的增删改查操作
一.数据库的基本操作 (1)创建数据库 创建数据库就是在数据库系统中划分一块存储数据的空间,方便数据的分配、放置和管理。在MySQL中使用CREATE DATABASE命令创建数据库,语法格式如下: CREATE DATABASE数据库名称; 注:…...

VUE 集成企微机器人通知
message-robot 便于线上异常问题及时发现处理,项目中集成企微机器人通知,及时接收问题并处理 企微机器人通知工具类 export class MessageRobotUtil {constructor() {}/*** 发送 markdown 消息* param robotKey 机器人 ID* param title 消息标题* param…...

《Java核心技术 卷II》Java平台的脚本机制
Java平台的脚本机制 脚本引擎:可以执行用某种特定语言编写的脚本类库。 ScriptEngineManager 虚拟机启动时用它发现可用的脚步引擎。 调用getEngineFactories来枚举这些引擎。 知道所需要的引擎可以通过名字、MIME类型或拓展文件来请求它。 var manager new S…...

Ollama + AnythingLLM + Deepseek r1 实现本地知识库
1、Ollama:是一个开源的大型语言模型 (LLM)服务工具,旨在简化在本地运行大语言模型的过程,降低使用大语言模型的门槛。 2、AnythingLLM:是由Mintplex Labs Inc. 开发的一款全栈应用程序,旨在构建一个高效、可定制、…...

记录 | WPF基础学习Style局部和全局调用
目录 前言一、Style1.1 例子1.2 为样式起名字1.3 BasedOn 继承上一个样式 二、外部StyleStep1 创建资源字典BaseButtonStyle.xamlStep2 在资源字典中写入StyleStep3 App.xaml中写引用路径【全局】Step4 调用三、代码提供四、x:Key和x:Name区别 更新时间 前言 参考文章ÿ…...
PromptSource安装报错
一、现象 运行命令:streamlit run promptsource/app.py 报错: streamlit run promptsource/app.py Traceback (most recent call last): File "/usr/local/bin/streamlit", line 5, in <module> from streamlit.cli import main File …...
Leetcode 3448. Count Substrings Divisible By Last Digit
Leetcode 3448. Count Substrings Divisible By Last Digit 1. 解题思路2. 代码实现 题目链接:3448. Count Substrings Divisible By Last Digit 1. 解题思路 这一题的话我们走的是一个累积数组的思路。 首先,我们使用一个cache数组记录下任意段数字…...
Maven 下载与配置教程:附百度网盘地址
一、引言 在 Java 开发领域,Maven 是一款广泛使用的项目管理和构建工具。它能够帮助开发者自动化项目的构建、依赖管理和文档生成等任务,从而提高开发效率和项目质量。本文将详细介绍 Maven 的下载方法、安装步骤、配置教程以及使用技巧,并提…...

基于 GEE 的网格化降雨数据可视化与时间序列分析
目录 1 数据介绍 2 代码解析 3 完整代码 4 运行结果 降雨数据在遥感分析中是一个重要的因素,GEE 中有许多相关的降雨量数据以供研究。本文分享以 CHIRPS 网格化降雨量数据为例,进行时间序列分析,统计研究区年降雨量,以及将年降雨量导出至 csv 中。 1 数据介绍 气候灾…...
java-初识List
List: List 是一个接口,属于 java.util 包,用于表示有序的元素集合。List 允许存储重复元素,并且可以通过索引访问元素。它是 Java 集合框架(Java Collections Framework)的一部分 特点: 有序…...
[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解
突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 安全措施依赖问题 GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...

【WiFi帧结构】
文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成:MAC头部frame bodyFCS,其中MAC是固定格式的,frame body是可变长度。 MAC头部有frame control,duration,address1,address2,addre…...

HTML 列表、表格、表单
1 列表标签 作用:布局内容排列整齐的区域 列表分类:无序列表、有序列表、定义列表。 例如: 1.1 无序列表 标签:ul 嵌套 li,ul是无序列表,li是列表条目。 注意事项: ul 标签里面只能包裹 li…...
【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具
第2章 虚拟机性能监控,故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令:jps [options] [hostid] 功能:本地虚拟机进程显示进程ID(与ps相同),可同时显示主类&#x…...

安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲
文章目录 前言第一部分:体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分:体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...
NPOI Excel用OLE对象的形式插入文件附件以及插入图片
static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...

脑机新手指南(七):OpenBCI_GUI:从环境搭建到数据可视化(上)
一、OpenBCI_GUI 项目概述 (一)项目背景与目标 OpenBCI 是一个开源的脑电信号采集硬件平台,其配套的 OpenBCI_GUI 则是专为该硬件设计的图形化界面工具。对于研究人员、开发者和学生而言,首次接触 OpenBCI 设备时,往…...

WPF八大法则:告别模态窗口卡顿
⚙️ 核心问题:阻塞式模态窗口的缺陷 原始代码中ShowDialog()会阻塞UI线程,导致后续逻辑无法执行: var result modalWindow.ShowDialog(); // 线程阻塞 ProcessResult(result); // 必须等待窗口关闭根本问题:…...

嵌入式学习之系统编程(九)OSI模型、TCP/IP模型、UDP协议网络相关编程(6.3)
目录 一、网络编程--OSI模型 二、网络编程--TCP/IP模型 三、网络接口 四、UDP网络相关编程及主要函数 编辑编辑 UDP的特征 socke函数 bind函数 recvfrom函数(接收函数) sendto函数(发送函数) 五、网络编程之 UDP 用…...

论文阅读:Matting by Generation
今天介绍一篇关于 matting 抠图的文章,抠图也算是计算机视觉里面非常经典的一个任务了。从早期的经典算法到如今的深度学习算法,已经有很多的工作和这个任务相关。这两年 diffusion 模型很火,大家又开始用 diffusion 模型做各种 CV 任务了&am…...