7. C++通过select的方式实现高性能网络服务器

什么是异步IO

异步IO指的是用户程序将IO请求提交后,无需等待IO操作的完成,而是可以继续处理别的事情。
所谓异步IO,是指以事件触发的机制来对IO操作进行处理。
与多进程和多线程技术相比,异步I/O技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。
我们举个例子吧:
假如说我们要去读一个数据,我们要发生系统调用,然后去询问IO是否准备好了,
在这里插入图片描述
对于同步I/O,如果没有准备好的话,他就会一直等,一直等,直到他准备好了。然后再进行下一步操作。
在这里插入图片描述
对于这个异步I/O,发起系统调用后,不是一直等,而是立即返回一个没准备好的I/O。
在这里插入图片描述
然后过一段时间这个用户态会再次询问是否准备好了。如果还是没有准备好,还是会立即返回,知道准备好了。
在这里插入图片描述
然后在一些空挡里面,这个CPU是可以干其他的事情的。

以select方式实现高性能网络服务器

  • 遍历文件描述符集中的所有描述符,找出有变化的描述符
    所以这个select是一个半自动,需要我们遍历所有的

  • 对于侦听的socket和数据处理的socket要区别对待

  • socket必须设置为非阻塞方式工作

重要的API

1.函数接口(1)select int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);功能:监听文件描述符集合参数:nfds:最大文件描述符个数+1readfds:读文件描述符集合writefds:写文件描述符集合exceptfds:其余文件描述符集合,也就是异常的文件描述符集合timeout:设定超时时间,也就是多长时间询问一次,如果没有这个的话,阻塞的就会一直阻塞NULL:永远等待,直到有数据发生返回值:成功返回产生事件的文件描述符个数失败返回-1 	
2. void FD_CLR(int fd, fd_set *set);功能:将fd从文件描述符集合中移除		
3. int  FD_ISSET(int fd, fd_set *set);功能:判断fd是否仍在文件描述符集合中		
4. void FD_SET(int fd, fd_set *set);功能:将fd加入文件描述符集合			
5. void FD_ZERO(fd_set *set);功能:将文件描述符集合清0 
6. flag fcntl(fd,F_SETFL/F_GETFL, flag)
这个可以将文件描述符设置成阻塞或者非阻塞。fcntl 函数的基本原型:
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
fd: 需要进行操作的文件描述符。
cmd: 需要执行的操作命令,可以是 F_GETFL(获取文件状态标志)、F_SETFL(设置文件状态标志)等。
arg: 根据不同的命令可能需要的参数。常见的 fcntl 命令:
F_DUPFD:
复制文件描述符,返回一个新的文件描述符。
F_GETFD:
获取文件描述符标志。
F_SETFD:
设置文件描述符标志。
F_GETFL:
获取文件状态标志。
F_SETFL:
设置文件状态标志。
F_GETLK:
获取记录锁的信息。
F_SETLK:
设置或释放记录锁。
F_SETLKW:
阻塞地设置或释放记录锁。示例用法:
获取和设置文件状态标志:
// 获取文件状态标志
int flags = fcntl(fd, F_GETFL);
// 设置文件状态标志为非阻塞模式
flags |= O_NONBLOCK;
fcntl(fd, F_SETFL, flags);

代码的思路就是:首先将socket_fd设置成异步非阻塞,然后设置一个fd_set fd_sets;为文件集。
然后先将其全部清零,然后将一开始创造的监听的socket,加入到文件描述符集合fd_sets中

//文件描述符集合清0
FD_ZERO(&fd_sets);
//将第一个socket_fd加入到文件描述符集合
FD_SET(socket_fd,&fd_sets);

然后创建一个accept_fds[FD_SIZE]表示每个连接的socket,然后将accept_fds[i]加入到fd_sets中去。并且更新max_fd。

