I/O多路复用之select

来源:互联网 发布:cst软件介绍 编辑:程序博客网 时间:2024/06/06 00:52

I/O模型

  • 阻塞I/O
  • 非阻塞I/O
  • I/O复用
  • 信号驱动I/O
  • 异步I/O

阻塞I/O模型

应用程序调用一个I/O函数,应用程序会一直等待数据准备好。如果数据没有准备好,就会一直等待。只有当数据准备好,从内核拷贝到用户空间IO函数才成功返回。

非阻塞I/O模型

把一个套接口设置成非阻塞告诉内核,当所有的I/O操作无法完成时,不要将进程睡眠,而返回一个错误信息。此时I/O操作函数将不断的测试数据是否准备好,如果没有准备好,继续测试,知道数据准备好为止。不断测试的过程中会占用cpu时间。

I/O复用模型

I/O复用模型会用到select或者poll函数,这两个函数会使进程阻塞,并同时阻塞多个I/O操作,而且同时对多个读写操作的I/O函数进行检测,知道有数据刻毒或可写,才正真I/O操作函数。

信号驱动I/O模型

允许套接字进行信号驱动I/O,并安装信号驱动函数,进程继续运行并不停止,当数据=准备好时,进程会收到SIGIO信号,可以在信号处理函数中调用I/O操作函数进行处理数。

异步I/O模型

调用aio_read函数,告诉内核描述字,缓冲区指针,缓冲区大小,文件偏移以及通知方式,然后立即返回。当内核数据拷贝到缓冲区后,再通知应用程序


大部分场景,一个程序或网络服务出现性能问题时,可以优先考虑I/O所带来的影响。
站在用户的角度,I/O分两个阶段完成,第一阶段是等事件就绪,第二阶段才是真正的进行I/O操作。
对于这5种I/O模型,前四种第一二阶段基本相同,都是等数据就绪后将其从内核拷贝到调用者的缓冲区。而异步I/O模型不同。
影响I/O性能主要是第一阶段等待数据就绪的时候。
提高I/O性能,首先要考虑I/O等的比重。

主要讲述下I/O复用模型及select

I/O复用模型

I/O多路复用指内核一旦发现进程指定的一个或多个IO条件准备读取,它就通知进程。
I/O复用一般用于一下场合:

  1. 客户端程序要同时处理多个socket。
  2. 客户端程序要同时处理用户输入和网路连接
  3. TCP服务器要同时处理监听socket和连接socket。这时I/O复用使用最多的场合
  4. 服务器要同时处理TCP和UDP请求。
  5. 服务器要同时监听多个端口或者处理多种服务。

I/O复用虽然能同时监听多个文件描述符,但它本身是阻塞的。并且当多个文件描述符同时就绪的同时,如果不采取额外的措施,程序就只能按照顺序的依次处理其中的每一个文件描述符,这就使得服务器程序看起来是串行工作的。当然如果要实现并发,只能使用多线程或多进程编程。

select

select系统调用的用途是:提供轮询等待的方式从多个文件描述符中获取状态变化后的消息

  /* According to POSIX.1-2001 */  #include <sys/select.h> /* According to earlier standards */  #include <sys/time.h>  #include <sys/types.h>  #include <unistd.h> int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout); struct timeval {               long    tv_sec;         /* seconds */               long    tv_usec;        /* microseconds */           };//第一个参数nfds指定要坚挺的文件描述符的总数,测试的范围是在0到nfds-1的文件描述(nfds为这些文件描述符的最大值+1,因为文件描述符是从0开始的)。//后面4个参数是输入输出型参数,第2、3、4参数类型都是fd_set*(文件描述符集)。readfds、writefds、exceptfds分别指向可读、可写、可写的文件描述符集。当select调用返回时,内核将修改它们通知应用程序那个文件描述符就绪。//最后一个参数timeout用来设置select调用的超时时间。采用timeval结构类型的指针,内核将修改它来告诉应用程序select等待了多久。//select成功返回就绪的文件描述符的总数;返回0时,说明还没有文件描述符就绪。失败返回-1并设置errno

select操作函数

