IO多路复用select原理


前言

在计算机网络编程中,IO(输入/输出)操作通常是服务端与客户端通过网络数据交互的重要环节。IO多路复用是一种高效的IO处理机制,它允许程序同时监视多个IO流,当其中任何一个IO流准备好进行读写操作时,程序能够及时得到通知并进行处理。select 是IO多路复用中的一种常用方法,本文将详细介绍 select 的原理、使用方法及其优缺点

一、select是什么?

功能:select函数可以让进程等待多个描述字的就绪状态,并根据不同的条件返回。它允许一个进程或线程同时监听多个文件描述符的I/O事件,并在有事件发生时进行处理。

适用场景:适用于需要同时处理多个I/O操作的场景,如服务器端需要同时监听多个客户端连接的读写请求等,可避免使用多个线程或进程来管理和执行这些I/O操作,从而减少系统资源浪费和编程复杂度。

//函数原型
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
参数解释:
	nfds:需要监听的文件描述符的最大值加1,即nfds = max(fd_set) + 1,用于指定需要检查的文件描述符的范围
	readfds:指向fd_set结构的指针,该结构用于存放需要检查读事件的文件描述符集合。如果该参数为NULL,则表示不检查读事件
	writefds:指向fd_set结构的指针,该结构用于存放需要检查写事件的文件描述符集合。如果该参数为NULL,则表示不检查写事件
	exceptfds:指向fd_set结构的指针,该结构用于存放需要检查异常事件的文件描述符集合。如果该参数为NULL,则表示不检查异常事件
	timeout:指向timeval结构的指针,用于指定select函数的超时时间。如果设置为NULL,则select会一直阻塞直到有文件描述符就绪;如果设置为{0, 0},则select不会阻塞,直接返回;如果设置为其他值,则select会阻塞等待指定的时间,如果在指定时间内没有文件描述符就绪,则返回
返回值
	大于0:表示有文件描述符就绪,返回值为就绪的文件描述符的数量
	等于0:表示在指定的超时时间内没有文件描述符就绪
	小于0:表示出错,如被信号中断等

二、select工作原理(重点)

我们先想明白几个概念:什么叫可读的文件描述符?什么叫可写的文件描述符?(异常事件暂未接触,就不做讨论了)。

  • LInux下一切皆文件。对于一切均看作文件,可以用文件描述符来标识其任意文件唯一性。所以,来自客户端的请求socket也可看作文件,有一个标识的socket_fd,而服务端的监听socket,以至于后面与客户端通信的通信socket都有一个描述符。当客户端发送请求到服务端,服务端的监听socket监听到了这个请求,那么监听socket的文件描述符server_fd此时状态应该是可读,如果不可读,那么服务器后续又怎么与客户端建立通信连接。
    假设服务端和客户端建立了连接,服务端会有一个与客户端通信的通信socket,文件描述符为client_fd。服务器获取客户端的请求是读取客户端socket里面的内容,此时客户端的socket文件描述符socket_fd是可读状态,服务端给客户端发送数据,是通过服务端的通信socket发数据的,此时,服务端通信socket文件描述符client_fd的状态为可写。

  • 根据以上思路,多个可读的socket文件描述就可构成一个可读文件描述符集合read_fds,多个可写socket文件描述符可构成一个可写文件描述符集合(虽然用的多个形容,但应该想到空集,或者1个文件描述符也是集合)。

  • 上面提到了文件描述符集合,在linux下,类型为fd_set,它的实现方式是一个位图bitmap,位图是hash思想,每一个位对应socket的状态,你可能会想到既然一个位对应socket的一个状态,socket定义3个状态,读、写、异常,所以一个socket在位图中应该使用2个位来标识(00=读,01=写,10=异常)。非也,下面开始解释:

我们会这样定义3个文件描述符集合,分别对应3中状态
fd_set read_fds;//读集合
fd_set write_fds;//写集合
fd_set exception_fds;//异常集合
可以看出,对于一个socket是否可写,只需要判读其是否在read_fds中,故只需位图一位即可
  • 那么,select函数具体是怎么工作的?

对于这样一个语句:int activities=select(max_fd+1,&read_fds,nullptr,nullptr,nullptr);
select在read_fds中扫描所有文件描述符,判断哪些是符合可读的,并把这些可读文件描述符覆盖放入read_fds中,并返回可读文件描述符的个数,即:通过select选出可读事件。
首先,我们要清楚,select函数是一个内核函数,那么一切都说的清了:
当我们使用select函数时,操作系统会把read_fds从用户态copy到内核态,通过内核判断哪些文件描述符是可读的,修改位图,然后,用户态从内核态copy文件描述符集合覆盖写入原来的文件描述符集合,这样就得到一个完全只包含可读文件描述符的集合,select就只选出了读事件。


总结

  • 优点
    提高并发性:单个进程或线程可以同时处理多个I/O操作,提升了系统的并发处理能力

    节约资源:避免了大量进程或线程的创建、调度和上下文切换,降低了系统开销

    简化编程模型:使用较少的线程或进程,使得程序的结构更加简洁,易于维护和管理

  • 缺点

    性能瓶颈:当监控的文件描述符数量较多时,select需要遍历整个文件描述符集合来检查哪些描述符就绪,这会导致性能下降,尤其是在高并发场景下

    资源限制:select支持的文件描述符数量存在上限,通常是1024个,这限制了其在大规模并发连接场景下的应用

    效率问题:每次调用select都需要将文件描述符集合从用户态拷贝到内核态,当文件描述符数量较多时,这种拷贝操作会带来较大的开销

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值