news 2026/9/12 19:18:18

IO认识与Select

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
IO认识与Select
@bit::Shadow
✧(≖ ◡ ≖✿

目录

IO认识

同步 VS 异步

阻塞 VS非阻塞

fcntl

常用cmd

F_GETFL与F_GETFD

举例

read返回值-1

Select

原型

timeout

fd_set结构

Select通信原理

0.怎么解决单进程下复原问题?(三状态参数)

1.怎么区分新链接与信息?

Echo Server🔗

FD_ISSET

第一个参数fd

正确判断


IO认识

IO分作:等待 + 拷贝

先前直接使用的recv / read / readfrom / get / fread等读取均是IO缓慢,因为它们大多数时间都在“等待”!

同步 VS 异步

同步通信 vs 异步通信(synchronous communication/ asynchronous communication)

同步和异步关注的是消息通信机制。

  • 所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到得到结果了;换句话说,就是由调用者主动等待这个调用的结果;
  • 异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果;换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果;而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用

阻塞 VS非阻塞

阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态。

  • 阻塞IO:在内核将数据准备好之前,系统调用会一直等待。所有的套接字,默认都是阻塞的方式。阻塞IO是最常见的IO方式。
  • 非阻塞IO:如果内核还未将数据准备好,系统调用仍会直接返回,并且返回EWOULDBLOCK错误码。

注意:非阻塞IO往往需要程序员反复尝试读写文件描述符,这个过程称之为轮询。这对CPU来讲是极大的浪费,一般只有特定场景下才会使用。

fcntl

fcntl(File Control):是设置阻塞/非阻塞IO的瑞士军刀。

#include <fcntl.h> #include <unistd.h> int fcntl(int fd, int cmd, ...);

fd:设置阻塞/非阻塞的文件对应描述符。
cmd:可变参数,本质上是宏设置。

第二位及以后参数参数

第二位必须是“命令”像:F_GETFL 或 F_SETFL 而第三位必须是“状态类型”像:O_NONBLOACK。二者绝不可以为了省事而直接按位或全部一次性传入第二个参数

常用cmd

F_GETFL/F_SETFL获得/设置文件描述符状态标记。
O_NONBLOCK设置非阻塞IO
F_DUPFD复制一个现有的文件描述符。
F_GETFD/F_SETFD获得/设置文件描述符标记.
获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN).
获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW)

F_GETFL //获取fd状态 (常用来获取fd原状态)
F_SETFL //设置状态 (常用来设置其他状态像:O_NONBLOCK)

F_GETFL与F_GETFD

  • F_GETFL(Get File Status Flags):操作的是文件描述符的属性

    • 它获取的是该描述符的访问模式(只读、只写、读写)和状态标志(如追加O_APPEND、非阻塞O_NONBLOCK、同步O_SYNC等)。

    • 这些标志是在open()系统调用时设定的,并且会影响后续的read()write()操作。

  • F_SETFD(Set File Descriptor Flags):操作的是文件描述符本身的标志

    • 它主要设置的是FD_CLOEXEC(Close-on-exec)标志。

    • 这个标志不影响读写,只影响当进程执行exec系列函数时,该描述符是否会被自动关闭。(防止子进程继承不必要的文件描述符(安全加固)仅有FD_CLOEXEC(实际开发中几乎只用这个))

举例

#include<unistd.h> #include<fcntl.h> void SetNoBlock(int fd) { //1.获取原fd描述,由于保留原fd状态信息 int fl = fcntl(fd, F_GETFL); if(fl < 0) { perror("fcntl"); return; } //2.设置状态标记 fcntl(fd, F_SETFL, fl | O_NONBLOCK); }
exec系列函数回顾

exec系列是Unix/Linux中替换进程映像的系统调用组。它们的作用是用一个新的程序替换当前进程的代码段、数据段、堆和栈,但保留进程的PID、文件描述符表、信号处理等。

#include <unistd.h> int execl (const char *path, const char *arg, ..., NULL); int execlp(const char *file, const char *arg, ..., NULL); int execle(const char *path, const char *arg, ..., NULL, char *const envp[]); int execv (const char *path, char *const argv[]); int execvp(const char *file, char *const argv[]); int execvpe(const char *file, char *const argv[], char *const envp[]);
// 1. execl:逐个列出参数,以 NULL 结尾 execl("/bin/ls", "ls", "-l", "/home", NULL); // 2. execlp:不用写完整路径,自动搜 PATH execlp("ls", "ls", "-l", "/home", NULL); // 3. execle:自己指定环境变量 char *envp[] = {"PATH=/bin:/usr/bin", "USER=ubuntu", NULL}; execle("/bin/ls", "ls", "-l", "/home", NULL, envp);
特点:返回必然错误



