提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
一、IO模型
1.阻塞io
2.非阻塞io,优点:缺点:d对cpu资源的消耗特别大
3.多路io复用,
4.信号驱动io SIGIO,优点:cpu资源占有较少,缺点:若有两个管道得判断是那个管道
5.并行模型 进程
1.阻塞
ssize_t read(int fd, void *buf, size_t count);
默认情况下工作在阻塞模式下,当程序运行到read时一直停止,直到有read函数都取到内容程序才会向下进行。
2.非阻塞
示例:进程间通讯,一个读端,一个写端。
读端执行两个任务,一个时从写端输入的数据,另一个是读取终端的数据。
需要的函数:
int fcntl(int fd, int cmd, ... /* arg */ );
功能:可以将打开的文件描述符设置为非阻塞的工作方式。
参数:
fd:需要设置的文件描述符
两个宏:
F_GETFL:获取打开文件描述符的状态
F_SETFL:设置打开文件描述符的状态
cmd:设置的方法
... /* arg */ :可变参数,这个参数可以有也可以没有。
返回值:
代码:
/*读端代码*/
#include "head.h"
int main(int argc, const char *argv[])
{
mkfifo("./fifo",0777);
int fd = 0;
fd = open("./fifo",O_RDONLY);
if(-1 == fd)
{
perror("open error");
return -1;
}
int flags = fcntl(fd,F_GETFL);
fcntl(fd,F_SETFL,flags|O_NONBLOCK);
flags = fcntl(0,F_GETFL);
fcntl(0,F_SETFL,flags|O_NONBLOCK);
while(1)
{
char tempbuf[1024] = {0};
if(read(fd,tempbuf,sizeof(tempbuf))>0)
{
printf("fifo = %s\n",tempbuf);
}
if(fgets(tempbuf,sizeof(tempbuf),stdin))
{
printf("termanl = %s\n",tempbuf);
}
}
close(fd);
return 0;
}
/*写端代码*/
#include "head.h"
int main(int argc, const char *argv[])
{
mkfifo("./fifo",0777);
int fd = open("./fifo", O_WRONLY);
if(-1 == fd)
{
perror("open error");
return -1;
}
while(1)
{
char buf[50] = {"hello wold"};
write(fd,buf,strlen(buf));
sleep(1);
}
close(fd);
return 0;
}
3.多路io复用
linux系统中io事件的一种通知机制,主要用于检测文件io事件通知应用层做相应的处理。
1)select模型
编程步骤:
1、创建待检测文件描述符集合
类型为fd_set
2、清空集合
void FD_ZERO(fd_set *set);
3、添加描述符
void FD_SET(int fd, fd_set *set);
4、等待数据到来,可以设置阻塞等待、超时、或者非阻塞
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
功能:等待数据到来,可以设置阻塞等待、超时、或者非阻塞
参数:
nfds:最大文件描述符加1,因为内部是一个for循环
readfds:读事件集合(无该事件写NULL)
writefds:写事件集合(无该事件写NULL)
exceptfds:错误事件集合(无该事件写NULL)
timeout:超时版本设置时间若都为0为非阻塞,如果有时间则为超时版本,如果是NULL为阻塞,
返回值
5、处理数据
int FD_ISSET(int fd, fd_set *set);
参数:
fd:处理的文集描述符
set:位于哪个集合
6、清楚标志位
注意事项:
select的调用一般要注意几点:
(1) readfds等是指针结果参数,会被函数修改,所以一般会另外定义一个allread_fdset,保持全部要监听读的句柄,将它的拷贝传递给select函数,返回可读的句柄集合,类型fdset支持赋值运算符=;
(2)要注意计算nfds,当新增监听句柄时比较容易修改,当减少监听句柄时较麻烦些,如果要精确修改需要遍历或者采用最大堆等数据结构维护这个句柄集,以方便的找到第二大的句柄,或者干脆在减少监听句柄时不管nfds;
(3)timeout如果为NULL表示阻塞等,如果timeout指向的时间为0,表示非阻塞,否则表示select的超时时间;
(4) select返回-1表示错误,返回0表示超时时间到没有监听到的事件发生,返回正数表示监听到的所有事件数(包括可读,可写,异常),通常在处理事件时 会利用这个返回值来提高效率,避免不必要的事件触发检查。(比如总共只有一个事件,已经在可读集合中处理了一个事件,则可写和异常就没必要再去遍历句柄集 判断是否发生事件了);
(5) Linux的实现中select返回时会将timeout修改为剩余时间,所以重复使用timeout需要注意。
select缺点:
(1)由于描述符集合set的限制,每个set最多只能监听FD_SETSIZE(在Linux上是1024)个句柄(不同机器可能不一样);
(2)返回的可读集合是个fdset类型,需要对所有的监听读句柄一一进行FD_ISSET的测试来判断是否可读;
(3)nfds的存在就是为了解决select的效率问题(select遍历nfds个文件描述符,判断每个描述符是否是自己关心的,对关心的描述符判断是否发生事件)。但是解决不彻底,比如如果只监听0和1000两个句柄,select需要遍历1001个句柄来检查事件。
/*阻塞版本*/
#include "head.h"
int main(int argc, const char *argv[])
{
mkfifo("./fifo",0777);
int fd = 0;
fd = open("./fifo",O_RDONLY);
if(-1 == fd)
{
perror("open error");
return -1;
}
//1.创建待检测文件描述符集合
fd_set rd_set;
fd_set temp_set;
//2.清空集合
FD_ZERO(&rd_set);
FD_ZERO(&temp_set);
//3.添加描述符
FD_SET(fd,&temp_set);
FD_SET(0,&temp_set);
while(1)
{
//6.清楚标志位
rd_set = temp_set;
//4.等待数据到来,可以设置阻塞等待、超时、或者非阻塞
select(fd+1,&rd_set,NULL,NULL,NULL);
char tempbuf[1024] = {0};
//5.处理数据
if(FD_ISSET(fd,&rd_set))
{
read(fd,tempbuf,sizeof(tempbuf));
printf("fifo = %s\n",tempbuf);
}
//5.处理数据
if(FD_ISSET(0,&rd_set))
{
fgets(tempbuf,sizeof(tempbuf),stdin);
printf("termanl = %s\n",tempbuf);
}
}
close(fd);
return 0;
}
/*超时版本*/
#include "head.h"
int main(int argc, const char *argv[])
{
mkfifo("./fifo",0777);
int fd = 0;
fd = open("./fifo",O_RDONLY);
if(-1 == fd)
{
perror("open error");
return -1;
}
fd_set rd_set;
fd_set temp_set;
FD_ZERO(&rd_set);
FD_ZERO(&temp_set);
FD_SET(fd,&temp_set);
FD_SET(0,&temp_set);
struct timeval tv;
while(1)
{
rd_set = temp_set;
tv.tv_sec = 3;
tv.tv_usec = 0;
int ret = select(fd+1,&rd_set,NULL,NULL,&tv);
if(ret == 0)
{
printf("timeout \n");
continue;
}
else if(ret > 0)
{
char tempbuf[1024] = {0};
if(FD_ISSET(fd,&rd_set))
{
read(fd,tempbuf,sizeof(tempbuf));
printf("fifo = %s\n",tempbuf);
}
if(FD_ISSET(0,&rd_set))
{
fgets(tempbuf,sizeof(tempbuf),stdin);
printf("termanl = %s\n",tempbuf);
}
}
}
close(fd);
return 0;
}
2)epoll模型:
总体分为三个步骤:
1.创建关心事件总数
int epoll_create(int size);
功能:
创建一个事件结构体集合本质是红黑树,可动态添加或者减少事件。
参数:
size:创建的个数
返回值:
返回文件描述符集合
2.添加到关心的事件到集合中
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能:
关心的事件添加到到事件结构体集合中
参数:
epfd:创建的集合
op:可以进行的操作,一般有添加到集合或者从集合删除
添加:EPOLL_CTL_ADD
删除:EPOLL_CTL_DEL
状态改变:EPOLL_CTL_MOD(刚开始设置时读事件,后面向设置为写事件)
fd:关心的文件描述符
event:结构体里面有两部分
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
events:
EPOLLIN:读事件
The associated file is available for read(2) operations.
EPOLLOUT:写事件
The associated file is available for write(2) operations
data:
将关心的事件的文件描述符放到data部分
返回值:
成功返回0
失败返回-1
3.等待事件到来
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
功能:
等待关心事件的响应
参数:
epfd:关心事件的集合
events:响应事件的结构体数组
maxevents:最大响应事件数
timeout:延时,如果为-1为阻塞
返回值:
如果大于0,关心事件的总数
失败返回-1.
4.进行事件处理
epoll解决了select性能上的缺陷:
(1)不限制监听的描述符个数(poll也是),只受进程打开描述符总数的限制;
(2)监听性能不随着监听描述 符数的增加而增加,是O(1)的,不再是轮询描述符来探测事件,而是由描述符主动上报事件;
(3)使用共享内存的方式,不在用户和内核之间反复传递监听的描述 符信息;
(4)返回参数中就是触发事件的列表,不用再遍历输入事件表查询各个事件是否被触发。
epoll显著提高性能的前提是:监听大量描述符,并且每次触发事件的描述符文件非常少。
epoll的另外区别是:
①epoll创建了描述符,记得close;
②支持水平触发和边沿触发。
水平触发:当事件发生响应时,会一直通知,直到数据被读完否则一直通知。
边沿触发:当事件发生响应时,只会通知一次。
#include "head.h"
int add_ep(int ep_fd ,int fd)
{
struct epoll_event ev;
ev.data.fd = fd;
ev.events = EPOLLIN ;
int ret = epoll_ctl(ep_fd,EPOLL_CTL_ADD,fd,&ev);
if(-1 == ret)
{
perror("epoll_ctl error");
exit(1);
}
return 0;
}
int main(int argc, const char *argv[])
{
mkfifo("./fifo",0777);
int fd = 0;
fd = open("./fifo",O_RDONLY);
if(-1 == fd)
{
perror("open error");
return -1;
}
//1.创建
int ep_fd = epoll_create(2);
add_ep(ep_fd,fd);
add_ep(ep_fd,0);
struct epoll_event rev[2];
int i =0;
while(1)
{
char tempbuf[1024] = {0};
int ret_ep = epoll_wait(ep_fd,rev,2,-1);
for(i=0;i<ret_ep;++i)
{
if(rev[i].data.fd == fd)
{
read(fd,tempbuf,sizeof(tempbuf));
printf("fifo = %s\n",tempbuf);
}
if(rev[i].data.fd == 0)
{
fgets(tempbuf,sizeof(tempbuf),stdin);
printf("termanl = %s\n",tempbuf);
}
}
}
close(fd);
return 0;
}
3)多路io与并发的区别
多事件同时进行的频率决定用多路io或者并发模型。
4.信号io
/*读端*/
#include "head.h"
int fd ;
void handle(int num)
{
char tempbuf[1024] = {0};
read(fd,tempbuf,sizeof(tempbuf));
printf("fifo = %s\n",tempbuf);
return ;
}
int main(int argc, const char *argv[])
{
signal(SIGIO,handle);
mkfifo("./fifo",0777);
fd = open("./fifo",O_RDONLY);
if(-1 == fd)
{
perror("open error");
return -1;
}
int flags = fcntl(fd,F_GETFL);
fcntl(fd,F_SETFL,flags|O_ASYNC);
fcntl(fd,F_SETOWN,getpid());
while(1)
{
char tempbuf[1024] = {0};
fgets(tempbuf,sizeof(tempbuf),stdin);
printf("termanl = %s\n",tempbuf);
}
close(fd);
return 0;
}
/*写段*/
#include "head.h"
int main(int argc, const char *argv[])
{
mkfifo("./fifo",0777);
int fd = open("./fifo", O_WRONLY);
if(-1 == fd)
{
perror("open error");
return -1;
}
while(1)
{
char buf[50] = {"hello wold"};
write(fd,buf,strlen(buf));
sleep(1);
}
close(fd);
return 0;
}