了 fcntl 函数来操作文件描述符的状态标志,其中主要是为了设置非阻塞模式。下面是对 fcntl 函数及其参数的详细解释:
fcntl 函数
 
fcntl 是一个用于操作文件描述符的系统调用,可以用来设置或获取文件描述符的各种属性。其原型如下:
int fcntl(int fd, int cmd, ... /* arg */ );
- fd: 需要操作的文件描述符。
- cmd: 操作类型(命令),指定要执行的操作。
- arg: 额外参数,根据- cmd的不同可能需要。
参数解释
F_GETFL 和 F_SETFL
 
- F_GETFL: 获取文件描述符的当前状态标志。这个命令将返回文件描述符的当前状态标志。
- F_SETFL: 设置文件描述符的状态标志。这个命令将文件描述符的状态标志设置为指定的值。
代码分析
int flag = fcntl(fd_r, F_GETFL);
fcntl(fd_r, F_SETFL, flag | O_NONBLOCK);
-  int flag = fcntl(fd_r, F_GETFL);- 获取文件描述符 fd_r的当前状态标志,并存储在变量flag中。
- F_GETFL命令返回的状态标志包括文件描述符的访问模式(如读、写)和一些状态标志(如是否为非阻塞模式)。
 
- 获取文件描述符 
-  fcntl(fd_r, F_SETFL, flag | O_NONBLOCK);- 使用 F_SETFL命令设置文件描述符fd_r的状态标志。
- flag | O_NONBLOCK表示将- O_NONBLOCK标志添加到当前状态标志中。
- O_NONBLOCK是一个标志,用于设置文件描述符为非阻塞模式。非阻塞模式意味着读写操作不会阻塞进程,如果无法立即完成操作,系统调用会立即返回,而不是等待。
 
- 使用 
flag = fcntl(0, F_GETFL);
fcntl(0, F_SETFL, flag | O_NONBLOCK);
-  flag = fcntl(0, F_GETFL);- 获取标准输入(文件描述符 0)的当前状态标志,并存储在变量 flag中。
 
- 获取标准输入(文件描述符 0)的当前状态标志,并存储在变量 
-  fcntl(0, F_SETFL, flag | O_NONBLOCK);- 使用 F_SETFL命令设置标准输入的状态标志。
- flag | O_NONBLOCK表示将- O_NONBLOCK标志添加到标准输入的当前状态标志中。
- 这将把标准输入设置为非阻塞模式,使得读取标准输入的操作不会阻塞进程。
 
- 使用 
总结
- fcntl(fd_r, F_GETFL)用于获取文件描述符- fd_r的当前状态标志。
- fcntl(fd_r, F_SETFL, flag | O_NONBLOCK)用于将- fd_r的状态标志设置为非阻塞模式,确保对- fd_r的读写操作不会阻塞。
- 同样,fcntl(0, F_GETFL)和fcntl(0, F_SETFL, flag | O_NONBLOCK)将标准输入(文件描述符 0)设置为非阻塞模式,使得读取标准输入的操作也是非阻塞的。
通过这些设置,你可以确保读写操作在没有数据时不会导致程序阻塞,从而使程序能够在执行其他任务时保持响应。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <pthread.h>int main(int argc, char *argv[])
{int ret = mkfifo("myfifo1",0666);if(-1 == ret){if( EEXIST!= errno ){perror("mkfifo");return 1;}}int fd_r = open("myfifo1",O_RDONLY);if(-1 == fd_r){perror("open");return 1;}int flag = fcntl(fd_r,F_GETFL);fcntl(fd_r,F_SETFL,flag|O_NONBLOCK);flag = fcntl( 0,F_GETFL);fcntl(0,F_SETFL,flag|O_NONBLOCK);while(1){char buf[128]={0};if(read(fd_r,buf,sizeof(buf))>0){printf("fifo:%s\n",buf);}bzero(buf,sizeof(buf));if(fgets(buf,sizeof(buf),stdin)){printf("terminal:%s\n",buf);}}return 0;
}signal函数在多路io
演示了如何使用信号驱动 I/O(SIGIO)来处理命名管道(FIFO)的异步事件。下面是对代码及各个函数的作用的详细解释:
代码解析
1. #include 和 全局变量
 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <pthread.h>
#include <signal.h>int fd_r;
- #include:引入了必要的头文件,提供了函数原型和常量定义。
- fd_r:定义一个全局变量,用于存储打开的 FIFO 文件描述符。
2. handle 函数
 
void handle(int num)
{char buf[128]={0};read(fd_r,buf,sizeof(buf));printf("fifo:%s\n",buf);return ;
}
- handle:信号处理函数,当收到- SIGIO信号时被调用。- num:传递给信号处理函数的信号编号,这里没有用到。
- read(fd_r, buf, sizeof(buf)):从 FIFO 中读取数据到- buf。
- printf("fifo:%s\n", buf):打印读取到的数据。
 
