Linux C 笔记:Socket 的 select 函数及 I/O 多路复用

select 函数

它的直接作用是什么?

select() and pselect() allow a program to monitor multiple file descriptors, waiting until one or more of the file descriptors become "ready" for some class of I/O operation (e.g., input possible). A
file descriptor is considered ready if it is possible to perform a corresponding I/O operation (e.g., read(2), or a sufficiently small write(2)) without blocking.

select 可以让一个程序监视多个文件描述符,一直等待直到至少一个文件描述符可以被用于读写。类似的函数还有 pollepollpselect

select 的本质作用就是判断是否可以进行 I/O 操作。

注意:select 返回可读,并不是代表一定可读。

Under Linux, select() may report a socket file descriptor as "ready for reading", while nevertheless a subsequent read blocks. This could for example happen when
data has arrived but upon examination has wrong checksum and is discarded. There may be other circumstances in which a file descriptor is spuriously reported as
ready. Thus it may be safer to use O_NONBLOCK on sockets that should not block.

select 为什么可以实现非阻塞

首先引入“边缘”的概念:

在电子学中,信号边缘(英语:signal edge),或称信号边沿,是数字信号在两种逻辑电平(0或1)之间状态的转变。由于数字信号电平由方波来表示,因此这种状态的变化被称为“边缘”。

实际上在 Socket 编程中,我们可以把“边缘”理解为“状态变化”。现在规定,以下讨论的前提都在 Socket 编程。

接下来引入两个概念:

  1. 条件触发(Level Triggered,又译为:水平触发,又称为 LT):只要满足条件(达到一个水平)就触发。
  2. 边缘触发(Edge Triggered,又译为:边沿触发、状态触发,又称为 ET):每当状态变化时触发。

这两种模式类似对应 GUI 开发中的两种渲染方式,一种是实时渲染(LT 需要实时判断),一种是只在发生变化后渲染。

这里的触发是什么意思?

具体来讲,就是发生 IO 事件,比如读和写。

那么状态触发中的“条件”是什么?

具体来说,就是对文件描述符执行I/O操作不会阻塞这一条件。

那么边缘触发中的“状态”是什么?

具体来说,就是 I/O事件。新的 I/O事件 到来会改变状态。如果 I/O事件没有发生(到来)就不会触发。

select 原理

首先要明确,select 本质上是同步 I/O,因为检测到就绪后就负责读写,而读写的过程是阻塞的。

  1. 调用后 select 时传入所有需要监听的描述符,然后函数会阻塞。
  2. 阻塞直到有描述符就绪(有数据可读,可写,或者有 except),或者超时(timeout 指定等待时间,如果立即返回设为 null 即可)
  3. 当有描述符就绪时,select 会从 fdset 集合里去掉所有非就绪的描述符,只留下已经就绪的,然后函数返回。
  4. 当 select 函数返回后,可以通过遍历 fdset 来找到就绪的描述符。

详见参考文献 6.

select 默认的工作方式是条件触发模式。

多路复用到底是复用了什么?和事件驱动的区别是什么?

多路复用,就是一个线程负责处理并发。事件驱动是多路复用的一种形式而已。

select 为什么可以实现非阻塞?

首先,“非阻塞” 所说的阻塞,是 socket 的 receive 等待。,前面说道select 的本质作用就是判断是否可以进行 I/O 操作。而 select 往往是写在一个死循环中。也就是说,select 会一直检查能不能读写了,能就标记为能读写。此后会有语句进行具体的读写。这样就避免了 socket 的 receive 等待。但是实际上读写的过程不是瞬间完成的,select 并不能避免读写过程中的时间等待。所以前面说:select 本质上是同步 I/O,因为检测到就绪后就负责读写,而读写的过程是阻塞的。

参考文献

  1. linux socket的select函数例子
  2. SELECT(2)
  3. I/O多路复用之epoll
  4. Edge and Level Triggered
  5. 信号边缘
  6. 网络 IO - 多路复用技术 select,poll,epoll
Written with StackEdit.

发表留言

本站启用了垃圾评论检测插件,如果误删请联系我~