void FD_CLR(int fd, fd_set *set);//用来清除set中fd相关的位int  FD_ISSET(int fd, fd_set *set);//用来检测set中fd相关的位是否为真void FD_SET(int fd, fd_set *set);//用来设置set中fd相关的位void FD_ZERO(fd_set *set);//用来清除set中的所有`
//select.cinclude<stdio.h>#include<sys/types.h>#include<sys/socket.h>#include<netinet/in.h>#include<arpa/inet.h>#include<stdlib.h>#include<fcntl.h>#include<string.h>#include<sys/select.h>#include<unistd.h>int fds[1024];static void usage(const char* proc){    printf("usuage:%s [local_ip] [local_port]\n",proc);}int startup(const char* ip,int port){    int sock=socket(AF_INET,SOCK_STREAM,0);//创建套接字    if(sock<0)    {        perror("socket");        exit(1);    }    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(2);    }    if(listen(sock,5)<0)//服务器监听网络    {        perror("listen");        exit(3);    }    return sock;}int main(int argc,char* argv[]){    if(argc!=3)    {        usage(argv[0]);        return 1;    }    int listen_sock=startup(argv[1],atoi(argv[2]));//监听套接字    int nums=sizeof(fds)/sizeof(fds[0]);    int maxfd=-1;    int i=1;    for(i=1;i<nums;i++)    {        fds[i]=-1;//将放文件描述符的数组初始化位-1    }    fds[0]=listen_sock;//用数组的第一个放listen_sock    while(1)    {        struct timeval timeout={5,0};        fd_set rfds;//定义文件描述符集        FD_ZERO(&rfds);//每次进来将清除rfds的全部位        maxfd=-1;        for(i=0;i<nums;i++)//遍历fds数组,看哪个文件描述符有效        {            if(fds[i]>0)            {                FD_SET(fds[i],&rfds);//fds[i]有效,将设置在rfds中fds[i]的相关位                if(maxfd<fds[i])                {                    maxfd=fds[i];//找到文件描述符的最大值                }            }        }        switch(select(maxfd+1,&rfds,NULL,NULL,&timeout))        {            case 0:                printf("timeout..\n");//没有文件描述符事件就绪                break;            case -1:                perror("select");//调用失败                break;            default:                {                    for(i=0;i<nums;i++)                    {                           if(fds[i]<0)                        {                            continue;//找到第一个有效文件描述符                        }                        if(i==0 && FD_ISSET//listen_sock(listen_sock,&rfds))                        {                        struct sockaddr_in client;                        socklen_t len=sizeof(client);                        int new_fd=accept(listen_sock,(struct sockaddr*)&client,&len);//接受客户请求                        if(new_fd<0)                        {                            perror("accept");//失败继续                            continue;                        }                        printf("get a new client[%s,%d]\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));                        int j=1;                        for(j=1;j<nums;j++)//将新的标识符文件描述符放入fds中                        {                            if(fds[j]==-1)                            {                                break;                            }                        }                        if(j==nums)                        {                            printf("server full..\n");                            close(new_fd);                        }                        else                        {                            fds[j]=new_fd;                        }                    }//if                    else if((i>0)&&FD_ISSET(fds[i],&rfds))//第二次轮询的时候fds[i]=new_fd ready,进行读事件                    {                        char buf[1024];                        while(1)                        {                        ssize_t s=read(fds[i],buf,sizeof(buf)-1);                        if(s>0)                        {                            buf[s]=0;                            printf("client say:%s",buf);                            fflush(stdout);                        //  fds[i]=-1;                        }                        else if(s==0)                        {                            printf("client quit!\n");                            close(fds[i]);                            fds[i]=-1;//断开连接将fds[i]置为1。                            break;                        }                        else                        {                            perror("read");                            break;                        }                        }                    }//else if                    else                    {}                    }//for                }//default                break;        }//switch    }//whilereturn 0;}
//client.c#include<stdio.h>#include<sys/types.h>#include<sys/socket.h>#include<netinet/in.h>#include<arpa/inet.h>#include<stdlib.h>#include<string.h>void usage(const char* proc){    printf("%s [server_ip] [server_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("sock");        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);    }    char buf[1024];    while(1)    {        printf("please enter:");        fflush(stdout);        ssize_t s=read(0,buf,sizeof(buf)-1);        if(s>0)        {            buf[s]=0;            write(sock,buf,strlen(buf));        }    }    return 0;}

select缺点

  • 每次调用select,都需要把fd集合从用户态拷贝到内核态
  • 每次调用select都需要在内核中遍历所有的fd
  • select支持的文件描述符个数太小,为sizeof(fd_set)*8=1024