3. main 函数
 
int main(int argc, char *argv[])
{signal(SIGIO, handle);
- signal(SIGIO, handle):设置信号处理函数,当进程接收到- SIGIO信号时,会调用- handle函数。
    int ret = mkfifo("myfifo1",0666);if(-1 == ret){if( EEXIST!= errno ){perror("mkfifo");return 1;}}
- mkfifo("myfifo1", 0666):创建一个名为- "myfifo1"的 FIFO(命名管道)。如果文件已存在 (- EEXIST),则不处理错误。
    fd_r = open("myfifo1", O_RDONLY);if(-1 == fd_r){perror("open");return 1;}
- open("myfifo1", O_RDONLY):以只读模式打开 FIFO,并将文件描述符存储在- fd_r中。如果打开失败,打印错误信息并退出。
    int flag = fcntl(fd_r, F_GETFL);fcntl(fd_r, F_SETFL, flag | O_ASYNC);
- fcntl(fd_r, F_GETFL):获取当前文件描述符的状态标志。
- fcntl(fd_r, F_SETFL, flag | O_ASYNC):设置文件描述符为异步模式 (- O_ASYNC)。这意味着当文件描述符有数据可读时,会向进程发送- SIGIO信号。
    fcntl(fd_r, F_SETOWN, getpid());
- fcntl(fd_r, F_SETOWN, getpid()):设置- fd_r文件描述符的信号所有者为当前进程(- getpid())。这使得- SIGIO信号会发送到当前进程。
    while(1){char buf[128]={0};bzero(buf, sizeof(buf));fgets(buf, sizeof(buf), stdin);printf("terminal:%s\n", buf);}return 0;
}
- 无限循环: - fgets(buf, sizeof(buf), stdin):从标准输入读取数据到- buf。
- printf("terminal:%s\n", buf):打印从标准输入读取的数据。
 
主要函数的作用
-  signal(SIGIO, handle):- 用于设置进程在接收到 SIGIO信号时调用handle函数。
 
- 用于设置进程在接收到 
-  mkfifo("myfifo1", 0666):- 创建一个命名管道(FIFO)。如果已存在,则不处理错误。
 
-  open("myfifo1", O_RDONLY):- 打开创建的 FIFO 文件,以只读模式获取文件描述符。
 
-  fcntl(fd_r, F_GETFL)和fcntl(fd_r, F_SETFL, flag | O_ASYNC):- 获取和设置文件描述符的状态标志,使其处于异步模式。异步模式会使文件描述符触发信号(SIGIO),当数据到达时通知进程。
 
