I/O多路复用之select

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.c
include<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
    }//while
return 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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值