当前位置: 首页 > news >正文

嵌入式解谜日志—多路I/O复用

多路 I/O复用(Multiplexed I/O):

1.定义:系统提供的I/O事件通知机制

2.应用:是一种 I/O 编程模型,用于在单线程中同时处理多个(阻塞) I/O 操作,避免因等待某个 I/O 操作完成而阻塞整个程序的执行,及时处理。

3.I/O模型:①阻塞I/O(默认),闲等待()没有就等待

                  ②非阻塞(fcntl,NONBLOCK)I/O,忙等待(不停询问)。cpu使用率高

                  ③信号驱动I/O(不要求)SIGIO:使用少

                  ④并发I/O:进程线程(开销大,浪费内存)

                 ⑤多路I/O:select,epoll,poll

一,基于 有名管道(FIFO) 的简单 IO 交互程序,核心功能是同时监听 “有名管道” 和 “终端输入”,并分别打印接收到的数据。下面从 功能逻辑、关键技术、代码细节 三方面逐步解析

(1)核心功能总览

程序的本质是一个 “双源数据监听器”:

  1. 先创建一个名为 myfifo 的命名管道(用于进程间通信);
  2. 以 “只读 + 非阻塞” 模式打开管道,同时监听终端输入(标准输入 stdin);
  3. 进入死循环,不断尝试:
    • 从管道读取数据(非阻塞,没数据也不卡),读到就打印;
    • 从终端读取用户输入(默认阻塞),读到就打印;
  4. 实现 “管道数据” 和 “终端输入” 的并行处理(本质是轮询非阻塞 IO)。

读:

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int	main(int argc, char **argv)
{//创建有名管道int ret=mkfifo("myfifo",0666);{if(EEXIST==errno)//管道已存在不报错(正常情况){}else//其他错误,报错退出{perror("mkfifo errro\n");return 1;}}//打开管道并设置“非阻塞模式”//一只读模式打开管道int fd=open("myfifo",O_RDONLY);if(-1==fd){perror("open error\n");return 1;}//将管道设置为“非阻塞IO”模式int flag=fcntl(fd,F_GETFL,0);//获取当前fd的状态(阻塞,只读)fcntl(fd,F_SETFL,flag|O_NONBLOCK);// F_SETFL:设置标志,在原有的基础上添加 O_NONBLOCK(非阻塞)
//fileno():将流指针转换成整型flag=fcntl(fileno(stdin),F_GETFL,0);//获取到了输入端的状态值//死循环监听数据while(1){char buf[512]={0};if(read(fd,buf,sizeof(buf)>0))//将fd中的数据读到buf里面{printf("fifo:%s\n",buf);}//清空缓冲区buf,准备接收终端输入bzero(buf,sizeof(buf));// ② 从终端读取用户输入(默认阻塞)if (fgets(buf, sizeof(buf), stdin)){// fgets返回非NULL:读到了输入printf("terminal:%s", buf);        // 打印终端输入fflush(stdout);                    // 强制刷新 stdout(避免输出缓存)}}close(fd);//system("pause");return 0;
}
  • fcntl 作用:修改文件描述符的属性(File Control):

    • F_GETFL:获取当前 fd 的状态(比如是否是阻塞、只读 / 写等);
    • flag | O_NONBLOCK:在原有标志基础上,添加 “非阻塞” 标志(O_NONBLOCK)—— 设置后,read(fd, ...) 若没数据,不会阻塞等待,而是立即返回 -1 并设置 errno=EAGAIN

写:

