前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • HTTP

    • 基础篇

      • HTTP的前世今生
      • HTTP是什么
      • HTTP世界全览
      • HTTP分层
      • 键入网址到回车发生什么
      • HTTP报文是什么样子的
      • 理解请求方法
      • URI
      • 响应状态码
      • HTTP有哪些特点
      • HTTP优缺点
      • HTTP的实体数据
      • HTTP传输大文件
      • HTTP的连接管理
      • HTTP的重定向
      • HTTP的Cookie机制
      • HTTP的缓存控制
      • HTTP的代理服务
      • HTTP的缓存代理
    • 高级篇

    • 扩展篇

  • 浏览器相关

  • 计算机基础

完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

Socket|计算机基础篇

# 一、I/O 模型

30 秒速记

  • 一次输入操作可拆成两个阶段:数据到达并进入内核缓冲区,以及数据从内核缓冲区复制到应用缓冲区。
  • 不同 I/O 模型的主要差异,是进程在两个阶段如何等待、由谁感知就绪以及何时返回。
  • Unix I/O 模型包括阻塞式、非阻塞式、I/O 复用、信号驱动式和异步 I/O。
  • 讨论套接字读取是否“异步”时,需要分别检查等待数据和复制数据两个阶段,不能只看系统调用是否立即返回。

一次输入操作通常分为等待数据进入内核缓冲区,以及把数据复制到应用缓冲区两个阶段。 各类 I/O 模型的区别,本质上是进程怎样等待、谁负责感知就绪,以及调用在什么时机返回。阻塞式、非阻塞式、I/O 复用、信号驱动式和异步 I/O 都是在这两个阶段上做不同安排。判断是否真正异步,不能只看调用有没有立即返回,还要看后续等待和复制是否仍由应用发起。

一个输入操作通常包括两个阶段:

  • 等待数据准备好
  • 从内核向进程复制数据

对于一个套接字上的输入操作,第一步通常涉及等待数据从网络中到达。当所等待数据到达时,它被复制到内核中的某个缓冲区。第二步就是把数据从内核缓冲区复制到应用进程缓冲区。

Unix 有五种 I/O 模型:

  • 阻塞式 I/O
  • 非阻塞式 I/O
  • I/O 复用(select 和 poll)
  • 信号驱动式 I/O(SIGIO)
  • 异步 I/O(AIO)

原理拆解: “同步/异步”与“阻塞/非阻塞”不是同一维度。非阻塞只表示调用在当前条件不满足时迅速返回,后续重试和数据复制仍由应用发起;真正的异步完成语义要求提交读取后,内核负责等待与复制,并以完成事件告知结果。select、poll、epoll、kqueue通常属于就绪通知机制,应用拿到可读事件后仍要持续读取,直到返回“暂不可读”,否则边沿触发场景可能遗漏剩余数据。

最小验证: 输入是服务端分两次写入的两段字节;要验证的结论是:事件通知只保证当前存在可读数据,不保证一次回调对应一次发送,也不保证得到完整业务消息。以下代码可由 Node.js 直接执行:

const net = require('node:net');

const server = net.createServer(socket => {
  socket.write('HEL');
  setTimeout(() => socket.end('LO\nWORLD\n'), 20);
});

server.listen(0, '127.0.0.1', () => {
  const { port } = server.address();
  const client = net.createConnection({ port, host: '127.0.0.1' });
  let buffer = '';

  client.on('data', chunk => {
    console.log('本次读取字节:', chunk.length);
    buffer += chunk.toString('utf8');
    while (buffer.includes('\n')) {
      const index = buffer.indexOf('\n');
      console.log('完整消息:', buffer.slice(0, index));
      buffer = buffer.slice(index + 1);
    }
  });

  client.on('end', () => {
    console.log('剩余数据:', JSON.stringify(buffer));
    server.close();
  });
});
@前端进阶之旅: 代码已经复制到剪贴板

关键逻辑是把每个 chunk 累积到 buffer,再按换行符解析消息。预期输出包含 HELLO 和 WORLD,但“本次读取字节”的次数与大小不固定:操作系统和运行时可能合并两次写入,也可能进一步拆分。end 表示字节流结束,不能用单次 data 回调边界代替协议边界。

边界与排查: 可读事件还可能由对端关闭或错误触发;读取返回零字节通常表示有序关闭,而“暂不可读”不等于连接结束。工程验证应同时记录就绪等待时长、每次读取字节数、读取循环次数、事件循环延迟和业务帧解析耗时。Linux 上可结合 strace 观察 epoll_wait、read 等调用,但具体系统调用取决于操作系统、运行时版本和传输实现,不能仅凭 JavaScript 回调名称判断底层 I/O 模型。

面试官追问

追问 1某个 Socket 页面使用非阻塞读取,系统调用在无数据时立即返回;开发者据此在评审会上宣称已经实现异步 I/O,你会怎样判断?
参考回答

立即返回只能说明当前调用没有阻塞,不能据此认定为异步完成语义。若后续仍由应用等待就绪、重试读取并触发内核到进程的复制,它仍是非阻塞或就绪通知模式;真正异步需要内核完成等待与复制后再通知结果。

追问 2Node.js 服务端分两次执行 write 发送 HEL 和 LO\nWORLD\n,客户端却只收到一次 data 回调;页面解析器按回调次数切消息,你会怎样改?
参考回答

必须把每个 chunk 累积到应用缓冲区,再按换行符等协议边界提取完整消息。Socket 提供的是字节流,一次可读通知既不对应一次发送,也不保证完整业务帧;解析器还要保留末尾不足一帧的数据等待后续字节。

