0基础学会 IO多路复用技术(select代码实现)

一、select的使用

主旨思想:

1、构造一个关于文件描述符的列表,将要监听的文件描述符添加到列表当中;

2、调用一个系统函数,监听该列表中的文件描述符,直到这些描述符中的一个或者多个进行IO操作时,该函数才会返回。

(1)这个函数是阻塞的;

(2)函数对文件描述符的检测的操作由内核完成

3、在返回时,它会告诉进程有多少描述符进行IO操作。

#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);
    -参数:
        -nfds:委托内核检测的最大文件描述符的值+1
        -readfds:要检测的文件描述符的读的集合,委托内核检测哪些文件描述符的读的属性
            -一般检测读操作
            -对应的是对方发送过来的数据,因为读是被动的接收数据,检测的是读缓冲区
            -是一个传入传出参数
        -writefds:要检测的文件描述符的写的操作,委托内核检测哪些文件描述符写的属性
            -委托内核检测写缓冲区是不是还可以写数据(不满就可以写)
        -exceptfds:检测发生异常的文件描述符的集合
        -timeout:设置的超时时间
            struct timeval{
                long tv_sec;  //seconds
                long tv_usec; //microseconds
            };
          -NULL:永久堵塞,直到检测到了文件描述符有变化
          -tv_sec = 0 tv_usec = 0,不阻塞
          -tv_sec > 0 tv_usec > 0,阻塞对应时间
    返回值:
        -1,失败
        >0(n),检测的集合中有n个文件描述符发生了变化
void FD_CLR(int fd,fd_set *set);
功能:将参数文件描述符fd对应的标志位设置为0
int FD_ISSET(int fd,fd_set *set);
功能:判断fd对应的标志位是0还是1,返回值:fd对应的标志位的值如果是0则返回0,如果是1则返回1
void FD_SET(int fd,fd_set *set);
功能:将参数文件描述符fd对应的标志位置为1
void FD_ZERO(fd_set *set);
功能:fd_set 一共有1024bit,全部初始化为0

 分析工作原理:客户端A、B、C、D对应的文件描述符分别为3、4、100、101,则说明3、4、100、101文件描述符对应的标志位设置为1,如果这四个客户端向服务端发送了读信号,则使用FD_SET进行设置。如果一共有101个客户端,则需要遍历102个数据(0~101),所以select的第一个位置要设置为101+1。此时会把用户态的文件描述符状态位拷贝到内核态,如果此时只有A、B两个客户端发送了数据,而C、D没有发送数据,那么内核态的文件描述符循环遍历的时候只检测到A、B客户端发来的数据,而没有检测到C、D客户端发来的数据,此时内核态的文件描述符就会把C、D的文件描述符标志位置为0,并且把目前的这种状态返回给用户态,用户态会重新遍历文件描述符的状态,以上就是select的运行过程。接下来服务器就会进行读操作。

使用select可以实现多线程或者多进程的效果,接下来代码走起来

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <sys/select.h>
  4 #include <string.h>
  5 #include <arpa/inet.h>
  6 #include <sys/time.h>
  7 #include <sys/types.h>
  8 #include <unistd.h>
  9 int main()
 10 {
 11         //1、socket
 12         int socketfd=socket(AF_INET,SOCK_STREAM,0);
 13         if(socketfd==-1)
 14         {
 15                 perror("socket");
 16                 exit(-1);
 17         }
 18         //2、bind绑定
 19         struct sockaddr_in seraddr;
 20         seraddr.sin_family=AF_INET;
 21         seraddr.sin_port=htons(9999);
 22         seraddr.sin_addr.s_addr=INADDR_ANY;
 23         int bindfd=bind(socketfd,(struct sockaddr*)&seraddr,sizeof(seraddr));
 24         if(bindfd==-1)
 25         {
 26                 perror("bindfd");
 27                 exit(-1);
 28         }
 29         //3、监听listen
 30         int listenfd=listen(socketfd,128);
 31         if(listenfd==-1)
 32         {
 33                 perror("listenfd");
 34                 exit(-1);
 35         }
 36         //4、创建一个fd_set的集合,存放的是需要检测的文件描述符
 37         fd_set rdset,tmp;
 38         FD_ZERO(&rdset);//将rdset全部初始化为0
 39         FD_SET(socketfd,&rdset);//将socketfd文件描述符初始化为1
 40         int maxfd=socketfd;
 41         while(1)
 42         {
 43                 tmp=rdset;
 44                 //调用select系统函数,让内核帮检测哪些文件描述符有数据
 45                 int ret=select(maxfd+1,&tmp,NULL,NULL,NULL);
 46                 if(ret==-1)
 47                 {
 48                         perror("select");
 49                         exit(-1);
 50                 }
 51                 else if(ret==0)
 52                 {
 53                         continue;
 54                 }
 55                 else if(ret>0)
 56                 {
 57                         //说明检测到了文件描述符的对应缓冲区的数据发生了变化
 58                         if(FD_ISSET(socketfd,&tmp))
 59                         {
 60                                 struct sockaddr_in cliaddr;
 61                                 int len=sizeof(cliaddr);
 62                                 int cfd=accept(socketfd,(struct sockaddr *)&cliaddr,&len);
 63                                 //将新的文件描述符加入到集合中
 64                                 FD_SET(cfd,&rdset);
 65                                 //更新最大的文件描述符
 66                                 maxfd=maxfd > cfd ? maxfd : cfd;
 67                         }
 68                         for(int i=socketfd+1;i<=maxfd;i++)
 69                         {
 70                                 if(FD_ISSET(i,&tmp))
 71                                 {
 72                                         //说明这个文件描述符对应的客户端发来了数据
 73                                         char buf[1024]={0};
 74                                         int len=read(i,buf,sizeof(buf));
 75                                         if(len==-1)
 76                                         {
 77                                                 perror("read");
 78                                                 exit(-1);
 79                                         }
 80                                         else if(len==0)
 81                                         {
 82                                                 printf("client closed...\n");
 83                                                 FD_CLR(i,&rdset);
 84                                         }
 85                                         else if(len>0)
 86                                         {
 87                                                 printf("read buf=%s\n",buf);
 88                                                 write(i,buf,strlen(buf)+1);
 89                                         }
 90                                 }
 91                         }
 92                 }
 93         }
 94 
 95                 return 0;
 96 }

 上面代码是服务器的,客户端的代码没有往上放,用前面的代码复制过来就能用,基本上客户端的代码不用大改。

虽然select优点多多,但是他也有很多的缺点,比如:

1、每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd非常多的时候会很大;

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

3、select支持的文件描述符数量太小了,默认是1024;

4、fds集合不能重复使用,每次都需要重置。

太棒了,又学会一个知识点,记得复习哦。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值