WebServer 00 重要前置知识

简介

在梳理各种事件模型时,我切身体会到基础知识掌握不牢的痛苦。因此,我打算整理一篇文章,把关键的前置知识系统记录下来,为理解后续更复杂的内容打好基础。

1. 什么是网络IO?

因为我的工作与数据缓存相关,所以提到IO我脑子里就蹦出来磁盘IO。这种深刻印象让我理解起后面的内容十分别扭,所以这里着重整理一遍网络IO加深印象。

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

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

读取数据(Receive / Read)

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

写入数据(Send / Write)

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

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

这两个阶段对所有IO操作适用,这里以网络IO为例.

2.1. 阶段一:数据就绪

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

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

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

牛客的老师将分为数据就绪阶段分为阻塞和非阻塞两种方式。

也就是等待客户端发送的数据经过TCP协议栈,被内核接收完成的过程。

2.1.1. 阻塞

arduino 复制代码
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 + 事件驱动模型"模拟出了"异步"的行为效果,这其实是通过非阻塞+事件回调机制实现的 "伪异步"。

相关推荐
程序员良许6 小时前
三极管推挽输出电路分析
后端·嵌入式
Java水解7 小时前
【JAVA 进阶】Spring AOP核心原理:JDK与CGLib动态代理实战解析
后端·spring
编程小白20267 小时前
从 C++ 基础到效率翻倍:Qt 开发环境搭建与Windows 神级快捷键指南
开发语言·c++·windows·qt·学习
Java水解7 小时前
Spring Boot 4 升级指南:告别RestTemplate,拥抱现代HTTP客户端
spring boot·后端
宫水三叶的刷题日记7 小时前
工商银行今年的年终奖。。
后端
大黄评测7 小时前
双库协同,各取所长:.NET Core 中 PostgreSQL 与 SQLite 的优雅融合实战
后端
Java编程爱好者7 小时前
Java 后端定时任务怎么选:@Scheduled、Quartz 还是 XXL-Job?
后端
Java编程爱好者7 小时前
线程池用完不Shutdown,CPU和内存都快哭了
后端
神奇小汤圆7 小时前
Unsafe魔法类深度解析:Java底层操作的终极指南
后端
.小墨迹7 小时前
apollo学习之借道超车的速度规划
linux·c++·学习·算法·ubuntu