高级IO

一、五种IO模型

1、阻塞式IO:(进程没有结束时,一直等待进程结束,不做其他的事情)

分为两个阶段:a、等待:等待数据,不知道数据何时发送过来,等待的时间比较长。

b、拷贝:

性能优化:a、优化程序,先进性测试,找到性能瓶颈,首先进行优化。b、对系统有一个充分的了解。CPU密集型:由CPU承担大部分的数据计算。因为等待的时间比较长,而拷贝的时间相对比较短,所以优化的主要任务是缩短等待的时间。

案例:调用recvfrom时,由于recvfrom默认情况下为一个阻塞模式进行,所以对应阻塞式的IO模型。调用recvfrom首先等待数据,对方将数据发送过来之后recvfrom才能继续执行,recvfrom一旦执行,就进行等待。等待的过程就是数据没有准备好的过程,数据准备好就是指对方把数据发送到网卡上,内核把网卡中的数据读取出来。数据准备好之后,recvfrom就会返回并且把数据拷贝到recvfrom中,即完成了这个过程。

2、非阻塞式IO(进程没有结束之前,可以先做其他事情,不用一直等待进程结束)

非阻塞IO往需要程序员循环的方式反复尝试读写文件描述符, 这个过程称为轮询. 这对CPU来说是较大的浪费, 一般只有特定场景下才使用。仍然分为两个阶段:

a、等待:等待时不是让recvfrom阻塞式的一直等,而是一旦没有准备好,立刻让recvfrom返回,一旦函数返回,原有的代码,就可以在这个时间间隙做其他的事情,相当于没有把等待的时间全部浪费掉的过程。数据准备好之后,便可以立刻完成拷贝,最终返回成功。

b、拷贝

3、信号驱动IO(一旦信号到达,立即处理信号,进程处于挂起状态,信号处理完成之前不能进行其他操作。)

首先,调用一个signal或者signalaction函数,注册一个信号处理函数,等待数据的过程全部交给信号机制,让signal关联到某个信号处理函数上,一旦数据就绪,就会出发signal信号。从而触发信号处理函数,就可以在信号处理函数中调用recvfrom,拷贝数据。缺陷:一旦出发信号就会调用信号捕捉函数,但是信号捕捉函数执行过程中,原有的代码都是一个挂起等待的状态,信号达到必须处理,不能选择,而且必须处理完,但是信号也有缓急。所以这种机制使用范围很小。

4、IO多路复用(一个进程或线程可以处理多个文件描述符)

需要使用其他的一些函数如select,epoll等。以select为例:在等待的阶段是由select函数等待,select函数可以同时等待若干个文件描述符,返回就绪的文件描述符(相当于select参数中可以传递一组文件描述符,这一组文件描述符的任何一个文件描述符就绪,select都会返回,返回之后,再由recvfrom对就绪的文件描述符进行读操作和写操作。)单个线程可以同时监管多个文件描述符,使成本降低。(当有很多客户端尝试连接服务器时,使用IO多路复用比较划算,相当于需要用一个很低的成本管理很多的文件描述符,很多客户端的数据传输。)

5、异步IO(信号到达,是否处理该信号或者干其他事情,取决于自己,更加灵活。)

数据的等待过程与调用者没有太大关系,通过系统调用相当于给操作系统提供了一个回调函数,进程做自己应该完成的任务,一旦就绪,操作系统自动调用回调函数,完成数据拷贝,等待数据的过程不会影响原有进程的执行。(服务器与服务器之间的内部交互,由于当前互联网所有的服务器,都采用分布式的方式,服务器之间也是内部通信的,此时服务器不仅扮演服务器的角色,也扮演了客户端的角色,这种场景下,客户端的数目很少,都是有限的,所以一般采用异步IO的形式。异步IO,一旦数据传输过去之后,就不用管了。)

同步与异步中的同步和同步与互斥中的同步是不同的概念。同步与互斥中的同步表示多个进程或线程之间按照特定的顺序先后执行。而同步与异步中的同步是指最终程序的执行结果由管理者自己管理,异步是指最终程序的执行结果由被调用者控制。

任何IO过程中, 都包含两个步骤. 第一是等待, 第二是拷贝. 而且在实际的应用场景中, 等待消耗的时间往都远高于拷贝的时间. 让IO更高效, 最核心的办法就是让等待的时间尽量少.

