通俗易懂讲解网络IO模型

前言:

作为服务端开发,为了提高整体服务效率,网络编程是我们必不可少的知识。本文将会从网卡接收数据流程讲起,串起cpu中断、操作系统、线程调度等知识,进一步分析select到epoll的演变过程。

1. 网卡接收数据

  下边是一个典型的计算机结构图,计算机由 CPU、存储器(内存)与网络接口等部件组成。为了解网络IO,那么得先从硬件角度看计算机是怎么接收网络数据的。

在这里插入图片描述

  下图展示了网卡接收数据的过程:

  1. 在 ① 阶段,网卡收到网线传来的数据;
  2. 经过 ② 阶段的硬件电路的传输;
  3. 最终 ③ 阶段将数据写入到内存中的某个地址上。

  这个过程涉及到 DMA 传输、IO 通路选择等硬件有关的知识,但我们只需知道:网卡会把接收到的数据写入内存

在这里插入图片描述

  通过硬件传输,网卡接收的数据都存放在内存中了,操作系统就可以读取它们。

2. 如果知道接收了数据?

  网卡接收到的数据放入内存后,那我们在操作系统中创建的Sokcet怎么感知有数据发送过来了呢?要理解这个问题,需要了解一个概念——中断。

  计算机执行程序时,会有优先级的需求。比如,当计算机收到断电信号时,它应立即去保存数据,保存数据的程序具有较高的优先级(电容可以保存少许电量,供 CPU 运行很短的一小段时间)。

  一般而言,由硬件产生的信号需要 CPU 立马做出回应,不然数据可能就丢失了,所以它的优先级很高。CPU 理应中断掉正在执行的程序,去做出响应;当 CPU 完成对硬件的响应后,再重新执行用户程序。中断的过程如下图,它和函数调用差不多,只不过函数调用是事先定好位置,而中断的位置由“信号”决定。

在这里插入图片描述

  以网卡为例:当网卡把数据写入到内存后,网卡向 CPU 发出一个中断信号,操作系统便能得知有新数据到来,再通过网卡中断程序去处理数据。网卡中断程序:根据指定的 IP 和端口,将接收数据写入对应socket的输入缓冲区。(Socket:四元组标识唯一)

3. BIO模型

  操作系统为了支持多任务,实现了进程调度的功能,会把进程分为"运行"和"等待"等几种状态。运行状态可以获得cpu执行权,参与CPU时间分片;等待状态是阻塞状态,进程进入等待队列,不会获得cpu执行权,当被唤醒则进入运行状态。操作系统分时执行各个运行状态的进程,由于速度很快,看上去就像是同时执行多个任务。

  BIO模型的accept、recv方法,如果没有数据则进入等待状态。

  下图的计算机中运行着 A、B 与 C 三个进程,其中进程 A 执行着BIO模型的基础网络程序,一开始,这 3 个进程都被操作系统的工作队列所引用,处于运行状态,会分时执行。

在这里插入图片描述

3.1 创建 Socket

  当进程 A 执行到创建 Socket 的语句时,操作系统会创建一个由文件系统管理的 Socket 对象(如下图)。这个 socket 对象包含了发送缓存区、接收缓冲区和等待队列等成员变量。等待队列这个变量很重要,它指向所有需要等待该 socket 事件的进程。

在这里插入图片描述

3.2 recv 阻塞

  当程序执行到 accept、recv 时,如果接收缓存区中没有数据,则会调用 park 将线程 A 挂起加入等待队列。由于工作队列只剩下了进程 B 和 C,依据进程调度,CPU 会轮流执行这两个进程的程序,不会执行进程 A 的程序。所以进程 A 被阻塞,不会往下执行代码,也不会占用 CPU 资源。

在这里插入图片描述

  当 socket 接收缓存区有数据后,会调用 unpark 唤醒进程 A,该进程从等待状态变成运行状态,继续执行进程 A 的代码,读取接收缓存区的数据。

3.3 唤醒进程

  这一步,贯穿网卡、中断与进程调度的知识,叙述阻塞 recv 下,内核接收数据的全过程。

  如下图所示:进程在 recv 阻塞期间,计算机收到对端传送的数据(步骤1),数据经由网卡传送到内存(步骤2),然后网卡通过中断信号通知 CPU 有数据到大,CPU 执行网卡中断程序(步骤3)。

  网卡中断程序:

  • 先将网络数据写入到对应 socket 的接收缓冲区里面(步骤5)
  • 调用 unpark 唤醒进程 A,进程 A 重新进入工作队列

在这里插入图片描述

  写入数据、唤醒线程之后:

在这里插入图片描述

  以上是BIO模型,内核接收数据全过程,这里我们可能会思考一个问题:

  1. 操作系统如何知道网络数据对应哪个 socket?

    答:socket : socket 是(IP + 端口 : IP + 端口)四元组,而网络数据包中包含了 IP 和端口信息,内核可以通过端口号找到对应的 socket

4. NIO 模型

  操作系统实现 NIO 模型和 BIO 模型最大的区别就是:accpet、recv 方法不是阻塞的,进程在执行 NIO 模型的 recv 方法即使没获取到数据,也会继续往下执行代码。

在这里插入图片描述

  当网卡接收到数据,流程图如下:

在这里插入图片描述

  应用层在实现 BIO 网络编程时,往往一个用户线程对应一个 socket,当前主流商业Java虚拟机的线程模型都是基于操作系统原生线程模型来实现,即采用1:1的线程模型,线程数设置过多,会导致频繁的上下文切换很浪费性能,所以 BIO 模型不太适合现在网络连接数多的场景。

  至于 NIO 网络模型,可以一个用户线程管理多个 socket,但是用户线程不知道哪个 socket 有数据进来,在用户程序得遍历所有 socket 进行read数据,当read数据时候,会从用户态切换到内核态(系统调用),调用内核recv方法读取数据。

5. 多路复用 select

  NIO 用户线程不太好统一管理多个 Socket,操作系统就提供了多路复用器,来管理多个 socket。

  我们先从不太高效的 select 来讲,最后再来分析高效的 epoll。

// TODO

猜你喜欢

转载自blog.csdn.net/weixin_44981707/article/details/115388987