#include <errno.h>      // 提供错误码定义,用于错误处理
#include <fcntl.h>      // 提供文件控制操作函数(如open)的声明
#include <stdio.h>      // 标准输入输出函数
#include <stdlib.h>     // 标准库函数(如exit)
#include <string.h>     // 字符串处理函数(如strlen)
#include <sys/stat.h>   // 提供文件状态相关定义(如mkfifo所需的权限位)
#include <sys/types.h>  // 提供基本系统数据类型定义
#include <unistd.h>     // 提供POSIX操作系统API(如write、sleep、close)int main(int argc, char *argv[])
{// 创建名为"myfifo"的命名管道,权限为0666(所有用户可读写)int ret = mkfifo("myfifo", 0666);// 检查mkfifo调用是否失败if (-1 == ret){// 如果错误码是EEXIST,表示FIFO已存在,属于正常情况,不做处理if (EEXIST == errno){// 空语句块:已存在则无需重新创建,继续执行后续代码}// 其他错误情况(如权限不足),打印错误信息并退出else{perror("mkfifo error\n");  // 打印具体错误原因return 1;                  // 非0返回值表示程序异常退出}}// 以只写模式(O_WRONLY)打开FIFO,获取文件描述符int fd = open("myfifo", O_WRONLY);// 检查open调用是否失败if (-1 == fd){perror("open error\n");  // 打印打开失败的原因return 1;                // 异常退出}// 无限循环:持续向FIFO写入数据while (1){// 定义缓冲区并初始化要发送的字符串char buf[512] = "hello,this is fifo tested...\n";// 向FIFO写入数据:参数为文件描述符、缓冲区、数据长度(包含结束符)write(fd, buf, strlen(buf) + 1);//strlen(buf) + 1确保包含字符串结束符\0// 暂停3秒,控制发送频率,使程序每 3 秒写入一次数据sleep(3);}// 关闭文件描述符(注:由于上面是无限循环,此句实际永远不会执行)close(fd);return 0;  // 程序正常退出(实际不会执行到此处)
}

二,信号驱动I/O:

使用命名管道(FIFO)进行异步读取的程序,它通过信号机制实现当 FIFO 中有数据到来时进行处理

写:

#include <errno.h>      // 错误处理相关定义
#include <fcntl.h>      // 文件控制操作(fcntl等)
#include <signal.h>     // 信号处理相关函数和定义
#include <stdio.h>      // 标准输入输出
#include <stdlib.h>     // 标准库函数
#include <string.h>     // 字符串处理函数
#include <sys/stat.h>   // 文件状态相关定义
#include <sys/types.h>  // 基本系统数据类型
#include <unistd.h>     // POSIX系统APIint fd;  // 全局文件描述符,供信号处理函数使用// 信号处理函数:当接收到SIGIO信号时被调用
void myhandle(int num)
{char buf[512] = {0};// 从FIFO读取数据read(fd, buf, sizeof(buf));// 打印从FIFO接收到的数据printf("fifo :%s\n", buf);
}int main(int argc, char *argv[])
{// 注册SIGIO信号的处理函数为myhandlesignal(SIGIO, myhandle);// 创建命名管道"myfifo",权限0666int ret = mkfifo("myfifo", 0666);if (-1 == ret){// 如果管道已存在,不做处理if (EEXIST == errno){}// 其他错误则打印信息并退出else{perror("mkfifo error\n");return 1;}}// 以只读模式打开FIFOfd = open("myfifo", O_RDONLY);if (-1 == fd){perror("open error\n");return 1;}// 获取文件描述符当前的标志int flag = fcntl(fd, F_GETFL);// 设置文件描述符为异步模式(O_ASYNC)fcntl(fd, F_SETFL, flag | O_ASYNC);// 设置异步I/O的所有者为当前进程,当有数据时内核会向该进程发送SIGIO信号fcntl(fd, F_SETOWN, getpid());// 主循环:持续从终端读取输入并打印while (1){char buf[512] = {0};bzero(buf, sizeof(buf));  // 清空缓冲区// 从标准输入读取数据fgets(buf, sizeof(buf), stdin);// 打印终端输入的数据printf("terminal:%s", buf);fflush(stdout);  // 刷新输出缓冲区}// 关闭文件描述符(实际不会执行,因为上面是无限循环)close(fd);// remove("myfifo");  // 注释掉了删除FIFO的操作return 0;
}