fcntl后read返回值-1

read返回-1有两种可能:

1.出错(像:文件描述符关闭)

2.数据未就绪(在非阻塞情况下,read后因为是非阻塞读取,当未及时发送数据就会导致“数据未就绪”)

无论何种情况错误码均被设置

🔗错误码认识 C/C++进阶知识6.0(必要)

//测试非阻塞IO #include<iostream> #include<unistd.h> #include<fcntl.h> using namespace std; void SetNoBlock (int fd) { int fl = fcntl (fd, F_GETFL);//获取fd的默认描述码 if(fl < 0) { perror("fcntl"); return; } fcntl (fd, F_SETFL, fl | O_NONBLOCK);//保持fd的原权限码+非阻塞0 // 注意此处的独立 } int main() { // cout << sizeof (fd_set) * 8 << endl;//1024大小 SetNoBlock (0); while(true) { char buf[1024] = { 0 }; int n = read (0, buf, 1023); if(n > 0) { //正常读取到了 cout << buf; } else if(n < 0) { //n < 0 //1.出错了 2.底层没有数据准备好 if(errno == EAGAIN || errno == EWOULDBLOCK) //含义均是"现在无数据,请稍后再试",使用一个就够了 { sleep (1); cout << "数据没有准备好..." << endl; continue; } else if(errno == EINTR ) { cout << "被信号打断" << endl; continue; } //真正读取出错 break; } else { //原为读到文件(0)末尾。因为是终端末尾(Ctrl+d) cout << "末尾n:" << n << endl; break; } } }

.mp4效果演示

fcntl非阻塞IO




Select

“一种兼容性极好,较为古老的IO模式”

select系统调用是用来让我们的程序监视多个文件描述符的状态变化的;

原型

#include <sys/select.h> #include <sys/time.h> #include <sys/types.h> #include <unistd.h> int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

对于select正常返回后的fd默认情况下,读事件不就绪,写事件是就绪的

nfds:等待的所有文件描述符中最大的值加1后的值。

timeout

输入输出型参数

struct timeval { time_t tv_sec; //单位:秒 time_t tv_usec; //单位:毫秒 }

①nullptr:阻塞性一旦有某个fd就绪就 . ↓ .,如果如果当时没有就绪的就阻塞。

/*了解fd的遍历*/ // 1. schedule() 返回(进程重新获得CPU) // 2. 从 do_select_wait() 的 schedule() 后继续执行 // 3. 移除等待队列条目 // 4. 重新遍历fd集合,检查哪些就绪了 // 5. 返回就绪的fd数量 // 6. 返回到用户态

②0:非阻塞性仅检测一轮若无就绪就立即返回。(不再等待)

③ > 0:作为输入的等待最大时间容忍度,输出时输出被消耗后的剩余时间

fd_set结构

fd_set是一种位图结构,作为输入输出型参数。(基于不同的Linux系统可能会有差异)

输入:用户告诉内核位图上为1的值对应的下标就是你要关心的相应的 fd 值。
输出:表示关心的fd内已经状态就绪(设置为1)的fd是哪些。

此处的“状态”指参数位于参数对应的状态情况,分别是读、写、异常。

返回值 r

当新链接到达后也是“立即返回!!!”返回值 > 0 。