二、

1、同步与异步,阻塞与非阻塞的关系(正交关系):

同步阻塞:在进行执行过程中什么都没做,并且调用者管理执行的结果

同步非阻塞:非阻塞的方式,但是结果由调用者关注

异步阻塞:在进行执行过程中什么都没做,但是结果由被调用者关注

异步非阻塞:在进程结束之前可以干其他的事情,但是结果由被调用者关注

文件描述符默认是阻塞式的IO,可以通过加上一些选项变成非阻塞式的IO:

(1)如果不对默认输入进行修改,为阻塞模式

#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
//给某个文件描述符设置成非阻塞的形式,默认情况下,
//文件描述符是阻塞IO的形式,
//通过调用这个函数,就能把某个文件描述符改成非阻塞的形式
int SetNoBlock(int fd){
//获得属性
    int flag=fcntl(fd,F_GETFL);
//fcntl(int fd,in cmd,…/*arg*/);fd:文件描述符,表示对哪个文件描述符进行修改;
//cmd:传不同的值,使用不同的方式
//…/*arg*/:变长参数,随着cmd取值的不同,设置为不同的参数。
//通过F_GETFL获得当前对应文件的一些标记;F_SETFL:可以修改属性,达到设置为非阻塞的目的
     if(flag<0){
        perror("fcntl");
        return -1;
     }
     //设置属性为非阻塞
     int ret=fcntl(fd,F_SETFL,flag|O_NONBLOCK);
     return ret;
}
//把标准输入设置成非阻塞
int main()
{
    while(1){
       //定义一个缓冲区
       char buf[1024]={0};
       ssize_t read_size=read(0,buf,sizeof(buf)-1);//通过标准输入往缓冲区中读一个数据
       //默认情况下0号文件描述符是阻塞的,如果没有在终端输入任何一个数据,read会阻塞等待
         if(read_size<0){
           perror("read");
           continue;
       }
       if(read_size==0){
           printf("read done\n");
           break;
       }
       buf[read_size]='\0';
       printf("%s\n",buf);
    }
    return 0;
}

不输入数据,此时没有可以读取到的数据(read_size<0),输出出错错误

输入字符串,字符串会返回。

(2)将0号文件描述符标准输入设置为非阻塞

int main()
{
SetNoBlock(0);
    while(1){
       //定义一个缓冲区
       char buf[1024]={0};
       ssize_t read_size=read(0,buf,sizeof(buf)-1);//通过标准输入往缓冲区中读一个数据
       //默认情况下0号文件描述符是阻塞的,如果没有在终端输入任何一个数据,read会阻塞等待
         if(read_size<0){
           perror("read");
           continue;
       }
       if(read_size==0){
           printf("read done\n");
           break;
       }
       buf[read_size]='\0';
       printf("%s\n",buf);
    }
    return 0;
}

返回数据没准备好,立即返回出错信息

三、IO多路转接:

1、select:一次调用要接入的多个文件描述符的状态)

  #include <sys/select.h>
   int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

