《UNIX网络编程卷1:套接字联网API》第6章 IO复用:select和poll函数

《UNIX网络编程卷1:套接字联网API》第6章 I/O复用:select和poll函数


6.1 I/O复用的核心价值与适用场景

I/O复用是高并发网络编程的基石,允许单个进程/线程同时监控多个文件描述符(套接字)的状态变化,从而高效处理多个客户端请求。其核心价值在于:

  • 资源高效利用:避免为每个连接创建独立线程/进程的内存与调度开销;
  • 事件驱动模型:仅在数据可读/可写时触发处理逻辑,减少空转;
  • 实时性保障:及时响应多个连接的并发事件。

典型应用场景

  • Web服务器(如Nginx):处理数千并发HTTP连接;
  • 实时通信系统(如IM):同时管理多个客户端的长连接;
  • 嵌入式网关:资源受限设备中处理多路传感器数据。

6.2 select函数深度解析
6.2.1 select函数原型与参数
#include <sys/select.h>int select(int nfds, fd_set *restrict readfds,fd_set *restrict writefds,fd_set *restrict exceptfds,struct timeval *restrict timeout);

参数详解

  1. nfds:需监控的最大文件描述符+1(优化内核遍历效率);
  2. readfds/writefds/exceptfds:分别监控可读、可写、异常事件的文件描述符集合;
  3. timeout:超时时间(NULL为阻塞,0为非阻塞,>0为限时等待)。

返回值

  • >0:就绪的文件描述符总数;
  • 0:超时无事件;
  • -1:错误(如被信号中断)。
6.2.2 文件描述符集合操作宏
void FD_ZERO(fd_set *set);       // 清空集合
void FD_SET(int fd, fd_set *set);// 添加描述符到集合
void FD_CLR(int fd, fd_set *set);// 从集合移除描述符
int FD_ISSET(int fd, fd_set *set);// 检查描述符是否就绪
6.2.3 select工作流程
  1. 初始化集合:使用FD_ZEROFD_SET设置需监控的描述符;
  2. 调用select:阻塞或等待事件发生;
  3. 遍历检查:通过FD_ISSET轮询所有描述符,处理就绪事件;
  4. 重置集合:select会修改传入的集合,需在每次调用前重新初始化。

代码示例:基于select的TCP服务器框架

fd_set read_set, all_set;
int maxfd = listenfd; // 监听套接字
FD_ZERO(&all_set);
FD_SET(listenfd, &all_set);for (;;) {read_set = all_set; // 每次select调用前重置集合int nready = select(maxfd + 1, &read_set, NULL, NULL, NULL);if (FD_ISSET(listenfd, &read_set)) { // 新连接到达int connfd = Accept(listenfd, NULL, NULL);FD_SET(connfd, &all_set);maxfd = (connfd > maxfd) ? connfd : maxfd;}for (int fd = listenfd + 1; fd <= maxfd; fd++) { // 检查客户端连接if (FD_ISSET(fd, &read_set)) {// 处理客户端请求(如read/write)}}
}
6.2.4 select的局限性
  1. 文件描述符上限FD_SETSIZE(通常1024)限制最大监控数量;
  2. 线性扫描效率低:每次需遍历所有描述符,时间复杂度O(n);
  3. 内核态内存复制:每次调用需将集合从用户态复制到内核态;
  4. 无法动态扩展:无法在运行中添加/移除描述符,需重新初始化集合。

6.3 poll函数:改进的I/O复用机制
6.3.1 poll函数原型与参数
#include <poll.h>int poll(struct pollfd *fds, nfds_t nfds, int timeout);

参数解析

  1. fds:指向pollfd结构数组,每个元素描述一个监控的描述符;
  2. nfds:数组长度(即监控的描述符数量);
  3. timeout:超时时间(毫秒,-1为阻塞,0为非阻塞)。

pollfd结构体

struct pollfd {int   fd;       // 文件描述符short events;   // 监控的事件(POLLIN、POLLOUT等)short revents;  // 返回的事件(由内核填充)
};
6.3.2 poll事件标志
  • POLLIN:数据可读(包括TCP连接断开时的EOF);
  • POLLPRI:紧急数据可读(如TCP带外数据);
  • POLLOUT:数据可写;
  • POLLERR:错误发生(自动监控,无需设置);
  • POLLHUP:连接挂起(如对端关闭)。
6.3.3 poll工作流程
  1. 初始化pollfd数组:设置每个元素的fdevents
  2. 调用poll:等待事件发生;
  3. 遍历检查revents:处理就绪的描述符;
  4. 动态维护数组:可动态添加或移除描述符。

代码示例:基于poll的TCP服务器框架