  • r > 0:有 r 个 fd 就绪了。
  • r == 0:超时。(因为当为nullptr时为阻塞性所以没有这种情况)
  • r < 0:出错(像打开一个从未打开的文件描述符)

注意:select由于资历较老,所以其兼容性极好。

Select通信原理

实现了单进程/线程多路IO

0.怎么解决单进程下复原问题?(三状态参数)

加入辅助数组 int fd_array[8*sizeof(fd_set)] = -1;

select前将上一轮或许新纳入了的fd纳入状态参数。

1.怎么区分新链接与信息?

答:根据三状态参数 均是输入输出型参数,可知若是新链接则一定select后对应辅助数组参数就绪,因此应先判断listenfd对应的fd_array[0]是否就绪。

就绪:新链接到来。(因为listenfd是监听fd)

未就绪:信息到来

注意:创建fd_set后一定要第一时间初始化FD_ZERO()

Echo Server🔗

必看核心是Dispatch函数的因果逻辑

实现基于select的echo server

注意:TCP与UDP的差异

测试代码:

int n = select (_sockfd + 1, &rfds, nullptr, nullptr, nullptr);//阻塞性 if(n > 0) { //新链接到来/链接发送了信息!!! // Dispatch (buf); // 派遣 cout << "test"; continue; }

我发了一个信息为什么一直打印?不应该打印一次后阻塞吗?

答:因为没有服务端没有将接收缓冲区内的数据读走!!!

意识:read不仅仅是拷贝还将接收缓冲区内数据清除!!!

监听套接字是用来监听是否有新链接的,不能作为通信套接字否则:

FD_ISSET

FD_ISSET的底层含位运算,使用fd参数不可以带负数。

第一个参数fd

FD_ISSET的第一个参数可能是-1无法进行位运算

正确判断

//正确写法 if(FD_ISSET (fd_array[i], &rfds)) //错误写法 // if (FD_ISSET (fd_array[i], &rfds) != 0) // 在集合rfds中返回非零 // if (FD_ISSET (fd_array[i], &rfds) == true) // 在集合rfds中返回非零

注意是输入输出型参数。

select特点与缺点

特点

1.可检测文件描述符的个数取决于sizeof(fd_set)值。ubuntu账号sizeof(fd_set) == 128,每字节8比特共计128*8 = 1024个文件描述符。

2.将需要监控状态的fd(rfds wfds efds)符加入select监控后常需额外一个数据结构存储历史关心情况。

3.每次调用select都会将rfd/wfd/efd集合从用户态拷贝到内核态,这个开销在fd很多时会大很多。

缺点

1.每次调用select,都需要手动设置fd集合,很不方便。

2.select支持的文件描述符太少。

下节我们将讲述poll的使用



感谢支持,长期连载
欢迎关注

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 19:15:26

安路EG4S20开发板实战:国产FPGA从入门到仿真调试全记录

1. 为什么我会在2024年选择安路EG4S20这块板子1.1 从"国产FPGA能不能用"到"真香"的心路历程先说结论&#xff1a;这块板子彻底改变了我对国产FPGA的固有印象。我以前一直在用Xilinx和Altera&#xff08;现在叫Intel&#xff09;的芯片&#xff0c;手里攒了…

作者头像 李华
网站建设 2026/9/12 19:15:19

工业以太网多参量传感器技术与应用解析

1. 以太网多参量传感器的工业适配性解析在工业自动化领域&#xff0c;传感器就像设备的"感官神经"&#xff0c;而以太网多参量传感器则是这个神经网络中最强大的神经元。这类传感器能同时测量温度、压力、流量、振动等多种参数&#xff0c;并通过以太网实时传输数据。…

作者头像 李华
网站建设 2026/9/12 19:15:14

必备专业干货!4款AI专著撰写工具推荐,高效完成20万字专著

AI助力学术专著写作&#xff0c;开启高效新时代 写学术专著并不简单&#xff0c;不只是要把内容写出来&#xff0c;还得能够顺利出版并得到认可。在现实的出版环境里&#xff0c;学术专著的读者群比较小&#xff0c;出版社对选题的学术价值和作者的学术影响都有很高的要求。很…

作者头像 李华
网站建设 2026/9/12 19:14:30

2026湘西化工产品成分分析检测排名 TOP5 CMA 资质提供含量检测、纯度检测、元素分析 联系方式推荐

湘西街头巷尾&#xff0c;化工产品成分分析检测机构鳞次栉比&#xff0c;看似选择众多&#xff0c;实则鱼龙混杂。化工企业、新材料厂商、日化生产工厂、橡塑制造业乃至食品医药企业的研发质检部门&#xff0c;稍有不慎便可能筛选到无正规资质的检测机构。这类机构出具的成分分…

作者头像 李华
网站建设 2026/9/12 19:14:23

IRG知识蒸馏实战:用特征关系图将ResNet50压缩进ResNet18

简介&#xff1a;知识蒸馏IRG算法实战配套源码包&#xff0c;面向具备一定深度学习基础、希望掌握特征蒸馏与模型压缩技巧的研究者与工程师。资源围绕ResNet50作为教师网络、ResNet18作为学生网络的IRG蒸馏流程&#xff0c;提供完整的Python实现与中间结果&#xff0c;可辅助理…

作者头像 李华
网站建设 2026/9/12 19:13:56

粒子群算法优化碳捕集微电网调度实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华