1. 什么是网络IO?

  • 网络 IO 指的是程序通过网络接口(通常是 socket)进行数据收发的过程。
  • 简单说就是:程序和网络上的另一台机器交换数据
  • 在服务端和客户端通信场景中:
    • 发送数据 → 写入 socket,传输到对方
    • 接收数据 → 从 socket 读取对方发送的数据

可以理解为程序对网络数据的“读写操作”:

读取数据(Receive / Read)

  • 从网络缓冲区中获取客户端或远程主机发来的数据。
  • 对服务端来说,就是“接收客户端请求”。

写入数据(Send / Write)

  • 将数据写入网络缓冲区,由操作系统发送到远程主机。
  • 对服务端来说,就是“给客户端发送响应”。

2. IO的两个阶段:数据就绪、数据读取

2.1. 阶段一:数据就绪

这一阶段由 操作系统内核 负责。

  • 当客户端(如浏览器)通过网络发送数据包时,这些数据首先会被操作系统接收到,并存放到 内核的 socket 接收缓冲区 中。
  • 但对应用程序(例如服务器进程)来说,这时数据还“在内核里”,还没被用户程序拿到。
  • 如果应用程序此时调用 read() 去读取 socket:(以阻塞为例)
    • 若数据还没到达,read() 就会阻塞(等待数据就绪)。
    • 一直到内核检测到数据到达,标记为“可读”,这个阶段才算结束。

总结:数据就绪阶段就是 等待数据到达网络并被内核接受好。(网络数据进入socket读缓冲区,缓冲区标记为”可读“态)

2.1.1. 阻塞

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

上面的recv函数通过调整sockfd(文件描述符)参数,可以设置recv为“阻塞”或“非阻塞”。

默认recv为阻塞。此时调用recv,若数据还未就绪,线程就会被挂起,一致等待数据准备好再返回。

2.1.2. 非阻塞

当recv为非阻塞时,如果发现数据还没就绪,直接返回。

2.2. 阶段二:数据读写

当内核通知“数据已就绪”之后:

  • 应用程序再执行 read() 系统调用;
  • 操作系统将数据 从内核缓冲区拷贝到用户空间缓冲区(即应用程序可访问的内存中)。
  • 拷贝完成后,应用程序才能真正处理这批数据。

总结:数据读写极端就是 从内核向用户空间传输数据的过程。 (从socket 读缓冲区读取数据,或者把数据写入socket写缓冲区)

2.2.1. 同步IO

内核准备好数据后,必须应用程序通过自己把数据读出来。

同步 I/O: 程序自己搬数据,得等着。

2.2.2. 异步IO

应用程序发起请求后,操作系统完成所有IO操作,包括数据拷贝。当整个过程完成后,内核再通知应用程序。再这期间应用程序完全可以做别的事情。

异步 I/O: 系统帮你搬数据,搬完再通知你。

2.3. 区分阻塞/非阻塞和同步/异步

老师有一句非常粗暴的话“阻塞、非阻塞都是同步IO,只有调用了系统异步IO的api函数的时候才是异步IO”。

但是老师又讲到 “异步IO一般和非阻塞组合使用,因为和阻塞使用没有意义。如果使用阻塞,内核处理数据的时候,应用程序仍然挂起,没有起到提高效率的效果。”

“异步IO一般和非阻塞组合使用“。看到这句话我完全懵了。

非阻塞不是同步吗?因为非阻塞本质上也是我们在主动recv,从socket那里面拿数据啊?

寻问AI后才获得答案:

  • 真正的“异步 I/O”是 aio_read()io_uring 这种系统帮你全做完的。
  • 但这种机制在早期 Linux 上不成熟,大部分网络服务器并不用它

实际工程角度来看:

  • 我们通常想实现“异步处理”——线程不阻塞、一个线程能同时处理多个 socket。
  • 所以我们采用:
    1. 把 socket 设置成 非阻塞
    2. epoll/selectI/O 复用 技术检测“哪些 socket 有事件”;
    3. 一旦有事件就调用相应的 读写回调函数
    4. 没数据就立即返回去干别的。

这整套机制(Reactor 模式)虽然在系统角度上是同步 I/O
在框架层面实现了异步效果——应用不会被卡住。

我们通过“非阻塞 I/O + 事件驱动模型”模拟出了“异步”的行为效果,这其实是通过非阻塞+事件回调机制实现的 ”伪异步“。

本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:[email protected]