#define MAX_CLIENTS 1024
struct pollfd client_fds[MAX_CLIENTS];
int nfds = 1; // 初始只有监听套接字client_fds[0].fd = listenfd;
client_fds[0].events = POLLIN;for (;;) {int nready = poll(client_fds, nfds, -1);if (client_fds[0].revents & POLLIN) { // 新连接到达int connfd = Accept(listenfd, NULL, NULL);client_fds[nfds].fd = connfd;client_fds[nfds].events = POLLIN;nfds++;}for (int i = 1; i < nfds; i++) { // 遍历客户端连接if (client_fds[i].revents & POLLIN) {// 处理客户端请求if (read返回0) { // 客户端关闭连接Close(client_fds[i].fd);client_fds[i] = client_fds[nfds-1]; // 数组末尾元素覆盖当前nfds--;i--; // 重新检查当前位置}}}
}
6.3.4 poll的优势与不足

优势

  • 无描述符数量限制:仅受系统资源约束;
  • 动态管理:可随时添加/移除监控的描述符;
  • 更精细的事件控制:支持更多事件类型(如带外数据)。

不足

  • 仍为线性扫描:时间复杂度O(n);
  • 水平触发模式:未处理事件会持续通知(可能引起忙等)。

6.4 select与poll对比分析
特性selectpoll
描述符上限FD_SETSIZE(通常1024)仅受系统限制
事件类型仅读、写、异常支持更多事件(如POLLPRI)
效率低(O(n)遍历)低(O(n)遍历)
内存拷贝每次调用需复制整个集合仅传递数组指针
可扩展性静态集合,需重新初始化动态数组,可随时修改
跨平台兼容性所有UNIX系统多数UNIX系统(Linux、BSD)

选型建议

  • 嵌入式系统:优先poll(动态管理更灵活);
  • 高并发场景:推荐epoll(第7章详解);
  • 跨平台需求:select兼容性更好。

6.5 性能优化与陷阱规避
6.5.1 select性能优化
  1. 减少nfds值:仅传递当前最大描述符+1;
  2. 分离监控集合:将频繁活动的描述符单独监控;
  3. 避免阻塞操作:在事件处理函数中使用非阻塞I/O。
6.5.2 poll常见陷阱
  1. 未重置revents:每次调用poll前需清空revents或重新初始化结构体;
  2. 数组越界:动态添加描述符时需检查数组上限;
  3. 忽略POLLHUP:未处理可能导致死循环。

代码示例:非阻塞read处理

// 设置套接字为非阻塞
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);char buf[1024];
ssize_t n = read(fd, buf, sizeof(buf));
if (n > 0) {// 处理数据
} else if (n == 0) {// 对端关闭连接
} else if (errno == EAGAIN || errno == EWOULDBLOCK) {// 数据未就绪,继续监控
} else {// 其他错误处理
}

6.6 实战:基于select的聊天服务器
6.6.1 功能需求
  • 支持多客户端连接;
  • 客户端消息广播至所有其他客户端;
  • 客户端退出时自动清理资源。
6.6.2 核心代码实现
#include "unp.h"#define MAX_CLIENTS 1024
int client_fds[MAX_CLIENTS];void broadcast(int sender_fd, char *msg, int len) {for (int i = 0; i < MAX_CLIENTS; i++) {if (client_fds[i] != -1 && client_fds[i] != sender_fd) {Writen(client_fds[i], msg, len);}}
}int main() {int listenfd = Socket(AF_INET, SOCK_STREAM, 0);// 绑定与监听(代码同前)fd_set all_set, read_set;FD_ZERO(&all_set);FD_SET(listenfd, &all_set);int maxfd = listenfd;memset(client_fds, -1, sizeof(client_fds));for (;;) {read_set = all_set;int nready = select(maxfd + 1, &read_set, NULL, NULL, NULL);if (FD_ISSET(listenfd, &read_set)) {int connfd = Accept(listenfd, NULL, NULL);// 将新连接加入数组for (int i = 0; i < MAX_CLIENTS; i++) {if (client_fds[i] == -1) {client_fds[i] = connfd;FD_SET(connfd, &all_set);maxfd = (connfd > maxfd) ? connfd : maxfd;break;}}}for (int i = 0; i < MAX_CLIENTS; i++) {int fd = client_fds[i];if (fd == -1) continue;if (FD_ISSET(fd, &read_set)) {char buf[1024];ssize_t n = Read(fd, buf, sizeof(buf));if (n > 0) {broadcast(fd, buf, n);} else {Close(fd);FD_CLR(fd, &all_set);client_fds[i] = -1;}}}}
}

6.7 调试工具与性能分析
6.7.1 使用strace跟踪系统调用
strace -e select,poll,read,write ./server

输出示例

