【APUE】网络socket编程温度采集智能存储与上报项目技术------多路复用
作者简介: 一个平凡而乐于分享的小比特,中南民族大学通信工程专业研究生在读,研究方向无线联邦学习
擅长领域:驱动开发,嵌入式软件开发,BSP开发
作者主页:一个平凡而乐于分享的小比特的个人主页
文章收录专栏:网络socket编程之温度采集智能存储与上报项目,本专栏为网络socket编程之温度采集智能存储与上报项目技术简介,实现流程分析
项目源码:https://gitee.com/TJF865975/project1_ds18b20,README部分包含项目功能及实现,欢迎大家Watch、Star、Fork。
欢迎大家点赞 👍 收藏 ⭐ 加关注哦!💖💖
1.多路复用基础知识
1.1 阻塞和非阻塞,同步和异步
阻塞和非阻塞的概念描述的是用户线程调用内核IO操作的方式:阻塞是指IO操作在没有接收完数据或者没有得到结果之前不会返回,需要彻底完成后才返回到用户空间;而非阻塞是指IO操作被调用后立即返回给用户一个状态值,无需等到IO操作彻底完成。
同步和异步的概念描述的是用户线程与内核的交互方式:同步是指用户线程发起IO请求后需要等待或者轮询内核IO操作完成 后才能继续执行;而异步是指用户线程发起IO请求后仍继续执行,当内核IO操作完成后会通知用户线程,或者调用用户线程注册 的回调函数。
1.2 IO多路复用
IO多路复用:IO多路复用模型是建立在内核提供的多路分离函数select基础之上的,使用select函数 可以避免同步非阻塞IO模型中轮询等待的问题,此外poll、epoll都是这种模型。在该种模式下,用户首先将需要进行IO操作的 socket添加到select中,然后阻塞等待select系统调用返回。当数据到达时,socket被激活,select函数返回。用户线程正式发起 read请求,读取数据并继续执行。从流程上来看,使用select函数进行IO请求和同步阻塞模型没有太大的区别,甚至还多了添加 监视socket,以及调用select函数的额外操作,效率更差。但是,使用select以后最大的优势是用户可以在一个线程内同时处理 多个socket的IO请求。用户可以注册多个socket,然后不断地调用select读取被激活的socket,即可达到在同一个线程内同时处 理多个IO请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。
2.实现函数
2.1 select多路复用
select()函数允许进程指示内核等待多个事件(文件描述符)中的任何一个发生,并只在有一个或多个事件发生或经历一段指定时 间后才唤醒它,然后接下来判断究竟是哪个文件描述符发生了事件并进行相应的处理。
2.2 poll多路复用
select的缺点
- 单个进程能够监视的文件描述符的数量存在最大限制,通常是1024,当然可以更改数量,但由于select采用轮询的方式扫 描文件描述符,文件描述符数量越多,性能越差。
- 内核 / 用户空间内存拷贝问题,select需要复制大量的句柄数据结构,产生巨大的开销。
- select返回的是含有整个句柄的数组,应用程序需要遍历整个数组才能发现哪些句柄发生了事件。
- select的触发方式是水平触发,应用程序如果没有完成对一个已经就绪的文件描述符进行IO操作,那么之后每次select调 用还是会将这些文件描述符通知进程。
poll()的机制与 select() 类 似,与 select() 在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是 poll() 没有最大文件 描述符数量的限制(但是数量过大后性能也是会下降)。
poll函数的原型说明如下:
poll的实现
#include <poll.h>
struct pollfd
{int fd; /* 文件描述符 */short events; /* 等待的事件 */ short revents; /* 实际发生了的事件 */
} ;
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
第一个参数:用来指向一个struct pollfd类型的数组,每一个pollfd结构体指定了一个被监视的文件描述符,指示poll()监视多个文件描述符。每个结构体的events域是监视该文件描述符的事件掩码,由用户来设置这个域。revents域是文件描述符的操作结果事件掩码,内核在调用返回时设置这个域,events域中请求的任何事件都可能在revents域中返回。下表列出指定 events 标志以及测试 revents 标志的一些常值:
常量 | 说明 | 是否能作为 events 的输入 | 是否能作为revents的返回结果 |
---|---|---|---|
POLLIN | 普通或者优先级带数据可读 | 能 | 能 |
POLLRDNORM | 普通数据可读 | 能 | 能 |
POLLRDBAND | 优先级带数据可读 | 能 | 能 |
POLLPRI | 高优先级数据 | 能 | 能 |
POLLOUT | 普通数据可写 | 能 | 能 |
POLLWRNORM | 普通数据可写 | 能 | 能 |
POLLWRBAND | 优先级带数据可写 | 能 | 能 |
POLLERR | 发生错误 | 能 | 能 |
POLLHUP | 发生挂起 | 能 | |
POLLNVAL | 描述字不是一个打开的文件 | 能 |
POLLIN | POLLPRI等价于select()的读事件,POLLOUT |POLLWRBAND等价于select()的写事件。POLLIN等价于POLLRDNORM |POLLRDBAND,而POLLOUT则等价于POLLWRNORM。例如,要同时监视一个文件描述符是否可读和可写,我们可以设置 events为POLLIN |POLLOUT。在poll返回时,我们可以检查revents中的标志,对应于文件描述符请求的events结构体。如果POLLIN事件被设置,则文件描述符可以被读取而不阻塞。如果POLLOUT被设置,则文件描述符可以写入而不导致阻塞。这些标志并不是互斥的:它们可能被同时设置,表示这个文件描述符的读取和写入操作都会正常返回而不阻塞。
第二个参数: nfds 指定数组中监听的元素个数;
第三个参数 :timeout指定等待的毫秒数,无论I/O是否准备好,poll都会返回。timeout指定为负数值表示无限超时,使poll()一直挂起直到一个指定事件发生;timeout为0指示poll调用立即返回并列出准备好I/O的文件描述符,但并不等待其它的事件。这种情况下,poll()就像它的名字那样,一旦选举出来,立即返回。
该函数成功调用时,poll()返回结构体中revents域不为0的文件描述符个数;如果在超时前没有任何事件发生,poll()返回0;
失败时,poll()返回-1,并设置errno为下列值之一:
EBADF | 一个或多个结构体中指定的文件描述符无效。 |
---|---|
EFAULTfds | 指针指向的地址超出进程的地址空间。 |
EINTR | 请求的事件之前产生一个信号,调用可以重新发起。 |
EINVALnfds | 参数超出PLIMIT_NOFILE值。 |
ENOMEM | 可用内存不足,无法完成请求。 |
2.3 epoll多路复用
poll() 和 select() 同样存在一个缺点:
包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增
大。
epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著
提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。epoll除了提供select/poll那种IO事件的水平触发(Level Triggered)外,还提供了边缘触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。
epoll的实现
创建epoll实例:epoll_create()
int epoll_create(int size);//创建epoll实例
系统调用epoll_create()创建了一个新的epoll实例,其对应的兴趣列表初始化为空。若成功返回文件描述符,若出错返回-1。参数size指定了我们想要通过epoll实例来检查的文件描述符个数。该参数并不是一个上限,而是告诉内核应该如何为内部数据结构划分初始大小。从Linux2.6.8版以来,size参数被忽略不用。
作为函数返回值,epoll_create()返回了代表新创建的epoll实例的文件描述符。这个文件描述符在其他几个epoll系统调用中用来表示epoll实例。当这个文件描述符不再需要时,应该通过close()来关闭。当所有与epoll实例相关的文件描述符都被关闭时,实例被销毁,相关的资源都返还给系统。从2.6.27版内核以来,Linux支持了一个新的系统调用epoll_create1()。该系统调用执行的任务同epoll_create()一样,但是去掉了无用的参数size,并增加了一个可用来修改系统调用行为的flags参数。目前只支持一个flag标志:EPOLL_CLOEXEC,它使得内核在新的文件描述符上启动了执行即关闭标志。
修改epoll的兴趣表:epoll_ctl()
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *ev);
系统调用epoll_ctl()能够修改由文件描述符epfd所代表的epoll实例中的兴趣列表。若成功返回0,若出错返回-1。
第一个参数:epfd是epoll_create()的返回值;
第二个参数:op用来指定需要执行的操作,它可以是如下几种值:
- EPOLL_CTL_ADD:将描述符fd添加到epoll实例中的兴趣列表中去。对于fd上我们感兴趣的事件,都指定在ev所指向的结构体中。如果我们试图向兴趣列表中添加一个已存在的文件描述符,epoll_ctl()将出现EEXIST错误;
- EPOLL_CTL_MOD:修改描述符上设定的事件,需要用到由ev所指向的结构体中的信息。如果我们试图修改不在兴趣列表中的文件描述符,epoll_ctl()将出现ENOENT错误;
- EPOLL_CTL_DEL:将文件描述符fd从epfd的兴趣列表中移除,该操作忽略参数ev。如果我们试图移除一个不在epfd的兴趣列表中的文件描述符,epoll_ctl()将出现ENOENT错误。关闭一个文件描述符会自动将其从所有的epoll实例的兴趣列表移除;
第三个参数:fd指明了要修改兴趣列表中的哪一个文件描述符的设定。该参数可以是代表管道、FIFO、套接字、POSIX消息队列、inotify实例、终端、设备,甚至是另一个epoll实例的文件描述符。但是,这里fd不能作为普通文件或目录的文件描述符;
第四个参数:ev是指向结构体epoll_event的指针,结构体的定义如下:
typedef union epoll_data
{void *ptr; /* Pointer to user-defind data */int fd; /* File descriptor */uint32_t u32; /* 32-bit integer */uint64_t u64; /* 64-bit integer */
} epoll_data_t;
struct epoll_event
{uint32_t events; /* epoll events(bit mask) */epoll_data_t data; /* User data */
};
参数ev为文件描述符fd所做的设置(epoll_event)如下:
- events字段是一个位掩码,它指定了我们为待检查的描述符fd上所感兴趣的事件集合;
- data字段是一个联合体,当描述符fd稍后称为就绪态时,联合的成员可用来指定传回给调用进程的信息;
事件等待:epoll_wait()
int epoll_wait(int epfd, struct epoll_event *evlist, int maxevents, int timeout);
系统调用epoll_wait()返回epoll实例中处于就绪态的文件描述符信息,单个epoll_wait()调用能够返回多个就绪态文件描述符的信息。调用成功后epoll_wait()返回数组evlist中的元素个数,如果在timeout超时间隔内没有任何文件描述符处于就绪态的话就返回0,出错时返回-1并在errno中设定错误码以表示错误原因。
第一个参数:epfd是epoll_create()的返回值;
第二个参数:evlist所指向的结构体数组中返回的是有关就绪态文件描述符的信息,数组evlist的空间由调用者负责申请;
第三个参数:maxevents指定所evlist数组里包含的元素个数;
第四个参数:timeout用来确定epoll_wait()的阻塞行为,有如下几种:
- 如果timeout等于-1,调用将一直阻塞,直到兴趣列表中的文件描述符上有事件产生或者直到捕获到一个信号为止。
- 如果timeout等于0,执行一次非阻塞式地检查,看兴趣列表中的描述符上产生了哪个事件。
- 如果timeout大于0,调用将阻塞至多timeout毫秒,直到文件描述符上有事件发生,或者直到捕获到一个信号为止。
数组evlist中,每个元素返回的都是单个就绪态文件描述符的信息。events字段返回了在该描述符上已经发生的事件掩码。data字段返回的是我们在描述符上使用epoll_ctl()注册感兴趣的事件时在ev.data中所指定的值。注意,data字段是唯一可获取这个事件相关的文件描述符的途径。因此,当我们调用epoll_ctl()将文件描述符添加到感兴趣列表中时,应该要么将ev.date.fd设为文件描述符号,要么将ev.date.ptr设为指向包含文件描述符号的结构体。
当我们调用epoll_ctl()时可以在ev.events中指定的位掩码以及由epoll_wait()返回的evlist[].events中的值如下所示:
常量 | 说明 | 作为 epoll_ctl()的输入 | 作为epoll_wait()的返回 |
---|---|---|---|
EPOLLIN | 可读取非高优先级数据 | 能 | 能 |
EPOLLPRI | 可读取高优先级数据 | 能 | 能 |
EPOLLRDHUP | socket对端关闭(始于Linux 2.6.17) | 能 | 能 |
EPOLLOUT | 普通数据可写 | 能 | 能 |
EPOLLET | 采用边沿触发事件通知 | 能 | |
EPOLLONESHOT | 在完成事件通知之后禁用检查 | 能 | |
EPOLLERR | 有错误发生 | 能 | |
POLLHUP | 出现挂断 | 能 |
默认情况下,一旦通过epoll_ctl()的EPOLL_CTL_ADD操作将文件描述符添加到epoll实例的兴趣列表中后,它会保持激活状态(即,之后对epoll_wait()的调用会在描述符处于就绪态时通知我们)直到我们显示地通过epoll_ctl()的EPOLL_CTL_DEL操作将其从列表中移除。如果我们希望在某个特定的文件描述符上只得到一次通知,那么可以在传给epoll_ctl()的ev.events中指定EPOLLONESHOT标志。如果指定了这个标志,那么在下一个epoll_wait()调用通知我们对应的文件描述符处于就绪态之后,这个描述符就会在兴趣列表中被标记为非激活态,之后的epoll_wait()调用都不会再通知我们有关这个描述符的状态了。如果需要,我们可以稍后用过调用epoll_ctl()的EPOLL_CTL_MOD操作重新激活对这个文件描述符的检查。
相关文章:

【APUE】网络socket编程温度采集智能存储与上报项目技术------多路复用
作者简介: 一个平凡而乐于分享的小比特,中南民族大学通信工程专业研究生在读,研究方向无线联邦学习 擅长领域:驱动开发,嵌入式软件开发,BSP开发 作者主页:一个平凡而乐于分享的小比特的个人主页…...

GitHub 仓库 (repository) Pulse - Contributors - Network
GitHub 仓库 [repository] Pulse - Contributors - Network 1. Pulse2. Contributors3. NetworkReferences 1. Pulse 显示该仓库最近的活动信息。该仓库中的软件是无人问津,还是在火热地开发之中,从这里可以一目了然。 2. Contributors 显示对该仓库进…...

C语言题目:阶乘数列求和(函数)
题目描述 输入一个正数x和一个正整数n,求下列算式的值。要求定义两个调用函数:fact(n)计算n的阶乘;mypow(x,n)计算x的n次幂(即xn),两个函数的返回值类型是double。 x - x2/2! x3/3! ... (-1)n-1xn/n! …...

Unity与CocosCreator对比学习二
一、锚点与适配 1.在Creator中 适配通过锚点、位置和Widget达到适配目的;锚点是节点在其父节点坐标系中坐标对其点,其x,y范围在[0, 1]之间; 锚点为(0, 0)时在节点自身的左下角,节点坐标指其左下角在父节点中的坐标;锚…...