程序核心功能说明:

  1. 异步 I/O 处理机制

    • 使用fcntl设置 FIFO 为异步模式(O_ASYNC
    • 当 FIFO 中有数据到达时,内核会自动向进程发送SIGIO信号
    • 注册了SIGIO信号的处理函数myhandle,在信号到来时读取并处理数据
  2. 程序工作流程

    • 创建并打开 FIFO(只读模式)
    • 配置 FIFO 为异步通知模式,并设置当前进程为接收通知的进程
    • 主循环负责从终端读取用户输入并打印
    • 当有数据写入 FIFO 时,触发SIGIO信号,调用myhandle读取并打印 FIFO 中的数据
  3. 特点

    • 实现了非阻塞式的 FIFO 读取,主程序可以同时处理其他任务(这里是终端输入)
    • 信号驱动的 I/O 模型提高了效率,不需要主动轮询 FIFO 状态

读:

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char *argv[])
{int ret = mkfifo("myfifo", 0666);if (-1 == ret){if (EEXIST == errno){}else{perror("mkfifo error\n");return 1;}}int fd = open("myfifo", O_WRONLY);if (-1 == fd){perror("open error\n");return 1;}while (1){char buf[512] = "hello,this is fifo tested...\n";write(fd, buf, strlen(buf) + 1);sleep(3);}close(fd);return 0;
}

三,I/O多路复用:select,ep

(1) select函数:

        通过监听一组文件描述符(fd_set),来判断其中是否有描述符就绪(可读、可写或异常)。程序会阻塞在 select 调用上,直到有描述符就绪或者超时。

核心功能:帮你同时盯着多个 “消息入口”(文件描述符:比如套接字、键盘输入),对应的信息放入对应的集合里,等某个 / 某些入口有消息了,再通知你处理 —— 避免你自己挨个蹲守,让程序更高效、不卡顿。

select 是最早的多路 I/O 实现方式,在 POSIX 标准中定义,具有较好的跨平台性(在 Linux、Windows 等系统中都有支持)。

1.使用步骤:①创建集合

                  ②加入fd;

                  ③select轮询

                  ④找到对应的fd  r/w

2. 函数原型
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

返回值:就绪的文件描述符数量;-1 表示出错;0 表示超时

3. 核心参数
  • nfds:需监听的最大文件描述符 + 1(因 FD 从 0 开始编号)。
  • readfds:监听 “可读事件” 的 FD 集合(输入参数,内核修改后返回就绪 FD)。
  • writefds:监听 “可写事件” 的 FD 集合(同上)。
  • exceptfds:监听 “异常事件” 的 FD 集合(同上)。
  • timeout:超时时间(NULL 表示永久阻塞;struct timeval{0,0} 表示非阻塞;其他值表示等待指定时间)。

select 阻塞等待,程序暂停,不占用 CPU

4. 辅助宏(操作 FD 集合)
FD_ZERO(fd_set *set);      // 清空 FD 集合
FD_SET(int fd, fd_set *set);  // 将 FD 添加到集合
FD_CLR(int fd, fd_set *set);  // 从集合中移除 FD
FD_ISSET(int fd, fd_set *set); // 检查 FD 是否在就绪集合中(返回非 0 表示就绪)

基本原理
select 函数通过监听一组文件描述符(fd_set),来判断其中是否有描述符就绪(可读、可写或异常)。程序会阻塞在 select 调用上,直到有描述符就绪或者超时。

5. 特点与局限
  • 优点:跨平台性好,接口简单,适合监听少量 FD(如 < 1000)。
  • 缺点
    • FD 数量限制(默认最大 1024,由 FD_SETSIZE 定义)。
    • 每次调用需将 FD 集合从用户空间拷贝到内核空间,效率低。
    • 需遍历所有 FD 才能判断就绪状态(时间复杂度 O (n))。

使用 select 多路复用机制同时监听管道(fifo)和标准输入(终端)的程序,功能与之前的 epoll 版本类似,但使用了不同的 I/O 多路复用技术。