select(5, [3 4], NULL, NULL, NULL) = 1 (in [3])
read(3, "hello", 1024)             = 5
6.7.2 监控文件描述符状态
# 查看进程打开的文件描述符
ls -l /proc/<pid>/fd
6.7.3 性能压测工具
# 使用netcat模拟多客户端
for i in {1..1000}; donc 127.0.0.1 9999 &
done

6.8 本章小结与进阶习题

小结:本章深入解析了select和poll的原理、使用场景与优化技巧,通过实战案例展示了高并发服务器的实现方法。

习题

  1. 实现基于poll的聊天服务器,支持昵称注册与私聊功能;
  2. 对比select与poll在1000并发连接下的CPU使用率差异;
  3. 扩展select服务器支持可写事件监控,实现大文件传输。

说明

  1. Linux下更详细select函数内容,可参见博主相关博文Linux下select使用
  2. 嵌入式下如何高效使用select与epoll,可参见博主相关专题博文在嵌入式Linux中实现高并发TCP服务器:从select到epoll的演进与实战
  3. Linux下更详细epoll函数内容,可参见博主相关博文Linux下epoll函数使用详解

付费用户专属资源

  • 完整聊天服务器代码工程(含Makefile);
  • select/poll性能对比测试报告;
  • 扩展阅读:《从select到epoll:Linux I/O模型的演进》。

通过本章学习,读者将掌握I/O复用的核心技术,并能够开发高并发的网络应用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/74630.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot+vue前后端分离整合sa-token(无cookie登录态 详细的登录流程)

SpringBootvue前后端分离整合sa-token&#xff08;无cookie登录态 & 详细的登录流程&#xff09; 1.介绍sa-token1.1 框架定位1.2 核心优势 2.如何整合sa-token3.如何进行无cookie模式登录3.1后端3.1.1 VO层3.1.2 Controller层3.1.3 Service层 3.2前端3.2.1 登录按钮自定义…

MYOJ_1171:(洛谷P1075)[NOIP 2012 普及组] 质因数分解(数学相关,质数与约数基础)

题目描述 已知正整数 n 是两个不同的质数的乘积&#xff0c;试求出两者中较大的那个质数。 1≤n≤210^9 输入 输入一个正整数 n。 输出 输出一个正整数 p&#xff0c;即较大的那个质数。 样例输入输出 输入&#xff1a;21 输出&#xff1a;7 思路: 为了节约时间与…

Python语言的测试用例设计

Python语言的测试用例设计 引言 随着软件开发的不断进步&#xff0c;测试在软件开发生命周期中的重要性日益凸显。测试用例设计是软件测试的核心&#xff0c;它为软件系统的验证和验证提供了实施的基础。在Python语言中&#xff0c;由于其简洁明了的语法和强大的内置库&#…

SpringKafka消息消费:@KafkaListener与消费组配置

文章目录 引言一、Spring Kafka消费者基础配置二、KafkaListener注解使用三、消费组配置与负载均衡四、手动提交偏移量五、错误处理与重试机制总结 引言 Apache Kafka作为高吞吐量的分布式消息系统&#xff0c;在大数据处理和微服务架构中扮演着关键角色。Spring Kafka为Java开…

VMware 虚报化Ubuntu 卡成一B,如何接招?

故事背景 Win10 专业版 安装VMware pro ,虚拟化出一个Window10&#xff0c;另一个是UBuntu.自从使用起来去不去就卡死。开始是以为驱动或者升级造成的&#xff0c;重新安装一段时间问题照旧。更气人的这种现象具有不定期性&#xff0c;说不定什么时候就来这么一出。 直接解决方…

cloud项目批量修改主机号

当clone了一个cloud项目后&#xff0c;要把别人的主机号全部改成自己的&#xff0c;非常麻烦 在项目根目录下&#xff0c;启动 Git Bash。在 Git Bash 终端中使用原始的 Unix 命令&#xff1a; find . -type f -exec sed -i s/127\.0\.0\.1/132.168.190.163/g {} 其中127.0.…

微信小程序使用 Vant Weapp 组件库教程

在微信小程序项目中使用 Vant 组件库&#xff08;Vant Weapp&#xff09;主要包括以下几个步骤&#xff1a; 1. 初始化项目并安装 Vant Weapp 初始化 npm 在项目根目录下运行以下命令&#xff0c;生成 package.json&#xff1a; npm init -y安装 Vant Weapp 执行以下命令安装 V…

FPGA状态机思想实现流水灯及HDLBits学习

目录 第一章 在DE2-115上用状态机思想实现LED流水灯1.1 状态机设计思路1.2 Verilog代码实现1.3. 仿真测试代码1.4 编译代码与仿真 第二章 CPLD和FPGA芯片的主要技术区别是什么&#xff1f;它们各适用于什么场合&#xff1f;2.1 主要技术区别2.2 适用场合 第三章 HDLBits学习3.1…