01-Git 快速入门
https://learngitbranching.js.org/?localezh_CN在线练习git 1. Git 安装好Git以后, 先检查是否已经绑定了用户名和邮箱 git config --list再检查C:\Users\xxx.ssh 下是否存在 id_rsa.pub , 存在的话复制其内容到 GitHub 的 SSH KEY 中 没有这一步, PUSH操作的时候会报错:…...

Axure RP中的相关概念及高保真原型构建方法
1 Axure RP中概念介绍 对于构建高保真原型来说,需要知道事件(Event)、Case、Action等概念。Axure RP中给出这些概念,是为了方便原型的构建,尤其是高保真原型的构建。 事件(Event)是附着于控件…...

Ruoyi-vue-pro Vue + nginx 二级目录部署到云服务器
http://www.your-server.com/ 这是一级目录,由于项目多,一般会通过二级域名http://oa.your-server.com/或二级目录http://www.your-server.com/oa来发布,本篇记录一下二级目录发布。先看效果 1、router/index.js配置base export default new …...

leetcode2529--正整数和负整数的最大计数
1. 题意 给定有序数组,求其中正整数和负整数的计数最大值。 正整数和负整数的最大计数 2. 题解 2.1 遍历 直接判断 class Solution { public:int maximumCount(vector<int>& nums) {int neg 0;int pos 0;for (int num:nums) {if (!num)continue;i…...

使用YOLOv8训练自己的【目标检测】数据集
文章目录 1.收集数据集1.1 使用开源已标记数据集1.2 爬取网络图像1.3 自己拍摄数据集1.4 使用数据增强生成数据集1.5 使用算法合成图像 2.标注数据集2.1确认标注格式2.2 开始标注 3.划分数据集4.配置训练环境4.1获取代码4.2安装环境 5.训练模型5.1新建一个数据集yaml文件5.2预测…...

rust学习(recursive mutex 实现)
问题: 编写如下代码的时候出现死锁: pub fn test_double_lock() {let t Arc::new(Mutex::new(1));let t1 t.clone();let t2 t.clone();let h std::thread::spawn(move || {println!("hello trace1");let l1 t1.lock().unwrap();println…...

DasViewer可以添加照片到里面吗?点开就可以看照片?
DasViewer主要是三维模型浏览器,二维可以添加矢量和正射影像,航片暂不支持。 DasViewer是由大势智慧自主研发的免费的实景三维模型浏览器,采用多细节层次模型逐步自适应加载技术,让用户在极低的电脑配置下,也能流畅的加载较大规模实景三维模型,提供方便…...

python蓝桥杯选数
文章目录 前言一、题意二、代码1.代码的实现2.读入数据 总结 前言 本题涉及到很多python中的知识点,比如combinations(列表的组合)应用,以及素数的判断 一、题意 已知 n 个整数 x1,x2,…,xn,以及一个整数 k(k&#x…...

联想电脑开启虚拟化失败,开启虚拟化却提示还没有开启虚拟化
安装虚拟机的时候, 电脑要开启虚拟化, Intel VT, 去BIOS开启了, 但是依然报错,说虚拟化处于禁用状态。 解决方案: 去联想官方,下载BIOS更新包,更新BIOS。 更新文档: 联…...

物联网农业四情在线监测系统
TH-Q2随着科技的飞速发展和信息化时代的来临,物联网技术在各个领域都取得了显著的应用成果。其中,物联网农业四情在线监测系统作为农业现代化的重要组成部分,正在为农业生产带来革命性的变革。 一、物联网农业四情在线监测系统的概念 物联网…...

MySQL8.3.0 主从复制方案(master/slave)
一 、什么是MySQL主从 MySQL主从(Master-Slave)复制是一种数据复制机制,用于将一个MySQL数据库服务器(主服务器)的数据复制到其他一个或多个MySQL数据库服务器(从服务器)。这种复制机制可以提供…...

大数据相关组件安装及使用
自学大数据相关组件 持续更新中。。。 一、linux安装docker 1、更新yum sudo yum update2、卸载docker旧版本 sudo yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-engine3、…...

【攻防世界】web2(逆向解密)
进入题目环境,查看页面信息: <?php $miwen"a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws";function encode($str){$_ostrrev($str);// echo $_o;for($_00;$_0<strlen($_o);$_0){$_csubstr($_o,$_0,1);$__ord($_c)1;…...

Linux文件查找命令详解——以CentOS为例
Linux文件查找命令详解——以CentOS为例 在Linux系统中,文件查找是一项非常重要的任务。无论是系统管理员还是普通用户,都需要掌握一些基本的文件查找命令。本文将详细介绍Linux中常用的文件查找命令,并以CentOS为例,展示如何使用…...

【JavaEE】浅谈线程(一)
线程 前言线程的由来线程是什么线程的属性线程更高效的原因举个例子(线程便利性的体现) 多线程代码线程并发执行的代码jconsole(观测多线程) 线程的调度问题创建线程的几种方法1)通过继承Thread 重写run2)使用Runnable接口 重写ru…...

深度解析SPARK的基本概念
关联阅读博客文章: 深入理解MapReduce:从Map到Reduce的工作原理解析 引言: 在当今大数据时代,数据处理和分析成为了企业发展的重要驱动力。Apache Spark作为一个快速、通用的大数据处理引擎,受到了广泛的关注和应用。…...

FreeGPT3.5 开源软件
GPT-3.5不需要付费,也不需要注册用户,可以直接使用了,官方彻底开放了API接口。 该API政策一放开,GitHub很快就已经出现了一个开源项目FreeGPT35,可以自动生成key调用GPT3.5的API接口,再也用不着注册账号和申…...

AI绘本生成解决方案,快速生成高质量的AI绘本视频
美摄科技凭借其深厚的技术积累和前瞻性的市场洞察力,近日推出了一款面向企业的AI绘本生成解决方案,旨在通过智能化、自动化的方式,帮助企业快速将文字内容转化为生动有趣的绘本视频,从而提升内容传播效率,增强品牌影响…...

RabbitMQ3.13.x之九_Docker中安装RabbitMQ
RabbitMQ3.13.x之_Docker中安装RabbitMQ 文章目录 RabbitMQ3.13.x之_Docker中安装RabbitMQ1. 官网2. 安装1 .拉取镜像2. 运行容器 3. 访问 1. 官网 rabbitmq - Official Image | Docker Hub 2. 安装 1 .拉取镜像 docker pull rabbitmq:3.13.0-management2. 运行容器 # lates…...

【操作系统】STM32-操作系统——持续更新
【操作系统】STM32-操作系统——持续更新 文章目录 前言一、ucosii二、freertos1.介绍2.移植 总结 前言 提示:以下是本篇文章正文内容,下面案例可供参考 一、ucosii UCOSII移植到STM32F103C8T6上之移植记录(一) UCOSII移植到ST…...

Redux Toolkit+TypeScript最佳实践
Redux-Toolkit是为了简化使用Redux繁琐的步骤,可以j降低使用useReducer与useContext管理状态的频率,而且起到项目中状态管理规范和约束化的效果。 阅读本文需要的前置知识:React、Redux、Typescript、Redux hooks。 Redux-Toolkit使用步骤 …...

假期别闲着:REST API实战演练之创建Rest API
1、创建实体类,模拟实体对象 创建一个类,模拟数据数据库来存储数据,这个类就叫Person。 其代码如下: package com.restful;public class Person {private String name;private String about;private int birthYear;public Perso…...

C++模仿qq界面
#include "mywidget.h"MyWidget::MyWidget(QWidget *parent): QWidget(parent) {//设置窗口的大小this->resize(645,497);//设置窗口名字this->setWindowTitle("QQ");//设置窗口图标this->setWindowIcon(QIcon("C:\\zhouzhouMyfile\\qt_proj…...

3D模型在线轻量化工具
在计算机图形学领域,3D模型简化工具是一种强大的工具,用于减少模型的面数,以提高模型在渲染和处理过程中的性能。本文将全面介绍为何需要简化模型、简化的方法、常见的简化算法以及一款三维模型优化产品 的使用方法,帮助读者更好地…...

去中心化社交媒体:分析 Facebook 在区块链平台上的角色
在当今数字时代,社交媒体已经成为人们日常生活中不可或缺的一部分。然而,随着人们对数据隐私和信息控制的关注不断增加,传统的中心化社交媒体平台也面临着越来越多的质疑和挑战。为了应对这些挑战,越来越多的人开始探索去中心化社…...

实现多租户JAVA支付(微信拉起支付):一个简单而强大的解决方案
简介: 本篇文章为多租户场景 在当今数字经济时代,许多企业都面临着处理多租户支付的挑战。多租户系统是指一种架构,其中单个实例的软件服务多个租户,每个租户的数据通常被隔离,以确保安全性和数据隐私。而在这种环境…...