select 函数详解
author: hjjdebugdate: 2026年 01月 25日 星期日 15:08:37 CSTdescrip: select 函数详解.文章目录0: I/O 多路复用是什么意思 ??1. select 函数可以同时支持多少路I/O ?1.1. server_fd 是一个整数1.2 read_fds 是什么?1.3 read_fds 赋值.2. 使用select 的注意事项.2.1 timeout 值必需每次都要初始化.2.2 fd_set 值必需每次都要初始化.0: I/O 多路复用是什么意思 ??如果一个程序需要用到多个I/O, 例如从键盘读数, 从3个socket 中读数,这就是4路I/O 读操作.因为对I/O 的读写, read, write 是阻塞的, 当条件未满足时会被挂起, 为了保证各I/O 互相独立,通常用多线程来应对各I/O 的读写. 对每一个I/O 都启动一个线程来进行读写.那么能不能用一个线程来监测多个I/O 呢, 有数据了或可以写了再对不同的fd 操作,这就是select 函数出场的时机. 就是一个线程看管多个I/O于是我们明白I/O多路复用, 就是多个I/O, 复用同一个线程的意思. 复用就是重复使用.所以这种简化, 让人容易迷糊, 你会问 “谁复用?”, “I/O 复用是什么意思?” “什么叫I/O复用”你还不如直接说, “I/O多路复用” 就是 “多路I/O只用一个线程监测”.前者说得高大上不明不白, 就是词不达意, 后者简单直接一目了然.1. select 函数可以同时支持多少路I/O ?谁说了也不好使, 我从网上抄了一段代码, 我们调试一下看看吧.测试代码:$ cat main.c#includestdio.h#includestdlib.h#includestring.h#includeunistd.h#includesys/select.h#includesys/socket.h#includenetinet/in.h#includearpa/inet.h#includefcntl.h#includeerrno.h#defineMAX_CLIENTS100// 定义最大客户端数量远大于16#definePORT8080#defineBUFFER_SIZE1024intmain(){intserver_fd,new_socket;structsockaddr_inaddress;intopt1;intaddrlensizeof(address);intmax_fd;intclient_sockets[MAX_CLIENTS]{0};// 存储所有客户端套接字fd_set read_fds;// 用于存储当前需要监控的文件描述符集合// 创建服务器套接字if((server_fdsocket(AF_INET,SOCK_STREAM,0))0){perror(socket failed);exit(EXIT_FAILURE);}// 设置套接字选项允许重用地址if(setsockopt(server_fd,SOL_SOCKET,SO_REUSEADDR,opt,sizeof(opt))){perror(setsockopt);exit(EXIT_FAILURE);}address.sin_familyAF_INET;address.sin_addr.s_addrINADDR_ANY;address.sin_porthtons(PORT);// 绑定套接字if(bind(server_fd,(structsockaddr*)address,sizeof(address))0){perror(bind failed);exit(EXIT_FAILURE);}// 开始监听if(listen(server_fd,3)0){perror(listen);exit(EXIT_FAILURE);}printf(Server listening on port %d\n,PORT);// 初始化客户端套接字数组for(inti0;iMAX_CLIENTS;i){client_sockets[i]0;}// 主循环while(1){// 清空读描述符集合FD_ZERO(read_fds);// 将服务器套接字添加到集合中FD_SET(server_fd,read_fds);max_fdserver_fd;// 将所有活跃的客户端套接字添加到集合中for(inti0;iMAX_CLIENTS;i){if(client_sockets[i]0){FD_SET(client_sockets[i],read_fds);if(client_sockets[i]max_fd){max_fdclient_sockets[i];}}}// 调用 select阻塞等待事件intactivityselect(max_fd1,read_fds,NULL,NULL,NULL);if(activity0){perror(select error);break;}// 检查是否有新连接到来if(FD_ISSET(server_fd,read_fds)){if((new_socketaccept(server_fd,(structsockaddr*)address,(socklen_t*)addrlen))0){perror(accept);continue;}printf(New connection, socket fd is %d\n,new_socket);// 找到一个空闲的客户端槽位inti;for(i0;iMAX_CLIENTS;i){if(client_sockets[i]0){client_sockets[i]new_socket;printf(Adding to list of sockets as %d\n,i);break;}}if(iMAX_CLIENTS){printf(Too many clients, rejecting new connection\n);close(new_socket);}}// 检查所有客户端套接字是否有数据可读for(inti0;iMAX_CLIENTS;i){intsockclient_sockets[i];if(sock0FD_ISSET(sock,read_fds)){charbuffer[BUFFER_SIZE]{0};intvalreadread(sock,buffer,BUFFER_SIZE-1);if(valread0){// 客户端断开连接printf(Client disconnected, socket fd %d\n,sock);close(sock);client_sockets[i]0;}else{// 收到数据回显给客户端printf(Received: %s from client %d\n,buffer,i);write(sock,buffer,strlen(buffer));}}}}return0;}调试:1.1. server_fd 是一个整数(gdb) p server_fd$1 3只所以server_fd 为3, 是因为0,1,2已经被系统占用了, 懂的都懂.1.2 read_fds 是什么?(gdb) p read_fds$2 {__fds_bits {0 repeats 16 times}}(gdb) ptype read_fdstype struct {__fd_mask __fds_bits[16];}(gdb) ptype __fd_masktype long原来read_fds 是一个16个长整形数.当时看到16, 就朦胧的联想到是不是只能检测16个I/O呢? 实际上不是这意思.16位长整形,在64位机器上是1024个bit位, 它能够监测1024个I/O下面解释为什么bit位就对应着fd.1.3 read_fds 赋值.FD_SET(server_fd, read_fds);(gdb) p read_fds$3 {__fds_bits {8, 0 repeats 15 times}}server_fd 为3, read_fds 在bit3位置置1,形成8.FD_SET(client_sockets[i], read_fds);(gdb) p client_sockets[i]$6 4(gdb) p read_fds$7 {__fds_bits {24, 0 repeats 15 times}}240x18, 8是bit3的1, 0x10 是bit4的1, 对应client_socket[0]的描述符4至此我们就明白了, 你监测的fd, 要转换位bitmap 上的对应的位置1.至此, select 的工作原理就算说明白了.所以说,64位机器上最多监测1024个描述符.2. 使用select 的注意事项.如果select 是在一个循环中调用, 注意.2.1 timeout 值必需每次都要初始化.否则你就搞不对.测试程序没有使用timeout, 所以也就没有值的初始化问题了.如果你使用了timeout, 则执行完select 后, 其值 timeout.tv_sec, timeout.tv_usec就都变成0 了, 不初始化, 意味着第二次调用时其值为0,调用select时会立即返回超时.这是我测试2秒超时, 程序一运行, 程序哗哗的打印超时发现的.这里timeout 即是一个输入参数,也是一个输出参数,传递的是timeout地址.说实话,这种设计不太好,容易让人犯错误,不过知道了也就无所谓了.2.2 fd_set 值必需每次都要初始化.否则你就读不到数据.这里也是一个坑, 因为select 函数也是传递的fd_set的地址,它是输入参数,也是输出参数, 当select 返回时,保留的是准备就绪的描述符集.而不再是你感兴趣的描述符集合了, 如果在循环中不初始化,你将得不到预期结果.这就是参数即当输入,也当输出的风险, 我发现它.是我用select 监测键盘输入, 我设置超时1秒,2秒,就不能从键盘获取输入,而设置超时5,6,7,8秒就总能从键盘接受到数据, 为什么?因为设置2秒钟超时, 我启动程序后,2秒内来不及从键盘输入字符. 例如输入hello还要敲回车,结果超时,结果read_fds 此时是空的,结果再进入循环调用select, 由于read_fds 没有初始化,将不监测键盘, 所以收不到键盘输入.而5,6,7,8秒, 我有足够时间反应,启动程序后又输入测试字符加回车键,select 执行收到字符将返回准备好的read_fds, 这跟初始化的read_fds 数值是相等的, 都是bit1 置1,所以下一轮执行select, read_fds 未进行初始化还是能接受到数据,如果设置都正确, 别说2秒, 0.2秒,0.02秒超时都是可以的.这次经历让我认识到了初始化的重要性.这是函数参数设计的缺陷,即当输入也当输出,但好处是节省了参数个数.这也无所谓好赖,必需要符合原设计者的约定. 这就是审题的必要性了. 所以对于一个新的函数,新的接口要了解其用途,这是学习成本.所以若非必要,我们也不愿意学习新接口.用select函数很久了,也只是用用而已,也没碰到什么问题,没有认真想过,这次再用,自己手写时,碰到了一点问题,认真的思考了一下,感觉算是搞懂了, 写篇博客吧, 也没多少内容, 就是select 函数本身,起名就叫select 函数详解吧.连个函数原型都没有写也叫详解吗? 那些基本的东西大家搜一下就行了,我这就忽略了.这里侧重的是实现原理和注意事项.

