IO复用之select

一.背景
最初,在学习并发编程时,无论是多线程还是多进程,我们总是会在我们应用程序中调用各种各样的系统调用(如accept()、read()、recv()等)来监听客户端的各种事件,这样显然会使得我们的应用程序显得庞大,效率自然而然的会下降,所以,此时多路IO转接服务程序就产生了。

二.多路IO转接服务器
多路IO转接服务器是只应用程序中调用了系统内核的一种函数,这种函数实现了帮应用程序监控客户端事件是否发生,如果对应事件发生,告诉我们应用程序哪个文件描述符上有事件发生。这样我们的应用程序直接去处理事件即可,缓解了时刻监听所有文件描述符的压力。
在这里插入图片描述

有点像代理的意思,或者说是中间层,内核帮我们找到事件发生的文件描述符
我们自己的应用程序直接去处理就好了,就不需要我们等待什么的,客户和服务器交互立即可以完成

举个例子:
服务器和客户端建立链接,执行accept()一般会阻塞等待,但是我们可以将socketfd
交给内核,让内核帮我们监控起来,如果有事件(链接的事件)发生,
服务器就可以马上和这个sockfd建立链接,如果没有事件发生,这个时间我们可以做其他事
就不用一直等他
当我们建立链接以后又得到一个fd,我们又可以把这个fd交给内核帮我们监控起来

三.内核暴漏的接口 :select()

/* According to POSIX.1-2001, POSIX.1-2008 */
       #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);

四.select()函数参数以及对应操作函数的解释:

	   1.
	   返回值:
       返回所有监听集合中,有对应事件发生的文件描述符的总数
       2.
       参数解释:
       int nfds:
       监控的文件描述符里数值最大的文件描述符+1,
       例如,所有需要select监控文件描述符中数值最大的是100,则该值即为101
       select是从0开始一直遍历到数值最大的标识符。 
       fd_set *readfds:
       监控有读事件产生的文件描述符的集合,实际上是个位图,传入传出参数
       加入到这些集合中,只是觉得有可能发生对应的事件,也有可能不会发生
       fd_set *writefds:
       监控有写事件产生的文件描述符的集合,实际上是个位图,传入传出参数
       fd_set *exceptfds:
       监控有异常事件产生的文件描述符的集合,实际上是个位图,传入传出参数
       
	   struct timeval *timeout:
	   原型:
	    struct timeval {
               long    tv_sec;         /* seconds  秒 */  秒
               long    tv_usec;        /* microseconds 微秒 */
           };

	   定时器,告诉内核监控这些事件多长时间,分三种情况:
	   1.NULL,永远的等下去
	   2.设置timeval,等待固定的时间
	   3.设置timeval里事件为均为0,检查描述字后立即返回,轮询的方式

	   3.
       void FD_CLR(int fd, fd_set *set);
       //将fd从set中清除出去,也就是将对应位图的对应位置0
       int  FD_ISSET(int fd, fd_set *set);
       //判断对应的fd是否在set中
       void FD_SET(int fd, fd_set *set);
       //将对应的fd添加到对应的set中 
       void FD_ZERO(fd_set *set);
        //将对应的set进行清空,也就是置为0

五.优缺点总结

1.可以监听的事件类型:读、写、异常;
2.返回值:
假设我三种类型的文件描述符集合分别为:
r: 7 8 9         (7 8 有读事件产生)
w:8 10 27       (8 有写事件产生)
e: 7 8 9 10 11   (7 8  11有异常事件产生)
那么返回值将返回5,因为有五个文件描述符有事件产生,即使可能有重复的
是三个集合中所有有事件产生的文件描述符之和
(当然此时函数的第一个参数可能是11 + 1 =12)


缺点:返回值没有告诉我们的应用程序到底是哪个描述符发生了什么事件,需要我们自己去判断

