阻塞 I/O 模型和IO多路复用

阻塞 I/O 模型-基于 TCP 的 Socket 编程原理

一、Socket 编程概述

Socket 是进程间通信的一种特殊方式,能够实现跨主机间的通信。在网络通信前,客户端和服务器都需要各自创建一个 Socket,它就如同一个“口子”,数据的读取和发送都通过这个“口子”进行,类似于一根网线连接客户端和服务端。创建 Socket 时,可以指定网络层的 IPv4 或 IPv6 协议,以及传输层的 TCP 或 UDP 协议,本文重点介绍基于 TCP 的 Socket 编程。

二、服务端 Socket 编程流程

  1. 创建 Socket
    服务端调用 socket() 函数,创建一个网络协议为 IPv4、传输协议为 TCP 的 Socket。这是进行网络通信的基础,就像打开了一个用于通信的“窗口”。

  2. 绑定 IP 地址和端口
    接着调用 bind() 函数,为这个 Socket 绑定一个 IP 地址和端口。

    • 绑定端口的目的:当内核收到 TCP 报文时,会通过 TCP 头中的端口号找到对应的应用程序,从而将数据传递给该程序。端口号就像是一个门牌号,帮助内核找到正确的“房间”(应用程序)。
    • 绑定 IP 地址的目的:一台机器可能有多个网卡,每个网卡都有对应的 IP 地址。绑定特定的 IP 地址后,只有当内核收到来自该网卡上的包时,才会将包发送给对应的应用程序。这就好比在一栋有多个入口(网卡)的建筑中,明确指定从哪个入口接收包裹(数据)。
  3. 监听端口
    绑定完 IP 地址和端口后,调用 listen() 函数进行监听,此时对应 TCP 状态图中的 listen 状态。可以通过 netstat 命令查看对应的端口号是否处于被监听状态,以此来判断服务器中的网络程序是否已经启动。

  4. 接受客户端连接
    服务端进入监听状态后,通过调用 accept() 函数从内核获取客户端的连接。如果没有客户端连接请求,该函数会阻塞等待,直到有客户端连接到来。

三、客户端 Socket 编程流程

  1. 创建 Socket
    客户端同样先创建一个 Socket,这是进行通信的前提条件。

  2. 发起连接
    客户端调用 connect() 函数发起连接,该函数的参数需要指明服务端的 IP 地址和端口号。此时,万众期待的 TCP 三次握手开始。

四、TCP 连接过程中的队列机制

在 TCP 连接过程中,服务器的内核为每个 Socket 维护了两个队列:

  1. TCP 半连接队列:这个队列中存储的是还没有完成三次握手的连接,此时服务端处于 syn-rcvd 状态。可以理解为连接请求已经发出,但还在等待对方的最终确认。
  2. TCP 全连接队列:该队列中存储的是已经完成三次握手的连接,此时服务端处于 established 状态。当 TCP 全连接队列不为空时,服务端的 accept() 函数会从内核的 TCP 全连接队列中取出一个已经完成连接的 Socket 返回给应用程序,后续的数据传输都将使用这个 Socket。

需要注意的是,监听的 Socket 和真正用来传输数据的 Socket 是不同的,一个称为监听 Socket,用于等待和接受客户端的连接请求;另一个称为已连接 Socket,用于实际的数据传输。

五、数据传输

连接建立后,客户端和服务端就可以开始相互传输数据了。双方都可以通过 read()write() 函数来进行数据的读取和写入操作,实现数据的交互。

I/O 多路复用(I/O Multiplexing)详解

I/O 多路复用是一种高效的 I/O 事件管理机制,允许单个线程同时监控多个文件描述符(如 Socket、管道等),并在它们可读、可写或发生异常时进行通知。它是 Redis、Nginx、Node.js 等高并发系统的核心实现技术之一。


为什么需要 I/O 多路复用?

在传统 I/O 模型中:

  • 阻塞 I/O(Blocking I/O):线程会一直等待数据就绪,无法处理其他请求。
  • 非阻塞 I/O(Non-blocking I/O):线程需要轮询检查数据是否就绪,浪费 CPU。
  • 多线程/多进程:虽然可以并发处理,但线程切换和资源竞争带来额外开销。

I/O 多路复用的优势
单线程管理多个 I/O 流,避免多线程竞争。
减少系统调用(相比非阻塞轮询)。
高并发支持,适用于 C10K(万级连接)甚至更高。


核心思想

  • 注册监听:应用程序告诉内核它关心的文件描述符(FD)和事件(读、写、异常)。
  • 事件通知:内核在 FD 就绪时通知应用程序,避免轮询。