读端:重点处理

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>/* 包含select相关头文件 */
/* 根据POSIX.1-2001, POSIX.1-2008标准 */
#include <sys/select.h>/* 根据早期标准 */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>int main(int argc, char *argv[])
{// 创建命名管道"myfifo",权限为0666(所有用户可读写)int ret = mkfifo("myfifo", 0666);if (-1 == ret)  // 创建失败{if (EEXIST == errno)  // 错误为"文件已存在",属于正常情况,不处理{}else  // 其他错误(如权限不足),输出错误信息并退出{perror("mkfifo error\n");return 1;}}// 以只读方式打开管道文件int fd = open("myfifo", O_RDONLY);if (-1 == fd)  // 打开失败{perror("open error\n");return 1;}// 定义select所需的文件描述符集合// rd_set:用于select调用的临时集合(会被select修改)// tmp_set:保存初始集合(用于每次循环恢复rd_set)fd_set rd_set, tmp_set;// 初始化集合,清空所有位FD_ZERO(&rd_set);FD_ZERO(&tmp_set);// 向集合中添加需要监听的文件描述符(对应集合类型)FD_SET(0, &tmp_set);    // 添加标准输入(终端,文件描述符为0)FD_SET(fd, &tmp_set);   // 添加管道文件描述符// 事件循环:持续监听输入事件while (1){// 每次循环前,从备份集合恢复rd_set// 因为select会修改集合,只保留就绪的文件描述符rd_set = tmp_set;// 调用select等待事件发生// 参数1:最大文件描述符值+1(fd是管道描述符,比0大)// 参数2:监听读事件的集合// 参数3、4:NULL,表示不监听写事件和异常事件// 参数5:NULL,表示无限期等待,直到有事件发生select(fd + 1, &rd_set, NULL, NULL, NULL);// 缓冲区,用于存储读取的数据char buf[512] = {0};// FD_ISSET函数:检查管道是否有数据可读(文件描述符在就绪集合中)if (FD_ISSET(fd, &rd_set))//如果fd文件准备就绪,就执行他{// 从管道读取数据read(fd, buf, sizeof(buf));// 打印管道中的数据printf("fifo :%s\n", buf);}// 检查终端(标准输入)是否有输入if (FD_ISSET(0, &rd_set))//如果终端有输入,就执行他{// 清空缓冲区bzero(buf, sizeof(buf));// 从终端读取一行输入fgets(buf, sizeof(buf), stdin);// 打印终端输入的数据printf("terminal:%s", buf);// 刷新标准输出缓冲区,确保内容立即显示fflush(stdout);}}// 关闭管道文件描述符(实际运行中循环不会退出,此句很少执行)close(fd);// 注释:删除管道文件(此处注释掉,保留文件供后续测试)// remove("myfifo");return 0;
}

写端:

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char *argv[])
{int ret = mkfifo("myfifo", 0666);if (-1 == ret){if (EEXIST == errno){}else{perror("mkfifo error\n");return 1;}}int fd = open("myfifo", O_WRONLY);if (-1 == fd){perror("open error\n");return 1;}while (1){char buf[512] = "hello,this is fifo tested...\n";write(fd, buf, strlen(buf) + 1);sleep(3);}close(fd);return 0;
}

(2)epoll 函数(Linux 特有):精准告诉你 “哪些设备有数据要处理了”,让程序不用瞎等、不用瞎查,直接处理有动静的设备就行。(加强的select)

epoll 是 Linux 专为高并发设计的多路 I/O 复用机制,性能远超 select/poll,支持海量 FD 且时间复杂度为 O (1)。

(1)epoll 工作流程总结

  1. 创建实例:通过 epoll_create 创建 epoll 实例(epfd),默认创建两个集合(二叉树);
  2. 注册事件:通过 epoll_ctl 向 epfd 中添加需要监听的 FD 及事件(如 EPOLLIN)结构体;
  3. 等待就绪:通过 epoll_wait 阻塞等待,内核自动将就绪事件写入 events 数组;
  4. 处理事件:遍历 events 数组,根据就绪的 FD 和事件类型(如可读)进行处理;
  5. 循环监听:重复步骤 3~4,持续处理新的就绪事件。

(2)核心函数详解

1. epoll_create:创建 epoll 实例

功能:在内核中创建一个 epoll 实例(事件表),用于管理后续需要监听的文件描述符(FD)和事件。
原型

#include <sys/epoll.h>
int epoll_create(int size);

参数

  • size:早期版本用于指定监听的 FD 数量上限,现在已废弃(内核不再使用此值),传入任意正整数即可(通常传 1)。