相关新闻

风险评估准备(上)

风险评估准备(上)

2026/7/3 21:20:25 阅读更多 →
JavaWeb企业级开发---用户登录认证

JavaWeb企业级开发---用户登录认证

2026/7/3 21:20:26 阅读更多 →
最值得推荐的5家跨境营销服务商

最值得推荐的5家跨境营销服务商

2026/7/3 21:20:25 阅读更多 →

最新新闻

2026普通人AI使用指南:看懂参数、混合思考与国产模型三大核心

2026普通人AI使用指南:看懂参数、混合思考与国产模型三大核心

1. 这不是科幻预告片,是普通人下周就该打开手机查的“技术天气预报”2026年4月这个时间点,听起来像科幻小说里随手写的年份,但如果你最近刷过几条国产大模型发布会的短视频,或者留意过身边朋友突然开始用“文心一言新版本”写周报…

2026/7/4 23:17:06 阅读更多 →
Let‘s Encrypt泛域名证书申请与自动化续期实战指南

Let‘s Encrypt泛域名证书申请与自动化续期实战指南

1. 项目概述与核心价值最近在折腾自己的个人博客和几个内部服务,域名下挂了好几个子域名,每次给每个子域名单独申请SSL证书,不仅麻烦,续期更是让人头大。直到我开始用Let‘s Encrypt的泛域名证书,配合自动化续期脚本&a…