追问 3Linux 服务把 select 换成边沿触发的 epoll 后,每次可读事件只调用一次 read,随后部分连接长期不再处理;你会怀疑什么?
参考回答

应怀疑应用没有持续读取到“暂不可读”,导致已经留在接收缓冲区中的数据不再产生预期事件。边沿触发只通知就绪状态变化,不负责交付完整消息;读取循环还要区分暂不可读、对端关闭和真实错误。

追问 4接口时延升高时,监控只记录一次 Socket API 的总耗时;值班同学想据此判断网络慢还是业务解析慢,你会要求补哪些观测?
参考回答

应拆分就绪等待时长、每次读取字节数、读取循环次数、事件循环延迟和业务帧解析耗时。必要时在 Linux 上结合 strace 观察 epoll_wait、read 等调用,但底层调用受系统与运行时影响;仅凭 JavaScript 回调名不能确定 I/O 模型。

追问 5连接收到可读事件后,read 返回 0,业务代码把它当成暂时没数据并继续保留连接;这会带来什么判断错误?
参考回答

读取返回 0 通常表示对端有序关闭,而不是暂不可读,继续保留连接可能形成无效状态和资源占用。暂不可读应根据非阻塞调用的错误语义识别;可读事件也可能源于关闭或错误,不能等同于一定收到业务数据。

追问 6架构选型时,一方认为 select、poll、epoll 收到事件后就已经完成数据复制,另一方坚持它们只是就绪通知;你会支持哪一方?
参考回答

应支持就绪通知的判断,应用收到可读事件后仍需主动读取,完成内核缓冲区到应用缓冲区的数据复制。它们可减少对多个描述符逐个阻塞等待的困难,但不会自动解决拆包、协议边界或读取循环;具体机制还依赖操作系统。

# 阻塞式 I/O

30 秒速记

  • 阻塞式读取发起后,应用进程会暂停执行,直到数据完成从内核缓冲区到应用缓冲区的复制。
  • 以套接字接收为例,recvfrom()承担接收数据并写入应用提供的 buf。
  • 被阻塞的是当前应用进程,不是整个操作系统;调度器仍可运行其他进程。
  • 等待期间当前进程不会通过反复查询消耗 CPU,但它也无法继续处理自己的其他工作。

阻塞式 I/O 会让调用线程一直等待,直到数据复制到应用缓冲区、发生错误或被中断后才返回。 例如调用 recvfrom() 时,数据没到就由内核挂起当前线程,数据到达后再复制进调用者提供的 buf。被阻塞的只是当前调度实体,其他线程和进程仍可运行,因此并不是整个操作系统停住了。它的控制流比较直观,但连接很多时,线程内存、调度和上下文切换也会带来额外成本。

应用进程被阻塞,直到数据从内核缓冲区复制到应用进程缓冲区中才返回。

应该注意到,在阻塞的过程中,其它应用进程还可以执行,因此阻塞不意味着整个操作系统都被阻塞。因为其它应用进程还可以执行,所以不消耗 CPU 时间,这种模型的 CPU 利用率会比较高。

下图中,recvfrom() 用于接收 Socket 传来的数据,并复制到应用进程的缓冲区 buf 中。这里把 recvfrom() 当成系统调用。

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
@前端进阶之旅: 代码已经复制到剪贴板

原理拆解: 阻塞式读取包含两个连续阶段:数据尚未到达时,内核让调用线程进入睡眠并等待套接字就绪;数据到达后,内核再把内容从套接字接收缓冲区复制到调用者传入的 buf。recvfrom()只有在复制完成、发生错误或被信号中断等情况下才返回。阻塞通常落实在线程这一调度实体上:同一进程中的其他线程以及系统中的其他进程仍可被调度,并非整台机器停止运行。

具体例子: 单线程服务器对一个连接调用阻塞式 recvfrom()时,如果客户端迟迟不发送数据,该线程就无法同时接受新连接或处理定时任务。若服务器采用“一连接一线程”,某个线程的阻塞不会直接卡住其他连接,但连接数量增大后,线程栈、调度和上下文切换会形成额外成本。阻塞模型因控制流顺序清晰,常用于连接数量有限、处理逻辑简单或已经由线程池隔离等待的场景。

边界与反例: “等待时不忙轮询”不等于阻塞式 I/O 没有成本:线程会占用内存及内核调度资源,唤醒后仍要消耗 CPU 完成复制和业务处理。套接字可读也不保证一次调用取得完整业务消息;流式套接字可能只返回部分字节,返回 0通常表示对端有序关闭连接,返回负值则应结合错误码区分中断、超时和其他故障。

工程验证: 可让客户端建立连接后延迟发送,观察服务线程停留在 recvfrom()附近,同时用系统监控确认其他进程仍正常运行且该线程没有持续占用 CPU。随后发送数据,应看到调用返回实际字节数。排查永久等待时,还应核对套接字接收超时、对端是否关闭、协议是否真的会发送数据,以及业务是否错误地假定一次读取必然得到完整报文。

← Redis攻击技术 →

fe
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • HTTP

    • 基础篇

      • HTTP的前世今生
      • HTTP是什么
      • HTTP世界全览
      • HTTP分层
      • 键入网址到回车发生什么
      • HTTP报文是什么样子的
      • 理解请求方法
      • URI
      • 响应状态码
      • HTTP有哪些特点
      • HTTP优缺点
      • HTTP的实体数据
      • HTTP传输大文件
      • HTTP的连接管理
      • HTTP的重定向
      • HTTP的Cookie机制
      • HTTP的缓存控制
      • HTTP的代理服务
      • HTTP的缓存代理
    • 高级篇

    • 扩展篇

  • 浏览器相关

  • 计算机基础