- 获取和设置文件描述符的状态标志,使其处于异步模式。异步模式会使文件描述符触发信号(
-  fcntl(fd_r, F_SETOWN, getpid()):- 设置文件描述符 fd_r的信号所有者为当前进程,使得SIGIO信号会发送到当前进程。
 
- 设置文件描述符 
-  read(fd_r, buf, sizeof(buf)):- 在信号处理函数中,从 FIFO 中读取数据。
 
-  fgets(buf, sizeof(buf), stdin)和printf("terminal:%s\n", buf):- 在主循环中,读取标准输入的数据并打印。
 
总结
这段代码通过设置文件描述符 fd_r 为异步模式,并为其指定信号所有者,使得当有数据可读时会发送 SIGIO 信号。信号处理函数 handle 会在收到 SIGIO 时被调用,从 FIFO 中读取数据并打印。这种方式允许进程在等待数据的同时执行其他操作,比如读取标准输入。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <pthread.h>
#include <signal.h>
int fd_r;
void handle(int num)
{char buf[128]={0};read(fd_r,buf,sizeof(buf));printf("fifo:%s\n",buf);return ;
}
int main(int argc, char *argv[])
{signal(SIGIO,handle);int ret = mkfifo("myfifo1",0666);if(-1 == ret){if( EEXIST!= errno ){perror("mkfifo");return 1;}}fd_r = open("myfifo1",O_RDONLY);if(-1 == fd_r){perror("open");return 1;}//给管道设置信号驱动int flag = fcntl(fd_r,F_GETFL);fcntl(fd_r,F_SETFL,flag| O_ASYNC);//如果有写管道,本进程作为sigio信号的接收者fcntl(fd_r,F_SETOWN,getpid());while(1){char buf[128]={0};bzero(buf,sizeof(buf));fgets(buf,sizeof(buf),stdin);printf("terminal:%s\n",buf);}return 0;
}
select 函数用于监视多个文件描述符的状态,以便在其中一个或多个文件描述符变为可读、可写或出现异常时进行处理。下面是对 select 相关函数和操作的详细解释。
select 函数
 
select 函数用于监视文件描述符集合,以确定哪些文件描述符在某个时间点是就绪的(即,可以进行读、写操作或出现异常)。其原型如下:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
- nfds: 文件描述符的数量+1。它通常是你感兴趣的最大文件描述符值加1。
- readfds: 监视的文件描述符集合(用于读操作)。
- writefds: 监视的文件描述符集合(用于写操作)。
- exceptfds: 监视的文件描述符集合(用于异常条件)。
- timeout: 指定- select函数的超时时间。如果超时时间为- NULL,则- select将无限期等待直到有文件描述符变为可用。
fd_set 操作函数
 
- FD_ZERO(fd_set *set): 清空文件描述符集合- set。
- FD_SET(int fd, fd_set *set): 将文件描述符- fd添加到文件描述符集合- set中。
- FD_CLR(int fd, fd_set *set): 从文件描述符集合- set中移除文件描述符- fd。
- FD_ISSET(int fd, fd_set *set): 检查文件描述符- fd是否在文件描述符集合- set中。
代码分析
// 1. 创建文件描述符集合
fd_set rd_set, tmp_set; // 读集合
FD_ZERO(&rd_set);       // 清空读集合
FD_ZERO(&tmp_set);      // 清空临时集合// 2. 添加文件描述符
FD_SET(0, &tmp_set);    // 将标准输入(文件描述符 0)添加到临时集合
FD_SET(fd_r, &tmp_set); // 将文件描述符 fd_r 添加到临时集合while (1) {// 6. 复制临时集合到读集合rd_set = tmp_set;char buf[128] = {0};// 3. 等待事件发生select(fd_r + 1, &rd_set, NULL, NULL, NULL);// 4. 查找发生事件的文件描述符if (FD_ISSET(fd_r, &rd_set)) {read(fd_r, 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\n", buf);}
}
-  创建文件描述符集合: - fd_set rd_set, tmp_set;定义了两个- fd_set类型的变量,用于存储文件描述符集合。
- FD_ZERO(&rd_set);清空- rd_set集合。
- FD_ZERO(&tmp_set);清空- tmp_set集合。
 
-  添加文件描述符: - FD_SET(0, &tmp_set);将标准输入(文件描述符 0)添加到- tmp_set集合中。
- FD_SET(fd_r, &tmp_set);将文件描述符- fd_r添加到- tmp_set集合中。
 
-  等待事件: - rd_set = tmp_set;复制- tmp_set到- rd_set,- rd_set用于- select调用,避免在- select调用后丢失对文件描述符的跟踪。
- select(fd_r + 1, &rd_set, NULL, NULL, NULL);等待直到- fd_r或标准输入有数据可读。- fd_r + 1是需要监视的文件描述符的数量加1。
 
-  处理事件: - FD_ISSET(fd_r, &rd_set)检查- fd_r是否在- rd_set中,即检查- fd_r是否有数据可读。如果有,读取数据并打印。
- FD_ISSET(0, &rd_set)检查标准输入是否在- rd_set中,即检查标准输入是否有数据可读。如果有,读取数据并打印。
 
总结
- select用于监视多个文件描述符,以确定哪些文件描述符准备好进行读、写或异常处理。
- fd_set操作函数用于创建和修改文件描述符集合。
- 在代码中,select被用于等待标准输入或文件描述符fd_r中有数据可读,并根据文件描述符的状态执行相应的读取操作。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <pthread.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>int main(int argc, char *argv[])
{int ret = mkfifo("myfifo1",0666);if(-1 == ret){if( EEXIST!= errno ){perror("mkfifo");return 1;}}int fd_r = open("myfifo1",O_RDONLY);if(-1 == fd_r){perror("open");return 1;}//1 create set fd_set rd_set,tmp_set; //read set FD_ZERO(&rd_set);FD_ZERO(&tmp_set);// 2. add fd FD_SET(0,&tmp_set);FD_SET(fd_r,&tmp_set);while(1){//6.clean flagrd_set = tmp_set;char buf[128]={0};//3 wait event select(fd_r+1,&rd_set,NULL,NULL,NULL );//4 找到对应的fdif(FD_ISSET(fd_r,&rd_set)){read(fd_r,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\n",buf);}}return 0;
}
epoll
epoll 被用来实现对两个文件描述符的事件通知机制。下面是对各个函数和操作的详细分析:
1. add_fd(int epfd, int fd)
 
这个函数用于将一个文件描述符 fd 添加到 epoll 实例 epfd 中,并指定感兴趣的事件类型。
- 参数: - epfd:- epoll_create创建的- epoll文件描述符。
- fd:要添加到- epoll的文件描述符。
 