参数nfds是需要监视的最大的文件描述符值+1; (若是需要第10号文件描述符,则将第10号文件描述符置为1,加快对三个位图的遍历速度。三个位图表示的范围比较大。避免遍历整个位图,遍历到第一个参数结束。

【第2,3,4个参数都相当于是一个结构体指针,这个结构体是一个位图。select相当于是一次调用要接收的多个文件描述符的就绪状态,readfdswritefdsexceptfds相当于三个文件描述符的集合,通过位图的形式,来表示一组文件描述符。readfds表示这些文件描述符如果读状态就绪,select函数返回;writefds表示写就绪,如果可以写,文件描述符就返回;这三个集合既是输入参数也是输出参数。这三个参数作为输出的参数时,把就绪的文件描述符体现到位图中去

rdset,wrset,exset分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集合及异常文件描述符的集合; 

参数timeout为结构timeval,用来设置select()的等待时间(也是一个结构体指针,包含两个字段一个是秒数,一个是微秒数)timeout的取值:NULL:则表示select()没有timeout,select将一直被阻塞,直到某个文件描述符上发生了事件; 0:仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生。等待的时间是0,以非阻塞的形式等待。如果没有文件描述符就立刻返回    特定的时间值:如果在指定的时间段里没有事件发生,select将超时返回。

(1)fd_set结构体:就是一个位图,初始化、销毁、给某一位设置为1、给某一位设置为0、检测某一位是1还是0.

void FD_CLR(int fd, fd_set *set);      // 用来清除描述词组set中相关fd 的位   , 把某一位设置为0

int  FD_ISSET(int fd, fd_set *set);    // 用来测试描述词组set中相关fd 的位是否为真   ,判定这一位是否为1. 

void FD_SET(int fd, fd_set *set);      // 用来设置描述词组set中相关fd的位   ,把某一位设置为1

void FD_ZERO(fd_set *set);             // 用来清除描述词组set的全部位,全部设置为0.

(2)函数返回值:

执行成功则返回文件描述词状态已改变的个数 

如果返回0代表在描述词状态改变前已超过timeout时间,没有返回 

当有错误发生时则返回-1,错误原因存于errno,此时参数readfds,writefds, exceptfds和timeout 的值变成不可预测。

(3)通过select监控输入的情况:

# include<stdio.h>
# include<unistd.h>
# include<sys/select.h>

//这个程序使用select监控标准输入的就绪状态
//如果标准输入文件描述符就绪了,进行读数据并打印的操作。
int main()
{
   while(1){
   fd_set read_fds;//表示这个位图是用来描述监控哪些文件描述符的读状态
   FD_ZERO(&read_fds);//把整个位图初始化为全0
   FD_SET(0,&read_fds);//把0号文件描述符设置成1
   //设置select
   //select完成了等待的过程
   int ret=select(1,&read_fds,NULL,NULL,NULL);
                              //最大的文件描述符为0,加1为1;read_fds:监控哪些文件描述符的读就绪
                              //第三个参数为监控哪个文件描述符的写就绪,此处不用,置为NULL)
                              //不关注任何文件描述符的异常状态,置为NULL
                             //是一个完全阻塞形式,没有超时时间,所以最后一个参数置为NULL
   if(ret<0){
      perror("select");
      return 1;
   }
   //select是成功状态,尝试对数据进行读写,0号文件描述符就绪
   char buf[1024]={0};
   //执行read的时候意味着数据都准备好了,直接进行读(拷贝)即可;直接由read拷贝
   //可以让select一次等一批文件描述符
   //而用read,一次只能等一个文件描述符
   read(0,buf,sizeof(buf)-1);
   printf("%s\n",buf);
  }
   return 0;
}
           

敲入一串字符,select就会认为读就绪,就会执行到read返回函数

(4)socket就绪条件

A、读就绪

a、缓冲区中的字节数大于等于低水位标记(超过这个标记,则缓冲区太满了,把缓冲区的数据拷贝走(删除);read函数拷贝走数据,就是这个时候。让上层代码更快速的获取到有用数据)

b、对端关闭连接,此时对该socket读,则返回0

c、监听的socket上有新的连接请求

d、socket上有未处理的错误

B、写就绪

a、缓冲区超过一个低水位标记socket内核中, 发送缓冲区中的可用字节数(发送缓冲区的空闲位置大小), 大于等于低水位标记 SO_SNDLOWAT, 此时可以无阻塞的写, 并且返回值大于0;

b、socket的写操作被关闭(close或者shutdown). 对一个写操作被关闭的socket进行写操作, 会触发 SIGPIPE信号;

c、socket使用非阻塞connect接成功或失败之后;

d、socket上有未读取的错误;

实现一个select版本的TCP服务器(不用创建多个线程就可以实现多连接)

1、使用select完成所有的等待动作

2、select监控的文件描述符分为两类:a、listen_sock一般只有一个   b、new_sock可能有很多
如果listen_sock就绪,调用accept取出new_sock,将new_sock加入到select之中;
如果new_sock就绪,遍历结果集合,找出哪个文件描述符就绪,找到就绪的文件描述符之后,进行读写,只读写一次(当前读就绪了就立刻读,再写回去。而距离下次读就绪之间还有很长的等待,等待还是有select完成,所以读就绪,读一次、写一次,剩下的等待过程仍然交给select。)

# include<stdio.h>
# include<string.h>
# include<stdlib.h>
# include<unistd.h>
# include<sys/socket.h>
# include<netinet/in.h>
# include<arpa/inet.h>
# include<sys/select.h>
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
//重新封装一个位图
typedef struct FdSet{
   fd_set set;
//必须知道最大的文件描述符是谁(确定第一个参数时需要使用)
   int max_fd;//
}FDSet;
//位图的初始化
void FDSetInit(FDSet *fdset){
   FD_ZERO(&fdset->set);
   fdset->max_fd=0;
   return;
}
//位图的插入,此处的插入过程需要维护max_fd,max_fd可能发生了变化
void FDSetAdd(FDSet *fdset,int fd){
   FD_SET(fd,&fdset->set);
   if(fd>fdset->max_fd){
      fdset->max_fd=fd;
   }
}
//位图的删除
void FDSetDel(FDSet *fdset,int fd){
   FD_CLR(fd,&fdset->set);
   //删除了最大的元素通过遍历查找的方式找到第二大的元素
   int max_fd=-1;
   int i=0;
   for(;i<=fdset->max_fd;++1){
      if(!FD_ISSET(i,&fdset->set)){
        continue;
      }
      if(i>max_fd){
        max_fd=i;
      }
   }
   fdset->max_fd=max_fd;
//找最大元素的优化:从后往前遍历:从后往前遍历找到的第一个不为0的即为第二大的位置,因为文件描述符是连续
存放的,所以也能很快找到
}
int ProcessRequest(int new_sock){
    //循环从socket读写数据
    char buf[1024]={0};
    ssize_t read_size=read(new_sock,buf,sizeof(buf)-1);
    if(read_size<0){
      perror("read");
      return -1;
    }
    if(read_size==0){
      printf("read done\n");
      return 0;
    }
    buf[read_size]='\0';
//把结果写会new_sock中
    printf("[client %d]say %s\n",new_sock,buf);
    write(new_sock,buf,strlen(buf));
    return 1;
}
//初始化
int ServerInit(char *ip,short port)
{
   int fd=socket(AF_INET,SOCK_STREAM,0);
   if(fd<0){
     perror("socket");
     return -1;
   }
   //文件描述符达到上限则打开失败,一个文件描述符的上限是可配置的。
   sockaddr_in addr;
   addr.sin_family=AF_INET;
   addr.sin_addr.s_addr=inet_addr(ip);
   addr.sin_port=htons(port);
   int ret=bind(fd,(sockaddr*)&addr,sizeof(addr));
   if(ret<0){
     perror("bind");
     return -1;
   }
   ret=listen(fd,5);
   if(ret<0){
     perror("listen");
     return -1;
   }
   return fd;
}
//./server[ip][port]
int main(int argc,char *argv[])
{
   if(argc!=3){
      printf("Usage ./server [ip][port]\n");
      return 1;
   }
   //1、服务器的初始化,创建一个socket
   int listen_sock=ServerInit(argv[1],atoi(argv[2]));
   if(listen_sock<0){
     printf("ServerInit failed\n");
     return 1;
   }
   //初始化成功
   printf("ServerInit Ok\n");
   while(1){
     //select多路复用,既包含了等待,又包含了处理的过程
     //让所有的等待都有select完成
     //2、把listen_sock添加到对应的文件描述符位图之中
     //定义一个位图,把结构加到位图中去。
   FdSet fds;
   FDSetInit(&fds);
   FDSetAdd(&fds,listen_sock);
   whiel(1){
     int ret=select(fds.max_fd+1,&fds.set,NULL,NULL,NULL);//把位图放入第二个参数,某个客户端建立连接和>某个客户端断开连接都是读就绪的条件
   if(ret<)){//读失败
      perror("select");
      continue;
   }
//select返回得说明是哪个文件描述符就绪
   if(FD_ISSET(listen_sock,&fds.set)){//判断就绪的而文件描述符是不是listen_sock
      //如果有新的客户端建立连接,则listen_sock就会读就绪,此时就可以直接调用accept获取这个连接
      sockaddr_in peer;
      socklen_t len=sizeof(peer);
      int new_sock=accept(listen_sock,(sockaddr *)&peer,&len);
      //连接刚建立完毕,客户端可能还没有写数据,accept之后到read之前中间还有一个等待过程,这个过程也可>以交给select来完成,此时直接把new_sock加到文件描述符的集合当中。
      if(new_sock<0){
        perror("accept");
        continue;
      }
//成功了,则将new_sock加进去
      FDSetAdd(&fds,new_sock);
      printf("[client %d\n]",new_sock);
 }else{//new_sock就绪
      //处理new_sock的就绪情况,需要进行数据的读写,select返回的也是一个位图,所以也是同时有多个new_sock就绪,进行遍历
      int i=0;
      for(;i<=fds.max_fd;++i){
         if(!FD_ISSET(i,&fds.set)){//文件描述符没有就绪
            continue;
         }
         //对当前就绪的文件描述符进行读写处理操作
         int ret=ProcessRequest(i);//i表示要处理的文件描述符
         if(ret==0){
           //说明对端关闭了socket,我们也得关闭socket
           close(i);
           //把文件描述符从fds中删除
           FDSetDel(&fds,i);
         }
      }
    }
  }
   return 0;
}

与本意是同时监听的既是listen_sock,又是new_sock,但是这步之后返回的只是new_sock。后续有任何客户端和服务器建立连接就都处理不了。select每次重新调用都要重新设置一次,文件描述符状态,必须保证设置的文件描述符的集合是正确的。(既要关注new_sock又要关注listen_sock。

 

 

listen_sock每次都得重新设置,new_sock对端关闭则不用重新设置。

改正:

在每次设置文件描述符之前,把fds备份一下。

# include<stdio.h>
# include<string.h>
# include<stdlib.h>
# include<unistd.h>
# include<sys/socket.h>
# include<netinet/in.h>
# include<arpa/inet.h>
# include<sys/select.h>
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
//重新封装一个位图
typedef struct FdSet{
   fd_set set;
//必须知道最大的文件描述符是谁(确定第一个参数时需要使用)
   int max_fd;//
}FDSet;
//位图的初始化
void FDSetInit(FDSet *fdset){
   FD_ZERO(&fdset->set);
   fdset->max_fd=0;
   return;
}
//位图的插入,此处的插入过程需要维护max_fd,max_fd可能发生了变化
void FDSetAdd(FDSet *fdset,int fd){
   FD_SET(fd,&fdset->set);
   if(fd>fdset->max_fd){
      fdset->max_fd=fd;
   }
}
//位图的删除
void FDSetDel(FDSet *fdset,int fd){
   FD_CLR(fd,&fdset->set);
   //删除了最大的元素通过遍历查找的方式找到第二大的元素
   int max_fd=-1;
   int i=0;
   for(;i<=fdset->max_fd;++i){
      if(!FD_ISSET(i,&fdset->set)){
        continue;
      }
      if(i>max_fd){
        max_fd=i;
      }
   }
   fdset->max_fd=max_fd;
//找最大元素的优化:从后往前遍历:从后往前遍历找到的第一个不为0的即为第二大的位置,因为文件描述符是连续
存放的,所以也能很快找到
}
int ProcessRequest(int new_sock){
    //循环从socket读写数据
    char buf[1024]={0};
    ssize_t read_size=read(new_sock,buf,sizeof(buf)-1);
    if(read_size<0){
      perror("read");
      return -1;
    }
    if(read_size==0){
      printf("read done\n");
      return 0;
    }
    buf[read_size]='\0';
//把结果写会new_sock中
    printf("[client %d]say %s\n",new_sock,buf);
    write(new_sock,buf,strlen(buf));
    return 1;
}
//初始化
int ServerInit(char *ip,short port)
{
   int fd=socket(AF_INET,SOCK_STREAM,0);
   if(fd<0){
     perror("socket");
     return -1;
   }
   //文件描述符达到上限则打开失败,一个文件描述符的上限是可配置的。
   sockaddr_in addr;
   addr.sin_family=AF_INET;
   addr.sin_addr.s_addr=inet_addr(ip);
   addr.sin_port=htons(port);
   int ret=bind(fd,(sockaddr*)&addr,sizeof(addr));
   if(ret<0){
     perror("bind");
     return -1;
   }
   ret=listen(fd,5);
   if(ret<0){
     perror("listen");
     return -1;
   }
   return fd;
}
//./server[ip][port]
int main(int argc,char *argv[])
{
   if(argc!=3){
      printf("Usage ./server [ip][port]\n");
      return 1;
   }
   //1、服务器的初始化,创建一个socket
   int listen_sock=ServerInit(argv[1],atoi(argv[2]));
   if(listen_sock<0){
     printf("ServerInit failed\n");
     return 1;
   }
   //初始化成功
   printf("ServerInit Ok\n");
     //select多路复用,既包含了等待,又包含了处理的过程
     //让所有的等待都有select完成
     //2、把listen_sock添加到对应的文件描述符位图之中
     //定义一个位图,把结构加到位图中去。
   FDSet fds;
   FDSetInit(&fds);
   FDSetAdd(&fds,listen_sock);
   while(1){
     FDSet tmp_fds=fds;//tmp_fds只用于文件描述符的输出,fds用于文件描述符的输入//保证每次传入的fds都是>想要的值即可
//fds添加内容:有新的客户端连接进来;删除内容:客户端断开连接才删除
//fds中包含得内容:1、liisten_sock 2、所有得客户端对应得new_sock
//得保证每次传给selectfds都是一个符合要求得值(包含了listen_sock,所有已经连上得客户端对应得内容
     int ret=select(tmp_fds.max_fd+1,&tmp_fds.set,NULL,NULL,NULL);//把位图放入第二个参数,某个客户端建>立连接和某个客户端断开连接都是读就绪的条件
   if(ret<0){//读失败
      perror("select");
      continue;
   }
//select返回得说明是哪个文件描述符就绪
   if(FD_ISSET(listen_sock,&tmp_fds.set)){//判断就绪的而文件描述符是不是listen_sock
      //如果有新的客户端建立连接,则listen_sock就会读就绪,此时就可以直接调用accept获取这个连接
      sockaddr_in peer;
      socklen_t len=sizeof(peer);
      int new_sock=accept(listen_sock,(sockaddr *)&peer,&len);
      //连接刚建立完毕,客户端可能还没有写数据,accept之后到read之前中间还有一个等待过程,这个过程也可>以交给select来完成,此时直接把new_sock加到文件描述符的集合当中。
      if(new_sock<0){
        perror("accept");
        continue;
      }
//成功了,则将new_sock加进去
      FDSetAdd(&fds,new_sock);
      printf("[client %d\n]",new_sock);
 }else{//new_sock就绪
      //处理new_sock的就绪情况,需要进行数据的读写,select返回的也是一个位图,所以也是同时有多个new_sock就绪,进行遍历
      int i=0;
      for(;i<=tmp_fds.max_fd;++i){
         if(!FD_ISSET(i,&tmp_fds.set)){//文件描述符没有就绪
            continue;
         }
         //对当前就绪的文件描述符进行读写处理操作
         int ret=ProcessRequest(i);//i表示要处理的文件描述符
         if(ret==0){
           //说明对端关闭了socket,我们也得关闭socket
           close(i);
           //把文件描述符从fds中删除
           FDSetDel(&fds,i);
         }
      }
    }
  }
   return 0;
}

服务器端代码同上几篇博客。

实现多连接,关键在于快速调用到accept。

(4)select的特点                                                                                                                                                                       可监控的文件描述符个数取决与sizeof(fdset) 的值 。进程的文件描述符是可以配置的,在默认情况下是1024。文件描述符的上限是可配置的(ulimit),说明一般情况下不会使用默认值,IO多路复用,客户端的数目可能会非常多,一旦客户端过多,select回出现问题。在把fds加到select的同时会有一个备份的文件(由于select的参数既是输入参数,也是输出参数,必须保证不要因为select的输出结果而影响到下一次循环对select参数的设置。),保证每次传给select的参数都是正确的。maxfd是为了加速遍历的过程,由于整体位图的最大表示范围为sizeof(fdset),实际上使用select用不了这么多文件描述符,没必要遍历所有的文件描述符,nfds表示最大的文件描述符加1,可以简化遍历过程,加快遍历速度,避免遍历没有意义的文件描述符。

(5)select函数的缺点(相对于二poll的缺点):

a、每次调用select, 都需要手动设置fd集合, 从接口使用角度来说也非常不便而且机器容易出错。 

b、每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大 (IO多路复用,归根结底是内核在进行复用,把文件描述符传给select,本质上是要把你关注哪些文件描述符的集合告诉内核,需要把fd集合从用户态拷贝到内核态。由于每次使用select都需要遍历文件描述集合。如果文件描述符很多,反复遍历就会导致开销过大。)

c、同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大

d、select支持的文件描述符数量太小。

C、异常就绪

a、socket上收到带外数据. 关于带外数据, 和TCP紧急模式相关(回忆TCP协议头中, 有一个紧急指针的 字段), 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xuruhua

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值