for(int i=0;i<FD_SIZE;i++){if(accept_fds[i]!=-1){//判断这个是有效的socket//判断是否大于最大的文件描述符if(accept_fds[i]>max_fd){max_fd=accept_fds[i];}//将这个accepts_fds[i]加入到fd_sets中去FD_SET(accept_fds[i],&fd_sets);}}

然后用select进行监听

//fd_sets读的,写的,超时的,时间不关心
events=select(max_fd+1,&fd_sets,NULL,NULL,NULL);

然后判断符合条件的events有多少个。如果events等于0的话。首先要判断是新来了一个连接的socket,还是已有的socket需要发送消息。如果是新来一个socket的话,那就在accept_fds中找一个空位置插进去。

//判断socket_fd是否在fd_sets集合中,判断是一个新的连接还是一个数组,判断是否是侦听的这个socket触发的事件
if(FD_ISSET(socket_fd,&fd_sets)){for(int i=0;i<FD_SIZE;i++){//找一个空槽if(accept_fds[i]==-1){curpos=i;break;}}socklen_t addr_len=sizeof(struct sockaddr);accept_fd = accept(socket_fd,(struct sockaddr *)&remoteaddr,&addr_len);//设置成非阻塞//创建了socket之后我们要设置成异步的flags = fcntl(accept_fd,F_GETFL,0);//然后设置成非阻塞fcntl(accept_fd,F_SETFL,flags | O_NONBLOCK);//将这个接收的插入到槽中accept_fds[curpos]=accept_fd;
}

如果不是新来的话,那就是accept_fds[i]的事件,那么进行文件的收发

//对于每一个accept_fds[i]进行读数据
for(int i=0;i<FD_SIZE;i++){if(accept_fds[i]!=-1&&FD_ISSET(accept_fds[i],&fd_sets)){memset(in_buff, 0, sizeof(in_buff));//接收消息ret = recv(accept_fds[i],(void *)in_buff,MESSAGE_LEN,0);if(ret==0){close(accept_fds[i]);break;}std::cout<<"recv:"<<in_buff<<std::endl;//返回消息send(accept_fds[i],(void*)in_buff,MESSAGE_LEN,0);}
}

总的代码:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <iostream>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/types.h>
//端口
#define PORT 8888
#define MESSAGE_LEN 1024
#define FD_SIZE 1024int main(int argc,char* argv[]){int ret=-1;int on=1;int backlog=10;//缓冲区大小int socket_fd,accept_fd;pid_t pid;int flags;int max_fd=-1;//最大的文件描述符int curpos=-1;//临时变量int events=0;//select中符合条件的返回值fd_set fd_sets;//读的文件集int accept_fds[FD_SIZE]={-1,};struct sockaddr_in localaddr,remoteaddr;char in_buff[MESSAGE_LEN]={0,};//这个是侦听的那个socket_fd=socket(AF_INET,SOCK_STREAM,0);if(socket_fd==-1){std::cout<<"Failed to create socket!"<<std::endl;exit(-1);}//创建了socket之后我们要设置成异步的flags = fcntl(socket_fd,F_GETFL,0);//然后设置成非阻塞fcntl(socket_fd,F_SETFL,flags | O_NONBLOCK);//这是max_fd=创建的第一个socketmax_fd=socket_fd;ret=setsockopt(socket_fd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));if(ret==-1){std::cout<<"Failed to set socket options!"<<std::endl;}localaddr.sin_family=AF_INET;//地址族localaddr.sin_port=htons(PORT);//端口号localaddr.sin_addr.s_addr=INADDR_ANY;//这个就是0bzero(&(localaddr.sin_zero), 8);ret= bind(socket_fd,(struct sockaddr *)&localaddr,sizeof(struct sockaddr));//绑定if(ret==-1){//绑定失败std::cout<<"Failed to bind addr!"<<std::endl;exit(-1);}ret = listen(socket_fd,backlog);//第二个是缓冲区大小,因为同一时间只能处理一个,其他都放在缓冲区if(ret==-1){std::cout<<"failed to listen socket!"<<std::endl;exit(-1);}while(1){//等待连接//文件描述符集合清0FD_ZERO(&fd_sets);//将第一个socket_fd加入到文件描述符集合FD_SET(socket_fd,&fd_sets);for(int i=0;i<FD_SIZE;i++){if(accept_fds[i]!=-1){//判断这个是有效的socket//判断是否大于最大的文件描述符if(accept_fds[i]>max_fd){max_fd=accept_fds[i];}//将这个accepts_fds[i]加入到fd_sets中去FD_SET(accept_fds[i],&fd_sets);}}//fd_sets读的,写的,超时的,时间不关心events=select(max_fd+1,&fd_sets,NULL,NULL,NULL);if(events<0){//函数调用失败std::cout<<"Failed to use select!"<<std::endl;}else if(events==0){std::cout<<"timeout...!"<<std::endl;}else if(events){//判断socket_fd是否在fd_sets集合中,判断是一个新的连接还是一个数组,判断是否是侦听的这个socket触发的事件if(FD_ISSET(socket_fd,&fd_sets)){for(int i=0;i<FD_SIZE;i++){//找一个空槽if(accept_fds[i]==-1){curpos=i;break;}}socklen_t addr_len=sizeof(struct sockaddr);accept_fd = accept(socket_fd,(struct sockaddr *)&remoteaddr,&addr_len);//设置成非阻塞//创建了socket之后我们要设置成异步的flags = fcntl(accept_fd,F_GETFL,0);//然后设置成非阻塞fcntl(accept_fd,F_SETFL,flags | O_NONBLOCK);//将这个接收的插入到槽中accept_fds[curpos]=accept_fd;}//对于每一个accept_fds[i]进行读数据for(int i=0;i<FD_SIZE;i++){if(accept_fds[i]!=-1&&FD_ISSET(accept_fds[i],&fd_sets)){memset(in_buff, 0, sizeof(in_buff));//接收消息ret = recv(accept_fds[i],(void *)in_buff,MESSAGE_LEN,0);if(ret==0){close(accept_fds[i]);break;}std::cout<<"recv:"<<in_buff<<std::endl;//返回消息send(accept_fds[i],(void*)in_buff,MESSAGE_LEN,0);}}}}close(socket_fd);//侦听的那个return 0;
}

客户端也是之前的。
在这里插入图片描述

设置select超时时间

对于上面的代码我们没有设置select超时时间,也就是超时一直等待,直到处理到。那么怎么设置select超时时间呢?

struct timeval{long tv_sec;//秒long tv_usec;//微妙
}

一般设置为500ms。

select 函数

select 函数输入参数的意义

  • 我们关心的文件描述符

  • 对每个文件描述符我们关心的状态(读,写,异常)

  • 我们要等待的时间(永远(NULL)、一段时间(500ms),不等待(0))

从select函数得到的信息

  • 已经做好准备的文件描述符的个数
  • 对于读、写、异常,哪些文件描述符准备好了

理解select模型

  • 理解select模型的关键在于理解fd_set类型,这个fd_set可以理解为一个bit_set,里面的每一位都代表一个文件描述符。
  • 其实fd set就是多个整型字的集合,每个bit代表一个文件描述符。
  • FD_ZERO表示将所有位置置为0
  • FD_SET是将fd_set中的某一位置1
  • select函数执行后,系统会修改fd_set中的内容
  • select函数执行后,应用层要得新设置fd_set 中的内容

模型图

首先我们要侦听这三个文件描述符,是否会发生读事件。
在这里插入图片描述
当系统发现有改变的话,系统就会监听到,然后select就会返回。
在这里插入图片描述

在这里插入图片描述

select优缺点

优点:
1)单进程让服务端拥有基本的一对多响应能力。
2)实现较为简单。(模型比较轻量)
3)SELECT跨平台能力较强。(各个系统兼容)
4)如果网络IO监听模型对时间精度有要求,select可以满足需要,支持微妙级别定时监听。
缺点:
1)SELECT受fd_set监听集合类型的影响, 最大监听数为1024。(不能满足服务端高并发需求)
2)SELECT监听采用的轮询方式,(随着轮询数量的增加 ,IO处理性能呈线性下降),轮询模型可能导致事件处理不及时。
3)SELECT启动监听时传入监听集合, 监听到就绪后内核修改为就绪集合,该就绪集合无法作为监听集合再次使用,所以用户必须将传入传出进行分离,比较麻烦。
4)SELECT监听到就绪后只返回就绪的数量,没有返回谁就绪,开发者需要自行遍历查找就绪的socket并处理, 开销较大,比较耗时。
5)SELECT每轮监听,都要向内核空间重新拷贝监听集合, 将集合中设置的socket,挂载到等待队列中设置监听, 这种做法会导致大量重复的拷贝开销与挂载开销。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/16145.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

