IO认识与Select

发布时间:2026/9/12 19:18:56
IO认识与Select bit::Shadow✧(≖ ◡ ≖✿目录IO认识同步 VS 异步阻塞 VS非阻塞fcntl常用cmdF_GETFL与F_GETFD举例read返回值-1Select原型timeoutfd_set结构Select通信原理0.怎么解决单进程下复原问题三状态参数1.怎么区分新链接与信息Echo ServerFD_ISSET第一个参数fd正确判断IO认识IO分作等待 拷贝先前直接使用的recv / read / readfrom / get / fread等读取均是IO缓慢因为它们大多数时间都在“等待”同步 VS 异步同步通信 vs 异步通信(synchronous communication/ asynchronous communication)同步和异步关注的是消息通信机制。所谓同步就是在发出一个调用时在没有得到结果之前该调用就不返回。但是一旦调用返回就得到得到结果了换句话说就是由调用者主动等待这个调用的结果异步则是相反调用在发出之后这个调用就直接返回了所以没有返回结果换句话说当一个异步过程调用发出后调用者不会立刻得到结果而是在调用发出后被调用者通过状态、通知来通知调用者或通过回调函数处理这个调用阻塞 VS非阻塞阻塞和非阻塞关注的是程序在等待调用结果消息返回值时的状态。阻塞IO在内核将数据准备好之前系统调用会一直等待。所有的套接字默认都是阻塞的方式。阻塞IO是最常见的IO方式。非阻塞IO如果内核还未将数据准备好系统调用仍会直接返回并且返回EWOULDBLOCK错误码。注意非阻塞IO往往需要程序员反复尝试读写文件描述符这个过程称之为轮询。这对CPU来讲是极大的浪费一般只有特定场景下才会使用。fcntlfcntlFile Control是设置阻塞/非阻塞IO的瑞士军刀。#include fcntl.h #include unistd.h int fcntl(int fd, int cmd, ...);fd:设置阻塞/非阻塞的文件对应描述符。cmd可变参数本质上是宏设置。第二位及以后参数参数第二位必须是“命令”像F_GETFL 或 F_SETFL 而第三位必须是“状态类型”像O_NONBLOACK。二者绝不可以为了省事而直接按位或全部一次性传入第二个参数常用cmdF_GETFL/F_SETFL获得/设置文件描述符状态标记。O_NONBLOCK设置非阻塞IOF_DUPFD复制一个现有的文件描述符。F_GETFD/F_SETFD获得/设置文件描述符标记.获得/设置异步I/O所有权(cmdF_GETOWN或F_SETOWN).获得/设置记录锁(cmdF_GETLK,F_SETLK或F_SETLKW)F_GETFL //获取fd状态 常用来获取fd原状态F_SETFL //设置状态 常用来设置其他状态像O_NONBLOCKF_GETFL与F_GETFDF_GETFL(Get File Status Flags)操作的是文件描述符的属性。它获取的是该描述符的访问模式只读、只写、读写和状态标志如追加O_APPEND、非阻塞O_NONBLOCK、同步O_SYNC等。这些标志是在open()系统调用时设定的并且会影响后续的read()、write()操作。F_SETFD(Set File Descriptor Flags)操作的是文件描述符本身的标志。它主要设置的是FD_CLOEXECClose-on-exec标志。这个标志不影响读写只影响当进程执行exec系列函数时该描述符是否会被自动关闭。防止子进程继承不必要的文件描述符安全加固仅有FD_CLOEXEC实际开发中几乎只用这个举例#includeunistd.h #includefcntl.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, USERubuntu, NULL}; execle(/bin/ls, ls, -l, /home, NULL, envp);特点返回必然错误fcntl后read返回值-1read返回-1有两种可能1.出错像文件描述符关闭2.数据未就绪在非阻塞情况下read后因为是非阻塞读取当未及时发送数据就会导致“数据未就绪”无论何种情况错误码均被设置错误码认识 C/C进阶知识6.0必要//测试非阻塞IO #includeiostream #includeunistd.h #includefcntl.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)末尾。因为是终端末尾(Ctrld) cout 末尾n: n endl; break; } } }.mp4效果演示fcntl非阻塞IOSelect“一种兼容性极好较为古老的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通信原理实现了单进程/线程多路IO0.怎么解决单进程下复原问题三状态参数加入辅助数组 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_ISSETFD_ISSET的底层含位运算使用fd参数不可以带负数。第一个参数fdFD_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.将需要监控状态的fdrfds wfds efds符加入select监控后常需额外一个数据结构存储历史关心情况。3.每次调用select都会将rfd/wfd/efd集合从用户态拷贝到内核态这个开销在fd很多时会大很多。缺点1.每次调用select都需要手动设置fd集合很不方便。2.select支持的文件描述符太少。下节我们将讲述poll的使用感谢支持长期连载欢迎关注