2026/7/4 23:17:06 阅读更多 →
多维聚合实战:超越GROUP BY的OLAP数据操作指南

多维聚合实战:超越GROUP BY的OLAP数据操作指南

1. 项目概述:多维聚合中的数据操作,远不止GROUP BY那么简单“Part 20: Data Manipulation in Multi-Dimensional Aggregation”这个标题乍看像教科书某章编号,但实际踩中了数据分析和商业智能工程中最常被低估、最易出错、也最具业务价值的一…

2026/7/4 23:17:06 阅读更多 →
AMD ROCm 7.1.1正式支持Windows:本地AI电影制作全栈落地

AMD ROCm 7.1.1正式支持Windows:本地AI电影制作全栈落地

1. 项目概述:当本地AI电影制作从“概念图”变成“开机键”2025年11月26日,我盯着终端里一行绿色的True输出,手有点抖。不是因为咖啡喝多了,而是因为torch.cuda.is_available()终于没再报错——它真真切切地返回了True,…

2026/7/4 23:15:05 阅读更多 →
基于OpenCV与深度学习的车牌识别系统开发实践

基于OpenCV与深度学习的车牌识别系统开发实践

1. 项目概述这个车牌识别系统是我在指导学弟学妹毕业设计时开发的一个典型案例。作为一个结合了传统图像处理和深度学习技术的实用项目,它完美展现了如何将学术知识与工程实践相结合。系统采用PythonOpenCV作为基础框架,融入机器学习算法,实现…

2026/7/4 23:13:04 阅读更多 →
突破60帧限制:WaveTools鸣潮工具箱的智能游戏优化革命

突破60帧限制:WaveTools鸣潮工具箱的智能游戏优化革命

突破60帧限制:WaveTools鸣潮工具箱的智能游戏优化革命 【免费下载链接】WaveTools 🧰鸣潮工具箱 项目地址: https://gitcode.com/gh_mirrors/wa/WaveTools 当你为《鸣潮》的帧率限制感到困扰时,当你发现高性能硬件在游戏中无法完全发挥…

2026/7/4 23:13:04 阅读更多 →

日新闻

Memcached 1.6.43 发布:关键安全修复版本,多项问题得到解决

Memcached 1.6.43 发布:关键安全修复版本,多项问题得到解决

Memcached 1.6.43 正式发布,这是一个关键的安全修复版本,修复了多个方面的问题,还对部分功能进行了优化。 安全修复亮点 此次发布在安全修复上表现突出。binprot 避免了项目引用计数溢出,mcmc 因安全问题提升了上游版本号&#xf…

2026/7/4 0:04:29 阅读更多 →
终极指南:使用HMCL启动器跨平台畅玩Minecraft的完整解决方案

终极指南:使用HMCL启动器跨平台畅玩Minecraft的完整解决方案

终极指南:使用HMCL启动器跨平台畅玩Minecraft的完整解决方案 【免费下载链接】HMCL A Minecraft Launcher which is multi-functional, cross-platform and popular 项目地址: https://gitcode.com/gh_mirrors/hm/HMCL HMCL(Hello Minecraft! Lau…

2026/7/4 0:06:29 阅读更多 →
KMX63与PIC18F66K40在嵌入式HMI中的硬件协同与低功耗设计

KMX63与PIC18F66K40在嵌入式HMI中的硬件协同与低功耗设计

1. KMX63与PIC18F66K40的硬件协同架构解析KMX63作为一款三轴加速度计和磁力计组合传感器,与PIC18F66K40微控制器的搭配堪称嵌入式HMI开发的黄金组合。这套硬件组合的核心优势在于KMX63提供的高精度运动感知能力与PIC18F66K40强大的信号处理能力形成了完美互补。KMX6…

2026/7/4 0:06:29 阅读更多 →

周新闻

月新闻