R语言数据处理(四)

R语言数据处理&#xff08;四&#xff09; 1. 表格合并 1. 表格合并 &#x1f680;&#x1f680;&#x1f680;代码功能&#xff1a; 从指定文件夹中读取所有 .xlsx 文件&#xff0c;并提取每个文件中特定行和列的数据&#xff0c;然后将这些数据合并到一个最终的数据框中&…

在Nano上部署yolov5

确认镜像版本为JetPack4.4.1(L4T 32.4.4)以上版本 下载链接下载pytorch+nvidia docker镜像(pytorch1.6+torchvision0.7.0)yolov5opencv4.4.0 1. 在已经部署了镜像的机器上获取镜像   1.1 获取镜像名

《Ai学习笔记》-模型集成部署

后续大多数模型提升速度和精度&#xff1a; 提升速度&#xff1a; -知识蒸馏&#xff0c;以distillBert和tinyBert为代表 -神经网络优化技巧。prune来剪裁多余的网络节点&#xff0c;混合精度&#xff08;fp32和fp26混合来降低计算精度从从而实现速度的提升&#xff09; 提…

【教学类-58-04】黑白三角拼图04(2-10宫格,每个宫格随机1张-6张,带空格纸,1页6张黑白,1张6张白卡)

背景需求&#xff1a; 前期制作了黑白三角拼图2*2、3*3、4*4&#xff0c;确定了基本模板&#xff0c;就可以批量制作更多格子数 【教学类-58-01】黑白三角拼图01&#xff08;2*2宫格&#xff09;固定256种随机抽取10张-CSDN博客文章浏览阅读522次&#xff0c;点赞13次&#x…

