Linux下的五种I/O模型
来源:互联网 发布:淘宝怎么找同城店铺 编辑:程序博客网 时间:2024/06/08 08:06
一、五种I/O模型
1、阻塞I/O模型
应用程序调用一个I/O函数,导致应用程序阻塞,等待数据,如果数据没有准备好,则一直阻塞,直到数据准备好,将数据从内核空间拷贝到用户空间,I/O函数成功返回。
2、非阻塞I/O模型
将套接口设置为非阻塞形式就是告诉内核,当所请求的I/O函数还没有准备好时,直接返回错误,而不让进程进入睡眠状态,此后,I/O函数不断地测试数据有没有准备好,如果数据没有准备好,则不断进行测试,直到数据准备好为止。在测试期间会浪费CPU的资源。
3、I/O复用模型
I/O复用模型会用到select函数、poll函数或者poll函数。该模型也会阻塞等待I/O函数,但是与阻塞I/O模型不同的是,该模型可以同时阻塞多个I/O,并且可以同时对多个读操作和写操作的I/O函数进行检测,直到有数据可读或者可写时,才真正调用I/O函数。
4、信号驱动模型
首先允许套接口进行信号驱动I/O,并安装一个信号处理函数,进行继续运行并不阻塞,当数据准备好时,进程会收到一个SIGIO信号,可以信号处理函数中调用I/O操作函数处理数据。
5、异步I/O模型
调用aio_read函数,告诉内核描述字,缓冲区指针,缓冲区大小,文件偏移以及通知的方式,然后立即返回,直到数据准备好之后,再通知应用程序。
二、同步I/O与异步I/O
1、同步
所谓同步就是调用者发出一个调用,在该调用返回之前,调用者一直进行等待,直到调用成功为止,调用者返回并带返回值。
2、异步
调用者发出一个调用请求,然后直接返回,此次返回没有返回值,然后被调用者通过状态来通知调用者,或者通过回掉函数来处理这个调用。
三、阻塞与非阻塞
阻塞与非阻塞指的是应用程序在等待调用结果时的状态
1、阻塞等待
在调用结果返回之前线程会挂起,直到得到调用结果之后才会返回。
2、非阻塞等待
在调用结果返回之前不会一直等待,也就是应用程序不会阻塞当前线程。
目前的阻塞和非阻塞是针对同步机制来说的。
四、高级I/O
非阻塞I/O、记录锁、系统V流机制、I/O多路转接、readv函数和writev函数、存储映射I/O都属于高级I/O。
五、I/O多路转接
什么叫多路转接?
对于多个非阻塞的I/O,如何知道该I/O是否已经准备好读数据或者写数据?
方法一:采用read函数和write函数进行轮询式的检测,知道有数据准备好被读写为止,但是这种方法十分浪费CPU的资源;
方法二:采用I/O多路转接(I/O复用技术):先构造一张列表,该列表中存储的是文件描述符。然后调用一个函数(select/poll/epoll)进行等待,当文件描述符列表中的一个或者多个文件描述符准备好之后,该函数就进行返回,函数返回时告诉当前进程哪些文件描述符已经准备好了。
六、I/O多路转接之select函数
1、函数原型
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
2、需要包含的头文件:
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
3、参数类型说明:
fd_set:表示文件描述符集合
struct timeval:是一个用于描述监视时间的结构体,如果在规定的时间之内,需要检测的文件描述符没有事件发生,则返回0值。
该结构体如下所示:
4、参数说明:
nfds:需要监视的最大文件描述符值加一
readfds:需要检测的可读文件描述符集合
writefds:需要检测的可写文件描述符集合
exceptfds:需要检测的异常文件描述符集合
timeout:函数的等待时间
5、用四种宏处理三种描述词组
(1)FD_CLR(int fd,fd_set* set);用来清除描述词组set中相关fd 的位。
(2)FD_ISSET(int fd,fd_set *set);用来测试描述词组set中相关fd 的位是否为真。
(3)FD_SET(int fd,fd_set*set);用来设置描述词组set中相关fd的位 。
(4)FD_ZERO(fd_set *set);用来清除描述词组set的全部位 。
6、参数timeout的取值
(1)NULL:表示让函数select一直进行阻塞,直到某个文件描述符上的事件就绪。
(2)0:表示不等待文件描述符上的事件是否就绪,直接返回。
(3)取某一特定值x:表示在x时间内若还没有文件描述符上的事件就绪,则超时返回。
7、函数的返回值
(1)执行成功:则返回已经就绪的文件描述符的个数。
(2)返回0:意思为超时返回,即在指定时间内没有文件描述符的对应的事件就绪。
(3)返回-1:出错返回,出现错误的可能原因有:
错误原因存于errno,此时参数readfds,writefds,exceptfds和 timeout的值变成不可预测。错误值可能为: EBADF 文件描述词为无效的或该文件已关闭。
EINTR 此调用被信号所中断。
EINVAL 参数n 为负值。
ENOMEM 核心内存不足。
8、用select函数实现网络服务器
代码如下:
#include <stdio.h>#include <unistd.h>#include <sys/time.h>#include <sys/types.h>#include <sys/select.h>#include <arpa/inet.h>#include <netinet/in.h>#include <string.h>#include <sys/socket.h>#include <stdlib.h>#include <fcntl.h>static void usage(const char* proc){ printf("%s [local_ip] [local_port]\n",proc);}int CreatSock(const char* _ip,int _port){ int Sock=socket(AF_INET,SOCK_STREAM,0); if(Sock<0) { perror("socket"); exit(2); } struct sockaddr_in local; local.sin_family=AF_INET; local.sin_port=htons(_port); local.sin_addr.s_addr=inet_addr(_ip); if(bind(Sock,(struct sockaddr*)&local,sizeof(local))<0) { perror("bind"); exit(3); } if(listen(Sock,5)<0) { perror("listen"); exit(4); } return Sock;}int main(int argc,char* argv[]){ if(argc!=3) { usage(argv[0]); return 1; } char fds[1024]; int listensock=CreatSock(argv[1],atoi(argv[2])); int nums=sizeof(fds)/sizeof(fds[0]); int maxfd=-1; int i=1; for(;i<nums;i++) { fds[i]=-1; } fds[0]=listensock; umask(0); int open_fd=open("./sever_log",O_CREAT | O_RDWR,0664); if(open_fd<0) { perror("open"); exit(10); } close(1); int ret=dup2(open_fd,1); char buf[1024]; while(1) { struct timeval timeout={5,0}; fd_set set; FD_ZERO(&set); maxfd=-1; for(i=0;i<nums;i++) { if(fds[i]>0) { FD_SET(fds[i],&set); if(maxfd<fds[i]) { maxfd=fds[i]; } } } switch(select(maxfd+1,&set,NULL,NULL,NULL)) { case 0: printf("timeout...\n"); break; case -1: perror("select\n"); break; default: { int i=0; for(;i<nums;i++) { if(fds[i]<0) continue; if(i==0 && FD_ISSET(listensock,&set)) { struct sockaddr_in client; socklen_t len=sizeof(client); int new_fd=accept(listensock,(struct sockaddr*)&client,&len); if(new_fd<0) { perror("accept\n"); exit(5); } printf("get a client:[%s:%d]\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port)); int j=1; for(;j<nums;j++) { if(fds[j]==-1) { break; } } if(j==nums) { printf("set is full\n"); close(new_fd); } else { fds[j]=new_fd; } } else if(i>0 && FD_ISSET(fds[i],&set)) { ssize_t s=read(fds[i],buf,sizeof(buf)); if(s>0) { buf[s]=0; printf("client say:%s\n",buf); fflush(stdout); write(fds[i],buf,strlen(buf)); } else if(s==0) { printf("client quit\n"); fflush(stdout); close(fds[i]); fds[i]=-1; } else { perror("read\n"); } } else { ; } } } break; } } close(open_fd); return 0;}
通过对该函数的实现,可以更加深入地了解select模型
(1)可监控的文件描述符个数取决于sizeof(fd_set):例如sizeof(fd_set)=512,则最多可监控的文件描述符个数为(512*8),因为一个文件描述符占据一个比特位。
(2)必须使用一个全局数组来保存加入到select监控集中的文件描述符,该全局数组有两个作用:
a.用于select函数返回之后,全局数组作为源数据和fd_set进行判断;
b.由于select函数每次返回之后就会将fd_set中之前加入的但是并没有发生事件的文件描述符清空,因此在重新调用select函数之前都要重新从全局数组中取得文件描述符加入fd_set中,并且扫描最大的文件描述符值加1,作为select函数的第一个参数。
c.select模型必须在select函数调用之前循环全局数组:用于往fd_set中加文件描述符,并取得最大的文件描述符值;
也必须在select函数返回之后循环全局数组:用于判断fd_set中的文件描述符是否有效。
该函数中使用了网络输出重定向dup函数,将本应该输出到显示屏上的内容输出到了文件中。
下面是客户端的代码:
#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <sys/socket.h>#include <unistd.h>#include <string.h>#include <arpa/inet.h>#include <netinet/in.h>#include <sys/stat.h>#include <fcntl.h>static void usage(const char* proc){ printf("usage:%s [remote_ip] [remote_port]\n",proc);}int main(int argc,char* argv[]){ if(argc!=3) { usage(argv[0]); exit(1); } int sock=socket(AF_INET,SOCK_STREAM,0); if(sock<0) { perror("socket"); exit(2); } struct sockaddr_in remote; remote.sin_family=AF_INET; remote.sin_port=htons(atoi(argv[2])); remote.sin_addr.s_addr=inet_addr(argv[1]); if(connect(sock,(struct sockaddr*)&remote,sizeof(remote))<0) { perror("connect"); exit(3); } umask(0); int fd=open("./client_log",O_CREAT | O_RDWR,0664); if(fd<0) { perror("open"); exit(4); } close(1); int new_fd=dup2(fd,1); char buf[1024]; while(1) { printf("please enter:"); memset(buf,'\0',sizeof(buf)); fgets(buf,sizeof(buf),stdin); if(strncmp("quit",buf,4)==0) { printf("quit\n"); printf("client is quit!\n"); break; } printf("%s\n",buf); fflush(stdout); write(sock,buf,strlen(buf)); ssize_t s=read(sock,buf,sizeof(buf)-1); if(recv>0) { buf[s]=0; printf("sever echo:%s\n",buf); } } close(fd); return 0;}
该程序中依然使用了dup函数进行了网络输出重定向。
七、I/O多路转接之poll函数
1、函数原型
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
2、需要包含的头文件
#include <poll.h>
3、参数说明
(1)pollfd是一个结构体,该结构体的结构成员如下:
struct pollfd{
int fd; //poll监控的文件描述符
short events; //请求的事件
(fd上等待的事件)
short revents; //返回的事件
(fd上实际发生的事件)
};
第一个参数fds是一个数组,理论上poll所能等待的文件描述符没有上限,一次可以传入一个数组,这与select函数使用三个位图来描述三个文件描述符集的方式不同,同时pollfd结构体包含了要等待的事件和实际返回的事件,这点也不同于select函数,不用“参数-值”的形式进行传递。
poll所能处理的文件描述符上的事件:
POLLIN:当前有数据可读;
POLLPRI:读取优先级高的数据,即优先处理紧急指针所指向的数据;
POLLOUT:当前写入数据;
(2) 第二个参数表示poll函数监视的文件描述符的个数。
(3)第三个参数timeout和select函数中的timeout的作用是相同的,timeout可以取三个值,每个值代表不同的含义:
a.-1:表示如果监视的文件描述符没有事件发生,则一直进行阻塞等待,直到有事件发生为止;
b.0:表示不等待fd上所请求的事件,直接返回;
c.某一特定值x:表示在一段特定时间x内监控的文件描述符上所等待的事件没有发生,则超时返回。
4、poll函数与select函数的联系
(1)poll是select的优化版本,主要体现在两个方面:
a.poll所能监控的文件描述符理论上是没有上限的,由于poll每次传入一个数组;
b.poll将输入参数(所监控的文件描述符上所等待的事件)和输出参数(所监控的文件描述符上实际发生的事件)进行分离,因此在使用poll函数之前不需要重新传入文件描述符。
(2)poll和select一样,都需要在返回之后对poll或者select轮询来获取准备就绪的文件描述符。
5、函数返回值也同select
(1)执行成功:则返回已经就绪的文件描述符的个数。
(2)返回0:意思为超时返回,即在指定时间内没有文件描述符的对应的事件就绪。
(3)返回-1:出错返回
6、用poll实现网络服务器
代码如下:
#include <stdio.h>#include <sys/socket.h>#include <sys/types.h>#include <unistd.h>#include <string.h>#include <stdlib.h>#include <arpa/inet.h>#include <netinet/in.h>#include <poll.h>#define MAXBUFSIZE 1024#define MAXFD 1024static void usage(const char* proc){ printf("Usage:%s [local_ip] [local_port]\n",proc);}int CreatSock(char* _ip,int _port){ int sock=socket(AF_INET,SOCK_STREAM,0); if(sock<0) { perror("socket"); exit(2); } struct sockaddr_in local; local.sin_family=AF_INET; local.sin_port=htons(_port); local.sin_addr.s_addr=inet_addr(_ip); if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0) { perror("bind"); exit(3); } if(listen(sock,5)<0) { perror("listen"); exit(4); } return sock;}int main(int argc,char* argv[]){ if(argc!=3) { usage(argv[0]); exit(1); } int listenSock=CreatSock(argv[1],atoi(argv[2])); char buf[MAXBUFSIZE]; struct pollfd fds[MAXFD]; int i=0; int maxfd=0; for(;i<MAXFD;i++) { fds[i].fd=-1; fds[i].events=POLLIN|POLLOUT; } fds[0].fd=listenSock; while(1) { maxfd=0; for(i=0;i<MAXFD;i++) { if(fds[i].events&(POLLIN|POLLOUT)) { maxfd=i; } } switch(poll(fds,maxfd+1,-1)) { case 0: printf("timeout...\n"); break; case -1: perror("poll"); exit(5); break; default: { int i=0; for(;i<MAXFD;i++) { if(fds[i].fd<0) continue; if(i==0 && (fds[i].fd & POLLIN)) { struct sockaddr_in client; socklen_t len=sizeof(client); int new_fd=accept(listenSock,(struct sockaddr*)&client,&len); if(new_fd<0) { perror("accept"); exit(6); } printf("get a client:[%s:%d]\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port)); int j=1; for(;j<MAXFD;j++) { if(fds[j].fd==-1) { break; } } if(j==MAXFD) { printf("fds is full\n"); close(new_fd); } else { fds[j].fd=new_fd; fds[j].events=POLLIN|POLLOUT; } } else if(i!=0 && (fds[i].fd!=-1)) { while(1) { ssize_t s=read(fds[i].fd,buf,sizeof(buf)); if(s>0) { buf[s]=0; printf("client say:%s\n",buf); fflush(stdout); write(fds[i].fd,buf,strlen(buf)); } else if(s==0) { printf("client is quit!\n"); fflush(stdout); close(fds[i].fd); fds[i].fd=-1; break; } else { perror("read"); exit(7); } } } else {} } } break; } } close(listenSock); return 0;}
下面是客户端代码:
#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <sys/socket.h>#include <unistd.h>#include <string.h>#include <arpa/inet.h>#include <netinet/in.h>#include <sys/stat.h>#include <fcntl.h>static void usage(const char* _proc){ printf("usage:%s [sever_ip] [sever_port]\n",_proc);}int main(int argc,char* argv[]){ if(argc!=3) { usage(argv[0]); exit(1); } int sock=socket(AF_INET,SOCK_STREAM,0); if(sock<0) { perror("socket"); exit(2); } struct sockaddr_in remote; remote.sin_family=AF_INET; remote.sin_port=htons(atoi(argv[2])); remote.sin_addr.s_addr=inet_addr(argv[1]); int ret=connect(sock,(struct sockaddr*)&remote,sizeof(remote)); if(ret<0) { perror("connect"); exit(3); } umask(0); int fd=open("./pc_log",O_CREAT|O_RDWR,0664); if(fd<0) { perror("open"); exit(4); } close(1); int new_fd=dup2(fd,1); char buf[1024]; while(1) { printf("please enter:"); memset(buf,'\0',sizeof(buf)); fgets(buf,sizeof(buf),stdin); if(strncmp("quit",buf,4)==0) { printf("client quit!\n"); break; } printf("%s\n",buf); fflush(stdout); write(sock,buf,strlen(buf)); ssize_t _s=read(sock,buf,sizeof(buf)-1); { if(_s>0) { buf[_s]=0; printf("sever echo:%s\n",buf); } } } close(fd); return 0;}
说明:客户端的输入全部重定向在文件pc_log中。
- Linux下的五种I/O模型
- Linux下的五种I/O模型
- Linux下的五种I/O模型
- 浅谈Linux下的五种I/O模型
- Linux下的五种I/O模型
- 浅谈Linux下的五种I/O模型
- Linux下的五种I/O模型
- 浅谈Linux下的五种I/O模型
- Linux下的五种I/O模型
- Linux下的五种I/O模型
- Linux下的五种I/O通信模型
- Linux下的五种I/O模型
- Linux的五种I/O模型
- Unix下的五种I/O模型图
- Linux Socket五种I/O模型
- Linux Socket五种I/O模型
- Linux Socket五种I/O模型
- Linux Socket五种I/O模型
- python list字符串元素排序
- 文章标题
- linux_locale的设定中LANG、LC_ALL、LANGUAGE环境变量的区别
- Hibernate的缓存机制
- 使用 IoC 反转控制的三种设计模式
- Linux下的五种I/O模型
- mappedBy reference an unknown target entity property: com.sq.entity.Address.tt_user in com.sq.entit
- 传输控制协议TCP(Transmission Control Protocol)报文格式
- 【MySQl】- 随机生成大量测试数据(测试用)
- leetcode 563. Binary Tree Tilt
- I/O多路转接之select
- 基于微服务架构的门户平台改造的研究
- window+anaconda+opencv
- ubuntu16开启root账号登录