三种主要实现方式

方式特点适用场景
select1. 跨平台支持(Linux/Windows)
2. FD 数量有限(默认 1024)
3. 线性扫描 FD 集合
旧系统兼容
poll1. 无 FD 数量限制(链表存储)
2. 仍需要遍历所有 FD
select 稍高效
epoll1. Linux 特有,高性能
2. 事件驱动,仅返回就绪的 FD
3. 支持水平触发(LT)和边缘触发(ET)
高并发服务器(Redis/Nginx)
kqueueFreeBSD/macOS 的 epoll 替代方案,类似机制BSD 系系统

select 多路复用机制

1. 工作原理

select 会将已连接的 Socket 对应的文件描述符存于一个文件描述符集合中,调用 select 函数时,该集合会从用户态拷贝到内核态。内核通过遍历文件描述符集合来检查是否有网络事件产生,若有事件发生,会将对应的 Socket 标记为可读或可写。之后,整个文件描述符集合会被拷贝回用户态,用户态再遍历集合找到可读或可写的 Socket 并处理。

2. 局限性
  • 文件描述符数量限制:使用固定长度的 BitsMap 存储文件描述符集合,在 Linux 系统中,受 FD_SETSIZE 限制,默认最多只能监听 0 - 1023 的文件描述符。
  • 性能损耗大:需要进行两次遍历文件描述符集合(内核态和用户态各一次),并且发生两次文件描述符集合的拷贝(用户态到内核态,内核态到用户态)。随着并发数增加,性能损耗呈指数级增长。

poll 多路复用机制

1. 工作原理

poll 把进程关注的 Socket 以动态数组(链表形式)存储,突破了 select 中文件描述符个数的固定限制。不过它和 select 一样,都使用线性结构存储 Socket 集合。当需要检查可读或可写的 Socket 时,也是通过遍历文件描述符集合的方式,时间复杂度为 O ( n ) O(n) O(n)。同时,同样需要在用户态和内核态之间拷贝文件描述符集合。

2. 优缺点
  • 优点:突破了 select 文件描述符个数的固定限制。
  • 缺点:和 select 本质类似,随着并发数增加,性能损耗依然较大,因为仍需遍历和拷贝操作。

epoll 多路复用机制

1. 红黑树存储待检测文件描述符
  • epoll 在内核中使用红黑树来跟踪进程所有待检测的文件描述符。通过 epoll_ctl() 函数可以将需要监控的 Socket 加入内核的红黑树中。红黑树是一种高效的数据结构,其增删改操作的时间复杂度一般为 O ( l o g n ) O(logn) O(logn)
  • 相比 selectpollepoll 由于在内核维护了红黑树来保存所有待检测的 Socket,所以每次操作时只需传入一个待检测的 Socket,减少了内核和用户空间大量的数据拷贝和内存分配。
2. 事件驱动机制
  • epoll 使用事件驱动机制,内核维护了一个链表来记录就绪事件。当某个 Socket 有事件发生时,内核会通过回调函数将其加入到这个就绪事件列表中。
  • 当用户调用 epoll_wait() 函数时,只会返回有事件发生的文件描述符的个数,无需像 selectpoll 那样轮询扫描整个 Socket 集合,大大提高了检测效率。

对比总结

特性selectpollepoll
文件描述符存储固定长度的 BitsMap(有个数限制)动态数组(链表形式,突破个数限制)内核红黑树
查找方式遍历文件描述符集合(时间复杂度 O ( n ) O(n) O(n)遍历文件描述符集合(时间复杂度 O ( n ) O(n) O(n)基于事件驱动,无需遍历整个集合
内核用户态交互需在两者间拷贝整个描述符集合需在两者间拷贝整个描述符集合只需传入单个待检测描述符,减少拷贝
并发性能随并发数增加性能损耗指数级增长随并发数增加性能损耗较大高并发场景下性能表现优异
局限性文件描述符个数受限(默认 1024)仍受系统文件描述符总数限制实现相对复杂

综上所述,selectpoll 实现简单,但在高并发场景下性能较差;epoll 虽然实现相对复杂,但凭借红黑树和事件驱动机制,在高并发场景下能显著提升性能。


总结

  • I/O 多路复用的核心:单线程管理多个 I/O 流,依赖内核事件通知机制。
  • epoll 是最佳实践:高性能、低开销,适合 Linux 高并发服务器。
  • Redis/Nginx 的成功依赖于此:单线程 + 多路复用 = 高并发 + 低延迟。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值