零基础学Java(全170集)

课程概述 本课程旨在全面深化对 Java 语言的核心技术理解&#xff0c;并提升编程实践能力。课程内容涵盖以下关键领域&#xff1a; 掌握Java核心语法&#xff0c;为后续学习打下扎实的基础。熟练运用Java常用的类库与开发工具&#xff0c;提高开发效率与质量。解决面向对象编…

卡码网笔试 | 118 小y删数字、119 小红的字符串切割、120 小红的数字匹配

118 小y删数字 逐个数字循环除10&#xff0c;并且用一个变量记录非0位数&#xff0c;最后加起来即可。 代码如下&#xff1a; #include <iostream>using namespace std;int main() {int n;cin >> n;int a[n];int sum 0;for (int i 0; i < n; i) cin >&g…

高中数学:平面向量-题型总结及解题思路梳理

一、知识点及解题思路梳理 高中&#xff0c;2/3的向量题目是坐标向量题&#xff0c;1/3是几何向量题。但是&#xff0c;这1/3的几何向量题可以转换成坐标向量题。 二、练习 例题1 几何型向量题 例题2

【机器学习300问】100、怎么理解卷积神经网络CNN中的池化操作?

一、什么是池化&#xff1f; 卷积神经网络&#xff08;CNN&#xff09;中的池化&#xff08;Pooling&#xff09;操作是一种下采样技术&#xff0c;其目的是减少数据的空间维度&#xff08;宽度和高度&#xff09;&#xff0c;同时保持最重要的特征并降低计算复杂度。池化操作不…

【LeetCode 151】反转字符串中的单词

1. 题目 2. 分析 这题要是用Python写&#xff0c;就非常简单了。 3. 代码 class Solution:def reverseWords(self, s: str) -> str:s " ".join(reversed(s.strip().split()))return s