返回值

  • 成功:返回一个 epoll 实例的文件描述符(epfd),后续操作通过该描述符进行;
  • 失败:返回 -1,并设置 errno(如 ENFILE 表示系统文件描述符耗尽)。
int epfd = epoll_create(1);  // 创建 epoll 实例
if (epfd == -1) {perror("epoll_create failed");exit(EXIT_FAILURE);
}
2. epoll_ctl:管理 epoll 实例中的事件

功能:向 epoll 实例中添加、修改或删除需要监听的文件描述符及其事件(如 “可读”“可写”)。
原型

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

参数

  • epfdepoll_create 返回的 epoll 实例描述符;
  • op:操作类型,可选值:
    • EPOLL_CTL_ADD:向 epoll 实例添加一个新的 FD 及事件;
    • EPOLL_CTL_MOD:修改已添加的 FD 的监听事件;
    • EPOLL_CTL_DEL:从 epoll 实例中删除一个 FD(此时 event 可设为 NULL);
  • fd:需要监听的文件描述符(如 socket、管道 FD 等);
  • event:指向 struct epoll_event 的指针,用于描述监听的事件及用户数据:
    struct epoll_event {uint32_t events;  // 监听的事件类型(如 EPOLLIN 表示可读)epoll_data_t data; // 用户数据(通常存储 FD 或自定义指针)
    };// 用户数据联合体(可存储多种类型)
    typedef union epoll_data {void    *ptr;   // 自定义指针(如指向业务数据结构)int      fd;    // 最常用:存储当前监听的 FDuint32_t u32;uint64_t u64;
    } epoll_data_t;
    

常见事件类型(events 字段)

  • EPOLLIN:FD 可读(如 socket 有数据、管道有输入);
  • EPOLLOUT:FD 可写(如 socket 发送缓冲区空闲);
  • EPOLLERR:FD 发生错误(无需手动设置,内核自动触发);
  • EPOLLET:边缘触发模式(ET,高效模式,需配合非阻塞 IO);
  • EPOLLONESHOT:只监听一次事件,事件触发后需重新添加才会再次监听。

返回值

  • 成功:返回 0
  • 失败:返回 -1,并设置 errno(如 EEXIST 表示添加已存在的 FD)。
struct epoll_event ev;
// 监听 FD=5 的“可读事件”,并设为边缘触发(ET)
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = 5;  // 存储 FD 到用户数据中// 向 epoll 实例添加该 FD 及事件
if (epoll_ctl(epfd, EPOLL_CTL_ADD, 5, &ev) == -1) {perror("epoll_ctl add failed");exit(EXIT_FAILURE);
}
3. epoll_wait:等待并获取就绪事件

功能:阻塞等待 epoll 实例中监听的 FD 发生就绪事件(如可读、可写),返回就绪的事件列表。
原型

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

参数

  • epfd:epoll 实例描述符;
  • events:用户空间数组,内核会将所有就绪的事件写入该数组(输出参数);
  • maxeventsevents 数组的最大长度(必须 ≥ 1,且不能超过 epoll_create 时的 size 早期限制);
  • timeout:超时时间(毫秒):
    • -1:永久阻塞,直到有事件就绪;
    • 0:立即返回,无论是否有事件就绪;
    • 正数:等待 timeout 毫秒后返回(若期间有事件就绪则提前返回)

返回值

  • 成功:返回就绪事件的数量(>0);
  • 超时:返回 0timeout 非 -1 时);
  • 失败:返回 -1,并设置 errno(如 EINTR 表示被信号中断)。
