前言
在网络编程领域,IO 模型是支撑高效通信的核心基础之一。当需要让单个进程或线程同时处理多个网络连接的 IO 事件时,'IO 多路复用(多路转接)' 技术成为了关键解法 —— 它能让程序通过少量进程 / 线程,高效监控并处理多个 IO 事件,极大提升系统对 IO 资源的利用效率。
select作为 IO 多路复用模型中经典且具有代表性的实现,是开发者接触'多路转接'的重要入门点。尽管随着技术演进,它逐渐显现出一些局限性,但深入理解 select的工作机制、使用逻辑及其优缺点,不仅能帮助我们掌握'单进程管理多连接'的核心思路,更是学习更先进多路复用技术(如 poll、epoll)的重要前提。
本文将围绕'select 实现多路转接'展开,从 select接口的基本定义入手,逐步讲解基于 select的多路转接服务器实现(包含套接字封装、初始化流程、fd_set对象操作及服务器主循环设计等),最后剖析 select自身的优势与不足。希望通过对这些内容的梳理,能让读者清晰把握 select在多路 IO 转接中的核心作用,为后续 IO 模型学习与网络编程实践筑牢基础。
在介绍 select这三种多路转接的 IO 模型之前,有必要先介绍以下 5 种 IO 模型分别是哪几种。
一。五种 IO 模型
我们在操作系统中直接调用,read && write将数据读取上来,其本质就是将数据从用户层拷贝到操作系统中/从操作系统中拷贝到用户层——就是'拷贝';
- 虽然我们通过拷贝来发送/获取数据,但是我们必须要明确一个概念:IO = 等数据 + 拷贝,而不仅仅是对数据进行拷贝;
- 对于写于要等发送缓冲区中有位置,对于读取要等接收缓冲区中有数据。
因此在进行拷贝之前,必须先判断条件是否成立,也就是读写事件是否就绪。
我们通常定义高效 IO 指的是:单位时间内,IO 过程中,等的比重越小,效率越高。
下面介绍五种 IO 模型:
- 阻塞性:直到'等待数据就绪'和'数据拷贝'两个阶段完全完成,IO 调用才返回;
- 非阻塞性:等待数据就绪阶段不阻塞(内核会立即返回结果),即若数据未就绪,内核会返回
EAGAIN或EWOULDBLOCK错误; - 信号驱动型:用一个线程监控多个 IO,避免进程在单个未就绪 IO 上阻塞;
- 多路复用/多路转接型:让内核在 IO 数据就绪时主动发送
SIGIO信号通知进程来拿取数据; - 异步 IO 型:应用进程发起异步 IO 调用后,两个阶段(等待就绪、数据拷贝)均由内核完成,全程不阻塞进程。内核在完成所有操作后,通过'信号'或'回调函数'通知进程,进程直接使用已拷贝到用户缓冲区的数据。
- 对于阻塞 IO 和非阻塞 IO 在效率上并没有什么区别,只不过非阻塞 IO 在不等待期间可以做其他事情,因此我们通常说它的效率更高一些。
下面介绍实现多路转接 IO 的 3 种方式。
二。select 实现多路转接
关于 select 实现多路转接,此处将分为两部分进行介绍:
- 介绍 select 的接口;
- 使用 select 实现一个简单的 ech 服务器。
2.1 select 接口
select可以一次等待多个文件,当有一个文件就绪了就返回,这样可以一次性等待多个文件,提高了等待的效率。
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);


