文章目录
- 一、 阻塞IO
- (一)阻塞IO模式
- (二)示例
- 二、非阻塞IO
- (一)特点
- (二)fcntl
- (三)示例
- 三、IO多路复用
- (一)实现原理
- (二)select函数
- (三)使用示例
- (四)关于select函数的理解
一、 阻塞IO
(一)阻塞IO模式
写操作阻塞:
写操作有阻塞的,当缓冲区满时,写操作就会阻塞
直到缓冲区中腾出足够的空间容纳的下本次写操作时,写操作解除阻塞。
读操作阻塞:
当程序执行到读操作时,如果缓冲区有内容,就读走继续向下执行。
如果缓冲区中没有内容,程序就会进入休眠态,直到缓冲区中有内容了,
由内核唤醒当前进程,读走内容继续向下执行。
(二)示例
以有名管道通信为例:同时开启一个读端和三个写端,三个写端分别向各自的管道文件写数据,读端使用循环分别读出管道的内容并打印
read.c
#include <my_head.h>int main(int argc, char const *argv[])
{int fd1 = 0, fd2=0, fd3=0;if(-1 == (fd1 = open("fifo1",O_RDONLY))){ERR_LOG("open error");} if(-1 == (fd2 = open("fifo2",O_RDONLY))){ERR_LOG("open error");} if(-1 == (fd3 = open("fifo3",O_RDONLY))){ERR_LOG("open error");} char buff[128];int ret=0;while(1){if(0 < (ret = read(fd1,buff,sizeof(buff)))){printf("fifo1:[%s];ret = %d\n",buff,ret);memset(buff,0,sizeof(buff));}if(0 < (ret = read(fd2,buff,sizeof(buff)))){printf("fifo2:[%s];ret = %d\n",buff,ret);memset(buff,0,sizeof(buff));} if(0 < (ret =read(fd3,buff,sizeof(buff)))){printf("fifo3:[%s];ret = %d\n",buff,ret);memset(buff,0,sizeof(buff));}}close(fd1);close(fd2);close(fd3);return 0;
}
write.c(三个写端除了打开的管道文件不同,其余相同)
#include <my_head.h>int main(int argc, char const *argv[])
{int fd = open("fifo1",O_WRONLY);char buff[128];while(1){scanf("%s",buff);if(-1 == write(fd,buff,sizeof(buff))){ERR_LOG("write error");}}close(fd);return 0;
}
- 注:
- 阻塞IO会相互影响:以上例说明,在写入fifo1并输出后,就会在读fifo2文件内容时阻塞等待,此时如果fifo1或者fifo3中有内容输入也不会处理。
- 此时read进程处于休眠状态,等待用户输入内容,不会占用CPU
二、非阻塞IO
(一)特点
读操作为例:
当程序执行到读操作时,如果缓冲区有内容,就读走继续向下执行。
如果缓冲区中没有内容,程序会立即返回一个错误,而不会休眠。
这种操作一般需要一个循环来不停地测试是否有数据可读,
这种操作十分浪费CPU资源 一般不推荐使用。
有一部分函数 是自带非阻塞标志位的
如:recv和recvfrom的 MSG_DONTWAIT
O_NONBLOCK
、waitpid的 W_NOHANG
(二)fcntl
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
功能:操作文件描述符
参数:fd:要操作的文件描述符cmd:操作方式F_GETFL 获取文件描述符的属性 arg被忽略F_SETFL 设置文件描述符的属性 arg是int类型其中 O_NONBLOCK 就是非阻塞的标志位...:可变参
返回值:F_GETFL 成功 返回文件状态信息 失败 -1 重置错误码
(三)示例
将文件设置为非阻塞。
read.c
#include <my_head.h>int main(int argc, char const *argv[])
{int fd1 = 0, fd2=0, fd3=0;int flag=0;if(-1 == (fd1 = open("fifo1",O_RDONLY))){ERR_LOG("open error");} flag=fcntl(fd1,F_GETFL);flag |= O_NONBLOCK;if(-1 == fcntl(fd1,F_SETFL,flag)){ERR_LOG("fcntl error");}if(-1 == (fd2 = open("fifo2",O_RDONLY))){ERR_LOG("open error");} flag=fcntl(fd2,F_GETFL);flag |= O_NONBLOCK;if(-1 == fcntl(fd2,F_SETFL,flag)){ERR_LOG("fcntl error");}if(-1 == (fd3 = open("fifo3",O_RDONLY))){ERR_LOG("open error");} flag=fcntl(fd3,F_GETFL);flag |= O_NONBLOCK;if(-1 == fcntl(fd3,F_SETFL,flag)){ERR_LOG("fcntl error");}char buff[128];int ret=0;while(1){if(0 < (ret = read(fd1,buff,sizeof(buff)))){printf("fifo1:[%s];ret = %d\n",buff,ret);memset(buff,0,sizeof(buff));}if(0 < (ret = read(fd2,buff,sizeof(buff)))){printf("fifo2:[%s];ret = %d\n",buff,ret);memset(buff,0,sizeof(buff));} if(0 < (ret =read(fd3,buff,sizeof(buff)))){printf("fifo3:[%s];ret = %d\n",buff,ret);memset(buff,0,sizeof(buff));}sleep(1);}close(fd1);close(fd2);close(fd3);return 0;
}
- 注:
- 非阻塞IO不会相互影响:以上例说明,不会再阻塞等待用户输入,在哪个管道读到数据,就直接输出。
- 此时read进程处于运行状态,不会阻塞等待用户输入内容,会一直占用CPU资源进行遍历
三、IO多路复用
(一)实现原理
构造一个关于文件描述符的表,将所有要监视的文件描述符都放在这个表中
将这个表交给一个函数:select / poll / epoll
这个函数默认也是阻塞的,直到监视的文件文件描述符中有一个或多个准备就绪
这个函数会返回,返回时,会告诉调用者哪些文件描述符已经就绪,
调用者对已经就绪的文件描述符操作,就不会阻塞了。
(二)select函数
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
功能:实现多路IO复用
参数:nfds:要监视的最大文件描述符+1readfds:要监视的读文件描述符集合(监视它是否可读) 不关心可以传NULLwritefds:要监视的写文件描述符集合(监视它是否可写) 不关心可以传NULLexceptfds:要监视的异常文件描述符集合 不关心可以传NULLtimeout:超时时间 传 NULL 表示永久阻塞 直到有就绪的文件描述符为止
返回值:成功 返回就绪的文件描述符的个数void FD_CLR(int fd, fd_set *set);//将文件描述符在集合中删除
int FD_ISSET(int fd, fd_set *set);//测试文件描述符是否还在集合中//在 返回真 不在 返回假
void FD_SET(int fd, fd_set *set);//将文件描述符添加到集合中
void FD_ZERO(fd_set *set);//清空集合
- 注:
- select只能监视小于FD_SETSIZE(1024)的文件描述符
- 我们一般只关心读文件描述符集合 其他两个传NULL即可
- select每次返回时会将没有就绪的文件描述符在集合中擦除
所以在循环中使用select时每次需要重置集合
(三)使用示例
#include <my_head.h>int main(int argc, char const *argv[])
{int fd1 = 0, fd2=0, fd3=0;if(-1 == (fd1 = open("fifo1",O_RDONLY))){ERR_LOG("open error");} if(-1 == (fd2 = open("fifo2",O_RDONLY))){ERR_LOG("open error");} if(-1 == (fd3 = open("fifo3",O_RDONLY))){ERR_LOG("open error");} //创建fd_set readfds;//用来保存需要监视的文件描述符的母本fd_set tempfds;int max_fd=0;FD_ZERO(&readfds);FD_ZERO(&tempfds);FD_SET(fd1,&readfds);max_fd = max_fd > fd1 ? max_fd : fd1;FD_SET(fd2,&readfds);max_fd = max_fd > fd2 ? max_fd : fd2;FD_SET(fd3,&readfds);max_fd = max_fd > fd3 ? max_fd : fd3;char buff[128];int num = 0;while(1){tempfds = readfds;num = select(max_fd+1,&tempfds,NULL,NULL,NULL);for(int i=0; i<max_fd+1 && num; i++){if(FD_ISSET(i,&tempfds)){memset(buff,0,sizeof(buff));if(0 < read(i,buff,sizeof(buff))){printf("fd%d:[%s]\n",i,buff);}num--;}}}close(fd1);close(fd2);close(fd3);return 0;
}
(四)关于select函数的理解
- fd_set 实际是一个长度为1024的bit数组,
typedef struct{long ss[16]; //8*8*16=1024
}fd_set;
但是C语言最小单位是字节,因此才用这种方式表示;
- select的集合是基于数组实现的,最多监视1024个文件
- 本质是一个数组,包一个结构体是为了复制方便
- 四个宏定义的本质就是封装好的方便用户操作集合的位运算