Computer NetworkNotes

第 28 章:Socket 与 IO 模型

zjc 于 2026-01-28 发布

这是《计算机网络 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 Socket 是应用访问网络的编程接口,IO 模型决定了应用如何等待和搬运数据。阻塞 IO 简单但并发成本高,IO 多路复用适合大量连接,异步 IO 进一步减少等待和复制开销。

28.1 Socket API

TCP 客户端:

import socket

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.settimeout(3)
client.connect(("10.20.1.10", 9000))
client.sendall(b"hello\n")
print(client.recv(4096))
client.close()

UDP:

import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(2)
sock.sendto(b"hello", ("10.20.1.10", 9000))
print(sock.recvfrom(4096))

常用选项:

选项 作用
SO_REUSEADDR 重启时快速绑定地址
SO_KEEPALIVE TCP 保活
TCP_NODELAY 关闭 Nagle
SO_SNDBUF / SO_RCVBUF 缓冲区
SO_LINGER close 行为
O_NONBLOCK 非阻塞

28.2 阻塞 IO

read()
  -> 没有数据就等待
  -> 有数据复制到用户态并返回

适合:

  1. 连接数少;
  2. 逻辑简单;
  3. 内部低并发任务;
  4. 每连接一个线程成本可接受。

问题:

  1. 连接数增加,线程增多;
  2. 上下文切换增加;
  3. 慢连接占用线程;
  4. 阻塞点难治理;
  5. 故障时线程池耗尽。

28.3 非阻塞 IO

read()
  -> 没有数据返回 EAGAIN
  -> 应用稍后再试或注册事件

非阻塞解决“等待”问题,但仍需事件通知机制知道什么时候读。

28.4 IO 多路复用

模型 特点
select 跨平台,fd 数量和效率受限
poll 无 select 数量限制,仍线性扫描
epoll Linux 事件通知,适合大量空闲连接
kqueue BSD/macOS 事件通知

适合:

  1. 大量连接;
  2. 大部分连接不完全活跃;
  3. 单机网关、推送、代理;
  4. 长连接服务。

注意:

  1. 惊群问题;
  2. 事件循环阻塞;
  3. 边缘触发漏读;
  4. 写事件需要动态注册;
  5. 仍需业务线程池隔离。

28.5 信号驱动与异步 IO

信号驱动 IO:

内核准备好人通知应用
应用自己调用 read 复制数据

异步 IO:

应用提交请求
内核完成等待和复制
通过回调或完成队列通知

Linux 的 io_uring 提供高效异步 IO,适合高 PPS、文件与网络混合 IO 的场景,但编程复杂度更高。

28.6 Java NIO 与 Netty

Java NIO:

Selector selector = Selector.open();
SocketChannel channel = SocketChannel.open();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_CONNECT
        | SelectionKey.OP_READ | SelectionKey.OP_WRITE);

Netty 在 NIO 之上封装了:

  1. EventLoopGroup;
  2. ChannelPipeline;
  3. ByteBuf;
  4. 编解码器;
  5. 空闲检测;
  6. 背压;
  7. 内存池;
  8. 优雅关闭。

业务代码不应把耗时操作放在 EventLoop 中,否则会阻塞该 EventLoop 上所有 Channel。

28.7 缓冲与消息边界

读取到 ByteBuf:

readableBytes
readerIndex
writerIndex
capacity

自定义协议:

| Length | Payload |

解码时必须处理:

  1. 半包;
  2. 粘包;
  3. 帧超长;
  4. 非法长度;
  5. 拆包攻击;
  6. 解压后大小。

28.8 IO 与业务隔离

EventLoop: accept / read / write / codec
Business Pool: 数据库、RPC、计算
External Pool: 第三方调用

好处:

  1. 慢业务不影响 IO;
  2. 线程池可独立观测;
  3. 不同依赖可独立隔离;
  4. 故障不扩散;
  5. 容量更可预测。

每个池必须有:

  1. 最大线程;
  2. 有界队列;
  3. 拒绝策略;
  4. 执行耗时;
  5. 队列长度;
  6. 活跃数;
  7. 失败数。

28.9 模型选择

场景 推荐模型
管理工具、低并发 阻塞 IO + 线程池
Web 服务 框架异步或线程池
网关 / 代理 epoll + Reactor
长连接推送 epoll + 业务池
高性能存储 io_uring / SPDK 等专用方案
简单内网调用 同步 RPC

先满足正确性和可观测性,再引入复杂模型。

本章小结

Socket 提供网络编程接口,阻塞 IO 简单但并发扩展性差,非阻塞 IO 需要事件机制,epoll 和 Reactor 支撑大量连接,io_uring 提供异步 IO 能力。生产代码要处理消息边界、读写事件、慢业务隔离、有界队列和背压。模型选择应基于连接数、请求耗时和团队维护能力。

思考题

  1. 阻塞 IO 的主要扩展瓶颈是什么?
  2. select、poll 和 epoll 的差异是什么?
  3. 非阻塞 read 返回 EAGAIN 后应用应该做什么?
  4. 为什么耗时业务不能放在 EventLoop?
  5. 设计一个自定义 TCP 协议的解码器状态机。