Linux下的五种I/O模型

一、五种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 1024

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值