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中。