TCP Socket 编程模型

网络 I/O 模型定义的是应用线程与操作系统内核之间的交互行为模式。我们通常用阻塞(Blocking)/非阻塞(Non-Blocking)来描述网络 I/O 模型。

  • 阻塞 I/O(Blocking I/O)

    阻塞 I/O 是最常用的模型,这个模型下应用线程与内核之间的交互行为模式如下

在阻塞 I/O 模型下,当用户空间应用线程,向操作系统内核发起 I/O 请求后(一般为操作系统提供的 I/O 系列系统调用),内核会尝试执行这个 I/O 操作,并等所有数据就绪后,将数据从内核空间拷贝到用户空间,最后系统调用从内核空间返回。而在这个期间内,用户空间应用线程将阻塞在这个 I/O 系统调用上,无法进行后续处理,只能等待。

因此,在这样的模型下,一个线程仅能处理一个网络连接上的数据通信。即便连接上没有数据,线程也只能阻塞在对 Socket 的读操作上(以等待对端的数据)。虽然这个模型对应用整体来说是低效的,但对开发人员来说,这个模型却是最容易实现和使用的,所以,各大平台在默认情况下都将 Socket 设置为阻塞的。

  • 非阻塞 I/O(Non-Blocking I/O)

    非阻塞 I/O 模型下,应用线程与内核之间的交互行为模式是这样的:

在非阻塞模型下,当用户空间线程向操作系统内核发起 I/O 请求后,内核会执行这个 I/O 操作,如果这个时候数据尚未就绪,就会立即将“未就绪”的状态以错误码形式(比如:EAGAIN/EWOULDBLOCK),返回给这次 I/O 系统调用的发起者。而后者就会根据系统调用的返回状态来决定下一步该怎么做。

在非阻塞模型下,位于用户空间的 I/O 请求发起者通常会通过轮询的方式,去一次次发起 I/O 请求,直到读到所需的数据为止。不过,这样的轮询是对 CPU 计算资源的极大浪费,因此,非阻塞 I/O 模型单独应用于实际生产的比例并不高。

  • I/O 多路复用(I/O Multiplexing)

    为了避免非阻塞 I/O 模型轮询对计算资源的浪费,同时也考虑到阻塞 I/O 模型的低效,开发人员首选的网络 I/O 模型,逐渐变成了建立在内核提供的多路复用函数 select/poll 等(以及性能更好的 epoll 等函数)基础上的 I/O 多路复用模型。

    这个模型下,应用线程与内核之间的交互行为模式如下图:

    在这种模型下,应用线程首先将需要进行 I/O 操作的 Socket,都添加到多路复用函数中(这里以 select 为例),然后阻塞,等待 select 系统调用返回。当内核发现有数据到达时,对应的 Socket 具备了通信条件,这时 select 函数返回。然后用户线程会针对这个 Socket 再次发起网络 I/O 请求,比如一个 read 操作。由于数据已就绪,这次网络 I/O 操作将得到预期的操作结果。

    相比于阻塞模型一个线程只能处理一个 Socket 的低效,I/O 多路复用模型中,一个应用线程可以同时处理多个 Socket。同时,I/O 多路复用模型由内核实现可读 / 可写事件的通知,避免了非阻塞模型中轮询,带来的 CPU 计算资源浪费的问题。

    目前,主流网络服务器采用的都是“I/O 多路复用”模型,有的也结合了多线程。

posted @ 2022-03-13 21:25  请务必优秀  阅读(94)  评论(0编辑  收藏  举报