struct epoll_event events[10];  // 最多存储 10 个就绪事件
int nfds;// 永久阻塞等待事件(-1)
nfds = epoll_wait(epfd, events, 10, -1);
if (nfds == -1) {perror("epoll_wait failed");exit(EXIT_FAILURE);
}// 遍历所有就绪事件并处理
for (int i = 0; i < nfds; i++) {if (events[i].events & EPOLLIN) {// FD 可读,处理数据(events[i].data.fd 为就绪的 FD)handle_read(events[i].data.fd);}
}
2. 事件类型与触发模式
  • 常见事件EPOLLIN(可读)、EPOLLOUT(可写)、EPOLLERR(错误)。
  • 触发模式
    • 水平触发(LT,默认):只要 FD 就绪,每次 epoll_wait 都会返回。
    • 边缘触发(ET):仅在 FD 从 “未就绪” 变为 “就绪” 时触发一次(需配合非阻塞 IO 使用)。
3. 特点
  • 优点
    • 无 FD 数量限制(支持上万甚至百万级 FD)。
    • FD 仅在添加时拷贝到内核,后续无需重复拷贝。
    • 内核直接返回就绪 FD 列表,无需遍历(O (1) 复杂度)。
  • 缺点:仅支持 Linux 系统,不跨平台。

poll 多路复用机制,实现了同时监听管道(fifo)和标准输入(终端)的功能

读端:

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>   // epoll相关函数头文件
#include <sys/stat.h>    // 文件状态相关函数
#include <sys/types.h>   // 基本数据类型定义
#include <unistd.h>      // POSIX系统调用/*** 向epoll实例添加需要监听的文件描述符* @param epfd epoll实例的文件描述符* @param fd 要添加的文件描述符* @return 0表示成功,1表示失败*/
int add_fd(int epfd, int fd)
{struct epoll_event ev;       // epoll事件结构体,用于描述监听的事件类型和关联数据ev.events = EPOLLIN;         // 监听读事件(当有数据可读时触发)ev.data.fd = fd;             // 将文件描述符与事件绑定,方便后续识别// 调用epoll_ctl添加文件描述符到epoll实例// 参数:epoll实例、操作类型(添加)、目标文件描述符、事件结构体int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);if (-1 == ret){perror("add fd");  // 添加失败时输出错误信息return 1;}return 0;  // 成功添加
}int main(int argc, char *argv[])
{// 创建命名管道"myfifo",权限为0666(所有用户可读写)int ret = mkfifo("myfifo", 0666);if (-1 == ret)  // 创建失败{if (EEXIST == errno)  // 错误码为EEXIST,表示管道已存在,属于正常情况{// 管道已存在,无需处理,继续执行}else  // 其他错误(如权限不足){perror("mkfifo error\n");  // 输出错误信息return 1;                  // 异常退出}}// 以只读方式打开管道文件int fd = open("myfifo", O_RDONLY);if (-1 == fd)  // 打开失败{perror("open error\n");  // 输出错误信息return 1;                // 异常退出}// 定义epoll事件数组,用于存储epoll_wait返回的就绪事件// 大小为2,因为我们最多监听2个文件描述符(终端和管道)struct epoll_event rev[2];// 1. 创建epoll实例// 参数2表示期望处理的文件描述符数量(仅供内核参考,非严格限制)int epfd = epoll_create(2);if (-1 == epfd)  // 创建失败{perror("epoll_create");  // 输出错误信息return 1;                // 异常退出}// 2. 向epoll实例添加需要监听的文件描述符add_fd(epfd, 0);    // 添加标准输入(终端,文件描述符固定为0)add_fd(epfd, fd);   // 添加管道文件描述符// 事件循环:持续监听并处理事件while (1){char buf[512] = {0};  // 数据缓冲区,用于存储读取到的数据// 等待事件发生,epoll_wait会阻塞直到有事件触发// 参数:epoll实例、存储就绪事件的数组、数组大小、超时时间(-1表示无限等待)int ep_ret = epoll_wait(epfd, rev, 2, -1);// 遍历所有就绪的事件for (int i = 0; i < ep_ret; i++){// 判断就绪的是管道文件描述符if (rev[i].data.fd == fd){read(fd, buf, sizeof(buf));  // 从管道读取数据printf("fifo :%s\n", buf);   // 打印管道中的数据}// 判断就绪的是标准输入(终端)else if (0 == rev[i].data.fd){bzero(buf, sizeof(buf));          // 清空缓冲区fgets(buf, sizeof(buf), stdin);   // 从终端读取输入printf("terminal:%s", buf);       // 打印终端输入的数据fflush(stdout);                   // 刷新标准输出,确保内容立即显示}}}// 关闭管道文件描述符(实际运行中因无限循环,此句很少执行)close(fd);// 注释:删除管道文件(此处保留注释,实际运行时不删除,方便后续测试)// remove("myfifo");return 0;
}

写端:

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char *argv[])
{int ret = mkfifo("myfifo", 0666);if (-1 == ret){if (EEXIST == errno){}else{perror("mkfifo error\n");return 1;}}int fd = open("myfifo", O_WRONLY);if (-1 == fd){perror("open error\n");return 1;}while (1){char buf[512] = "hello,this is fifo tested...\n";write(fd, buf, strlen(buf) + 1);sleep(3);}close(fd);return 0;
}
对比维度selectepoll
底层机制轮询(遍历所有监听 fd)主动上报(有设备的中断触发)
文件描述符限制有上限(默认 1024,由 FD_SETSIZE 定义),修改需重新编译内核无上限(仅受系统内存和进程 fd 限制)
性能随 fd 增长趋势性能急剧下降(O (n),n 为监听 fd 数量)性能稳定(O (1),与监听 fd 数量无关)
用户态 / 内核态交互每次调用需拷贝整个 fd_set 到内核(高开销)仅注册 / 修改时拷贝 fd 到内核,后续无拷贝
就绪 fd 通知方式仅告知 “有就绪”,需用户二次遍历检查直接返回就绪 fd 列表,无需二次检查
支持的事件类型仅支持水平触发(LT)支持水平触发(LT)和边缘触发(ET)
接口复杂度简单(3 个函数:select/FD_SET/FD_ISSET稍复杂(3 个函数:epoll_create/epoll_ctl/epoll_wait
可移植性高(POSIX 标准,支持 Linux/Windows/macOS)低(仅 Linux 特有,非 POSIX 标准)
适用场景监听 fd 数量少(如 < 1000)、简单场景监听 fd 数量多(如万级 / 十万级)、高性能场景(如服务器)

http://www.xdnf.cn/news/1464031.html

相关文章:

  • AI日报 - 2025年09月05日
  • 专题:2025电力行业5G工厂及绿色转型、市场机制研究报告|附100+份报告PDF、数据仪表盘汇总下载
  • Ubuntu22.04-ROS2下navgation2编译到运行
  • 机器学习入门,用Lima在macOS免费搭建Docker环境,彻底解决镜像与收费难题!
  • 基于muduo库的图床云共享存储项目(五)
  • webshell及冰蝎双击无法打开?
  • 如何将视频从 iPhone 转移到 Mac
  • 开学信息收集不再愁,这个工具太省心
  • JavaEE---7.文件操作和IO
  • The Algorithmic Foundations of Differential Privacy - 3(2)
  • Windows Server2012 R2 安装.NET Framework 3.5
  • 安科瑞基站智慧运维云平台:安全管控与节能降耗双效赋能
  • python库 Py2app 的详细使用(将 Python 脚本变为 MacOS 独立软件包)
  • MacOS 15.6 编译SDL3 Android平台多架构so库
  • 【NVIDIA AIQ】自定义函数实践
  • windows安装flash-attn记录
  • 在 Java Web 项目中优雅地实现验证码拦截与校验
  • 新闻丨重庆两江新区党工委副书记、管委会主任许宏球一行莅临华院计算考察指导
  • Java 内存模型与垃圾回收机制详解
  • 迅为RK3568开发板OpenHarmonyv3.2-Beta4版本测试-命令终端
  • AI在目前会议直播系统中应用
  • CSS 选择器的优先级/层叠性
  • watchEffect 与 watch的区别
  • 双轴倾角传感器厂家与物联网角度传感器应用全解析
  • MySQL】从零开始了解数据库开发 --- 表的操作
  • 盘点完今年CoRL最火的VLA论文,发现最强的机器人,竟是用“假数据”喂大的
  • 前端视觉交互设计全解析:从悬停高亮到多维交互体系(含代码 + 图表)
  • “我店”模式:热潮中的商机还是泡沫陷阱?深度解析当前入局可行性
  • 阿里云vs腾讯云按量付费服务器
  • 腾讯云大模型训练平台