与总社团联合会合作啦

2025.4.2日&#xff0c;我社团向总社团联合会与暮光社团发起合作研究“浔川代码编辑器v2.0”。至3日&#xff0c;我社团收到回复&#xff1a; 总社团联合会&#xff1a; 总社团联合会已收到浔川社团官方联合会的申请&#xff0c;经考虑&#xff0c;我们同意与浔川社团官方联合…

Shiro学习(三):shiro整合springboot

一、Shiro整合到Springboot步骤 1、准备SpringBoot 环境&#xff0c;这一步省略 2、引入Shiro 依赖 因为是Web 项目&#xff0c;所以需要引入web 相关依赖 shiro-spring-boot-web-starter&#xff0c;如下所示&#xff1a; 3、准备Realm 因为实例化 ShiroFilterFactoryBean 时…

【图形API】片段着色器自动计算LOD

片段着色器中的自动 LOD 计算详解 在图形渲染中&#xff0c;Level of Detail (LOD) 用于优化纹理采样的性能和视觉质量。片段着色器&#xff08;Fragment Shader&#xff09;能够自动计算 LOD&#xff0c;而顶点着色器&#xff08;Vertex Shader&#xff09;则不行。以下是详细…

24、 Python Socket编程:从协议解析到多线程实战

Python Socket编程&#xff1a;从协议解析到多线程实战 一、文章概述 本文深入讲解Python网络编程核心技术&#xff0c;涵盖TCP/UDP协议底层原理、Socket API全流程解析、高并发服务端开发实践&#xff0c;以及网络通信中的典型问题解决方案。通过3个递进式代码案例和协议设计…

LabVIEW 中数字转字符串常用汇总

在 LabVIEW 编程环境里&#xff0c;数字与字符串之间的转换是一项极为基础且重要的操作&#xff0c;广泛应用于数据处理、显示、存储以及设备通信等多个方面。熟练掌握数字转字符串的方法和技巧&#xff0c;对编写高效、稳定的程序起着关键作用。接下来&#xff0c;我们将全面深…

轨迹速度聚类 实战

根据轨迹把速度聚类为3个类别,速度快的那部分不用平滑,速度慢的部分需要平滑。 速度聚类3个类别: kmeans++ import numpy as np import cv2 from sklearn.cluster import KMeans from matplotlib.colors import hsv_to_rgb from scipy.ndimage import gaussian_filter1d# …

vulkanscenegraph显示倾斜模型(5.6)-vsg::RenderGraph的创建

前言 上一章深入分析了vsg::CommandGraph的创建过程及其通过子场景遍历实现Vulkan命令录制的机制。本章将在该基础上&#xff0c;进一步探讨Vulkan命令录制中的核心封装——vsg::RenderGraph。作为渲染流程的关键组件&#xff0c;RenderGraph封装了vkCmdBeginRenderPass和vkCmd…

第二十八章:Python可视化图表扩展-和弦图、旭日图、六边形箱图、桑基图和主题流图

一、引言 在数据可视化领域&#xff0c;除了常见的折线图、柱状图和散点图&#xff0c;还有一些高级图表类型可以帮助我们更直观地展示复杂数据关系。本文将介绍五种扩展图表&#xff1a;和弦图、旭日图、六边形箱图、桑基图和主题流图。这些图表在展示数据关系、层次结构和流量…

大模型-爬虫prompt

爬虫怎么写prompt 以下基于deepseek r1 总结&#xff1a; 以下是为大模型设计的结构化Prompt模板&#xff0c;用于生成专业级网络爬虫Python脚本。此Prompt包含技术约束、反检测策略和数据处理要求&#xff0c;可根据具体需求调整参数&#xff1a; 爬虫脚本生成Prompt模板1 …

Vue中将pdf文件转为图片

平时开发中,我们经常遇到的场景应该是调用后端接口返回给前端pdf格式的文件流,然后我们可以通过URL.createObjectURL的方式转为object url临时路径然后可以通过window.open的方式来打开一个新的浏览器页签来进行预览,效果如下图: 但有时候这样满足不了的需求,它不想这样预…

物联网安全技术:守护智能世界的防线

最近研学过程中发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击链接跳转到网站人工智能及编程语言学习教程。读者们可以通过里面的文章详细了解一下人工智能及其编程等教程和学习方法。下面开始对正文内容的…

kubernetes安装部署k8s

kubernetes https://github.com/kubernetes/kubernetes.git go mod tidy go mod vendor go build -o .\bin -v ./… //手动创建bin文件夹 使用 minikube&#xff1a;https://gitee.com/mirrors/minikube.git 使用minikube启动本地化的集群服务 minikube start 启动集群&…