1. Linux下的IO模型
阻塞式IO、非阻塞式IO、异步通信IO、IO多路复用
1.1阻塞式IO
特点:最简单、最常用,效率低
阻塞I/O 模式是最普遍使用的I/O 模式,大部分程序使用的都是阻塞模式的I/O 。
缺省情况下(及系统默认状态),套接字建立后所处于的模式就是阻塞I/O 模式。
学习的读写函数在调用过程中会发生阻塞。相关函数如下:
•读操作中的read、recv、recvfrom
读阻塞--》需要读缓冲区中有数据可读,读阻塞解除
•写操作中的write、send
写阻塞--》阻塞情况比较少,主要发生在写入的缓冲区的大小小于要写入的数据量的情况下,写操作不进行任何拷贝工作,将发生阻塞,一旦缓冲区有足够的空间,内核将唤醒进程,将数据从用户缓冲区拷贝到相应的发送数据缓冲区。
注意:sendto没有写阻塞
1.2非阻塞式IO
特点:可以处理多路IO;需要轮询,浪费CPU资源
设置非阻塞:
1)通过函数参数设置
recv函数最后一个参数写0为阻塞,写MSG_DONTWAIT为非阻塞
2)通过fcntl函数设置文件描述符属性
int fcntl(int fd, int cmd, ... /* arg */ );
功能:获取/设置文件描述符属性 状态属性(O_RDONLY O_NONBLOCK非阻塞)
参数:fd:文件描述符
cmd:功能选择
状态属性:
F_GETFL :获取文件描述符原来的属性
F_SETFL :设置文件描述符属性
arg:根据cmd决定是否填充值 int
返回值:
失败:-1
成功:F_GETFL - 返回值的文件描述符号属性的值 int
F_SETFL 0
标准模板:
操作顺序:先读,后改,再写
int flag = fcntl(fd,F_GETFL);//原来属性值
flag |= O_NONBLOCK; //修改属性值,增加非阻塞属性
fcntl(fd,F_SETFL,flag);//将新的属性值写入文件描述符中
设置标准输入为非阻塞:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char const *argv[])
{
int flag = fcntl(0, F_GETFL); // 原来属性值
flag |= O_NONBLOCK; // 修改属性值,增加非阻塞属性
//flag &= ~ O_NONBLOCK;
fcntl(0, F_SETFL, flag); // 将新的属性值写入文件描述符中
char buf[32]="hello";
while(1)
{
fgets(buf,sizeof(buf),stdin);
printf("buf:%s\n",buf);
}
return 0;
}
1.3信号驱动IO(异步IO)
特点:异步通知模式,需要底层驱动的支持
//将APP进程号告诉驱动程序
fcntl(fd, F_SETOWN, getpid());
//使能异步通知
int flag;
flag = fcntl(fd,F_GETFL);
flag|= O_ASYNC ;
fcntl(fd,F_SETFL,flag);
signal(SIGIO,handler)
sudo cat /dev/input/mouse0 :查看鼠标文件
用非阻塞方式监听鼠标的数据(鼠标操作需要加sudo)
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
int fd;
void handler(int num)//信号处理函数
{
char buf[32];
int ret = read(fd, buf, sizeof(buf) - 1);
buf[ret] = '\0';
printf("buf:%s\n", buf);
}
int main(int argc, char const *argv[])
{
// 1.打开鼠标文件
fd = open("/dev/input/mouse1", O_RDONLY);
if (fd < 0)
{
perror("open err");
return -1;
}
// 2.将进程号告诉驱动程序
fcntl(fd, F_SETOWN, getpid());
// 3.开启异步IO通知
int flag;
flag = fcntl(fd, F_GETFL);
flag |= O_ASYNC;
fcntl(fd, F_SETFL, flag);
// 4.收到信号,会调用函数
signal(SIGIO, handler);
char buf[64] = {};
while (1)
{
scanf("%s", buf);
printf("buf:%s\n", buf);
}
return 0;
}
前三种使用场景假设总结:
假设妈妈有一个孩子,孩子在房间里睡觉,妈妈需要及时获知孩子是否醒了,如何做?
1. 进到房间陪着孩子一起睡觉,孩子醒了会吵醒妈妈:不累,但是不能干别的了
2. 时不时进房间看一下:简单,空闲时间还能干点别的,但是很累
3. 妈妈在客厅干活,小孩醒了他会自己走出房门告诉妈妈:互不耽误
1.4IO多路复用
IO多路复用场景假设
假设妈妈有三个孩子,分别不同的房间里睡觉,需要及时获知每个孩子是否醒了,如何做?
1.不停进每个房间看一下:简单,空闲时间还能干点别的,但是很累
2. 把三个房间的门都打开,在客厅睡觉,同时监听所有房间的哭声,如果被哭声吵醒,那么能准确定位某个房间,及时处理即可:既能得到休息,也能及时获知每个孩子的状态。
IO多路复用机制
其基本思想是:
¡先构造一张有关描述符的表(最大1024),然后调用一个函数。
¡当这些文件描述符中的一个或多个已准备好进行I/O时函数才返回。
¡函数返回时告诉进程哪个描述符已就绪,可以进行I/O操作。
基本流程:
1. 先构造一张有关文件描述符的表(集合、数组); ----》创建三个房间
2.将你关心的文件描述符加入到这个表中; -----》将孩子放入房间
3.然后调用一个函数。 select / poll -----》在客厅躺着
4.当这些文件描述符中的一个或多个已准备好进行I/O操作的时候
该函数才返回(阻塞)。 ------》孩子哭了
5. 判断是哪一个或哪些文件描述符产生了事件(IO操作); ---》哪个孩子哭了
6. 做对应的逻辑处理; ------》哄孩子
实现IO多路复用的方式
1.select实现
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
功能:select用于监测是哪个或哪些文件描述符产生事件;
参数:nfds: 监测的最大文件描述个数
(这里是个数,使用的时候注意,与文件中最后一次打开的文件
描述符所对应的值的关系是什么?)
readfds: 读事件集合; //读(用的多)
writefds: 写事件集合; //NULL表示不关心
exceptfds:异常事件集合;
timeout:超时检测
如果不做超时检测:传 NULL
如果设置了超时检测时间:&tv
返回值:
<0 出错
>0 表示有事件产生;
==0 表示超时时间已到;
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
void FD_CLR(int fd, fd_set *set);//将fd从表中清除
int FD_ISSET(int fd, fd_set *set);//判断fd是否在表中
void FD_SET(int fd, fd_set *set);//将fd添加到表中
void FD_ZERO(fd_set *set);//清空表
特点:
select实现io多路复用的特点:
1. 一个进程最多只能监听1024个文件描述符 (千级别)FD_SETFILE
2. select被唤醒之后需要重新轮询一遍驱动的poll函数,效率比较低(消耗CPU资源);
3. select每次会清空表,每次都需要拷贝用户空间的表到内核空间,效率低
标签:IO,网编,int,flag,阻塞,描述符,fd,liunx,io From: https://blog.csdn.net/luoqing418/article/details/144108077