池的概念以及数据库连接池 Druid

1.池的概述 池就相当于一个共享资源&#xff0c;是对资源的整合和调配&#xff0c;节省存储空间&#xff0c;当需要的时候可以直接在池中取&#xff0c;用完之后再还回去。 为什么需要连接池 假如没有连接池&#xff0c;我们操作数据库的流程如下&#xff1a; 应用程序使用…

若依 Ruoyi-Vue PageHelper 分页失效 total为记录数

分页插件PageHelper返回记录总数total竟然出错了 执行控制台的SQL&#xff0c;查询出来的total数量是对的&#xff0c;很奇怪分页的total设置为查询到的记录数。 怀疑对list.stream操作&#xff0c;影响了分页&#xff0c;代码发现确实是这样&#xff0c;debug&#xff0c;居然…

firewalld 防火墙

firewalld概述 Linux系统防火墙从CentOS7开始的默认防火墙工作在网络层&#xff0c;属于包过滤防火墙 Firewalld和iptables的关系 netfilter 位于Linux内核中的包过滤功能体系称为Linux防火墙的“内核态” firewalld Centos默认的管理防火墙规则的工具称为防火墙的“用…

Gradient-checkpointing的原理

原文&#xff1a; 将更大的网络安装到内存中。|by 雅罗斯拉夫布拉托夫 |张量流 |中等 (medium.com) 前向传播时&#xff0c;隔几层就保留一层activation数据&#xff0c;其余层的activation都释放掉&#xff1b; 反向传播时&#xff0c;从最近的checkpoint去重新跑forward&…

React 如何自定义 Hooks

自定义 Hooks React 内部自带了很多 Hooks 例如 useState、useEffect 等等&#xff0c;那么我们为什么还要自定义 Hooks&#xff1f;使用 Hooks 的好处之一就是重用&#xff0c;可以将代码从组件中抽离出来定义为 Hooks&#xff0c;而不用每个组件中重复去写相同的代码。首先是…

Ps:消失点滤镜 - 测量工具

Ps菜单&#xff1a;滤镜/消失点 Filter/Vanishing Point 快捷键&#xff1a;Ctrl Alt V “消失点”滤镜中的测量工具 Measure Tool用于在透视平面内测量图像中对象的大小&#xff0c;适用于建筑师、设计师、法医和木工等需要精确测量的用户。 快捷键&#xff1a;R ◆ ◆ ◆…

基于springboot+vue的4S店车辆管理系统

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

CMS Full GC流程以及调优配置

个人博客 CMS Full GC流程以及调优配置 | iwts’s blog CMS CMS 收集器是以实现最短 STW 时间为目标的收集器&#xff0c;所以对于偏业务的后台开发而言&#xff0c;基本上都无脑选CMS了。 多线程收集器&#xff0c;工作在老年代&#xff0c;采用标记清除算法。比较特殊&am…

React开发必须掌握这些es6语法-03

箭头函数 其实就是java的lamda编程&#xff0c;它的特点是单向无环流&#xff0c;没有变量&#xff0c;源数据状态不能被改变。 基本语法 ()> {} //表示一个空函数&#xff0c;和function(){}功能一样&#xff0c;如果只有一行语句则&#xff0c;{}可省略 lef fn arg &g…

【QNX】Qnx IPC通信 Message-passing

Qnx IPC通信 Message-passing Message-passing介绍 QNX提供了多种IPC(Interprocess Communication )通信方式&#xff0c;包括Message-passing、Plus&#xff08;脉冲&#xff09;、Event、Signal、共享内存、Pipe&#xff0c;当然还有socket。 Message-passing是Qnx IPC的主…

机器学习 - 特征预处理 - 分箱

分箱&#xff08;Binning&#xff09;是一种数据预处理技术&#xff0c;将连续变量分割成离散的组别或区间&#xff0c;有助于减少数据的噪音&#xff0c;提高模型的稳定性。以下是五种常见的分箱方法及其详细介绍&#xff1a; 1. 卡方分箱&#xff08;Chi-square Binning&…