3.对于三个传入传出参数,传入时为了让内核使用,让内核根据监听,修改对应位上哪个fd有事件产生
就置位1
等到定时器事件到了,然后再将内核修改的fd个数返回回去,
可惜的是,具体哪个fd,哪个事件,还需要我们应用程序自己去判断

4.文件描述符上限 1024 ,也就是最多可以监听的fd数据
因为在unix那个时期,由于硬件水平低,1024已经足够了,不然内存大小跟不上
相当于这个1024是写死了,但是实在想改,是可以的,需要重新编译linux内核

5.加入select()一共监听2和1023这两个fd,返回时返回值为2
但是我不知道到底是哪个,所以需要我们从2到1023循环遍历,挺影响效率的

6.传入和传出的集合都是同一个集合,所以我在修改前需要将原来的集合做一份拷贝
当监听的比较大的适合,拷贝也费时间

传入select()前的读事件对应的位图:
置1的是我们希望select()给我们监听的文件描述符,这里是三个,如图
在这里插入图片描述
当select()返回,就说明监听的三个集合中的某一个或者某几个集合中一些fd有事件发生

传出后的读事件对应的位图: 满足保持置1,不满足的修改为0,然后传出给用户
在这里插入图片描述
这个例子中,我们传入三个fd,最终有两个fd有事件发生
其他两个事件类型对应的原理是一样的。

顺序:

初始化
fd_set readfds;
FD_ZERO(&readfds);

给readfds添加我们期望监控的fd
FD_SET(fd,&readfds);
......
然后调用select(),等到函数返回后
FD_ISSET(fd,&readfds); //判断fd是否发生了读事件

###########################################################################
补充几点,以前一些东西可能没有解释清楚:

1)select的实现,在内核里它是被定义成一个数组的。数组肯定就有大小,数组的大小被默认定义了1024,默认的情况下只能支持1024个socket的管理。

2)select返回后,应用层需要不断轮询这个fd_set,去判断socket期望的是否发生。

3)select返回后,会把内核管理的这个fd_set清空,依然需要把用户态的socket拷贝到内核的select管理的fd_set,发生频繁从用户态的数据copy到内核里面。

poll的实现,对select进行了改进,就是用链表去保存了socket。也就是说克服了select对socket数量的限制。

高并发的情况下使用select或者poll能支持的并发是有限的。 C10K(一万个左右的并发)

因此内核为了解决这个问题,内核重新设计的多路并发,也就是使用epoll来管理了socket。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 C 语言中,可以使用 `select` 函数来复用 I/O。`select` 函数能够同时监视多个文件描述符的状态,包括可读、可写和异常等状态,并在有状态变化时通知程序进行相应的操作。 下面是 `select` 函数的基本用法: ```c #include <sys/select.h> int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); ``` 参数解释: - `nfds`:被监听的最大文件描述符,即待检查的文件描述符的值最大的那个加 1。 - `readfds`:指向可读文件描述符集合的指针。 - `writefds`:指向可写文件描述符集合的指针。 - `exceptfds`:指向异常文件描述符集合的指针。 - `timeout`:超时时间,若设置为 NULL,则表示一直等待,直到有文件描述符就绪。 `fd_set` 是一个位图,用于表示文件描述符集合。可以使用以下宏来设置和清除对应位: - `FD_ZERO(fd_set *set)`:将指定的文件描述符集合清空。 - `FD_SET(int fd, fd_set *set)`:将指定的文件描述符加入到集合中。 - `FD_CLR(int fd, fd_set *set)`:将指定的文件描述符从集合中删除。 - `FD_ISSET(int fd, fd_set *set)`:检查指定的文件描述符是否在集合中。 `select` 函数会阻塞程序,直到指定的文件描述符集合中的任何一个文件描述符发生变化或者超时。返回值为就绪的文件描述符数量,若返回 -1,则表示出错。 需要注意的是,`select` 函数的效率可能会受到文件描述符数量的限制,在大规模并发的场景下,可能需要使用其他方法来处理。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值