- 操作: - 创建一个 epoll_event结构体ev,设置感兴趣的事件为EPOLLIN,表示关注该文件描述符的可读事件。
- 使用 epoll_ctl函数将fd添加到epoll实例中,操作类型为EPOLL_CTL_ADD。
 
- 创建一个 
- 返回值: - 返回 epoll_ctl的结果,如果返回-1表示添加失败,并调用perror打印错误信息。
 
- 返回 
2. main(int argc, char *argv[])
 
主函数的执行流程:
-  创建命名管道(FIFO): - 使用 mkfifo创建两个命名管道"myfifo1"和"myfifo2",这两个管道用于进程间通信。如果创建失败并且错误码不是EEXIST(文件已存在),则打印错误信息并退出。
 
- 使用 
-  打开命名管道: - 使用 open打开myfifo1(以只读模式)和myfifo2(以只写模式),分别获得fd_r和fd_w文件描述符。如果打开失败,打印错误信息并退出。
 
- 使用 
-  创建 epoll实例:- 使用 epoll_create创建一个epoll实例,并返回一个文件描述符epfd。如果创建失败,打印错误信息并退出。
 
- 使用 
-  添加文件描述符到 epoll实例:- 调用 add_fd函数将标准输入(文件描述符0)和管道myfifo1的文件描述符fd_r添加到epoll实例中,开始监听它们的事件。
 
- 调用 
-  事件监听循环: -  进入无限循环,调用 epoll_wait等待事件发生。epoll_wait会阻塞直到有事件发生,或者超时(这里设置为-1,表示无限等待)。
-  事件处理: - 如果 epoll_wait返回有事件发生,遍历所有的事件:- 如果事件的文件描述符是标准输入(0),读取输入数据并写入到fd_w,如果输入为"#quit\n",则退出程序。
- 如果事件的文件描述符是 fd_r,从fd_r读取数据并打印。如果读取的数据是"#quit\n"或读取失败,则退出程序。
 
- 如果事件的文件描述符是标准输入(
 
- 如果 
 
-  
主要函数和操作的作用总结
-  epoll_create:创建epoll实例,返回epoll文件描述符。此文件描述符用于后续的epoll_ctl和epoll_wait操作。
-  epoll_ctl:对epoll实例进行控制操作,如添加、修改或删除文件描述符及其事件。此处用于将文件描述符添加到epoll实例中。
-  epoll_wait:等待epoll实例中的文件描述符发生事件。阻塞直到有文件描述符的事件发生或者超时,返回发生的事件数量。
-  fgets和write:用于从标准输入读取数据,并写入到fd_w对应的管道中。
-  read和printf:用于从fd_r对应的管道中读取数据,并输出到标准输出。
这段代码展示了如何使用 epoll 机制来处理异步 I/O 操作,同时实现了基于命名管道的进程间通信。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <pthread.h>
#include <sys/epoll.h>int add_fd(int epfd,int fd)
{struct epoll_event ev;ev.events = EPOLLIN;ev.data.fd = fd;int ret = epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev);if(-1==ret){perror("add_fd");}return ret;
}int main(int argc, char *argv[])
{int ret = mkfifo("myfifo1",0666);if(-1 == ret){if( EEXIST!= errno ){perror("mkfifo");return 1;}}ret = mkfifo("myfifo2",0666);if(-1 == ret){if( EEXIST!= errno ){perror("mkfifo");return 1;}}int fd_r = open("myfifo1",O_RDONLY);if(-1 == fd_r){perror("open");return 1;}int fd_w = open("myfifo2",O_WRONLY);if(-1 == fd_w){perror("open");return 1;}struct epoll_event rev[2];int epfd;int ep_ret;epfd = epoll_create(2);if(-1==epfd){perror("epoll_create");return 1;}add_fd(epfd,0);add_fd(epfd,fd_r);while(1) {ep_ret = epoll_wait(epfd,rev,2,-1);if(ep_ret>0){int i=0;for(i=0;i<ep_ret;i++){if(0==rev[i].data.fd){printf("to A:");char buf[128]={0};fgets(buf,sizeof(buf),stdin);//#quitif(0 == strcmp(buf,"#quit\n")){exit(0);}write(fd_w,buf,strlen(buf));}else if(fd_r==rev[i].data.fd){char buf[128]={0};int ret = read(fd_r,buf,sizeof(buf));if(0==strcmp(buf,"#quit\n") || ret<=0){exit(0);}printf("from A:%s",buf);fflush(stdout);}}}}return 0;
}
第一步 创建epoll_create

第二步  添加成员

第三步 epoll_wait()

有timeout的使用
