深入剖析 I/O 复用之 select 机制

深入剖析 I/O 复用之 select 机制

在网络编程中,I/O 复用是一项关键技术,它允许程序同时监控多个文件描述符的状态变化,从而高效地处理多个 I/O 操作。select 作为 I/O 复用的经典实现方式,在众多网络应用中扮演着重要角色。本文将深入探讨 select 的原理、使用方法、相关数据结构以及实际应用示例。

一、I/O 复用概述

I/O 复用使得程序能够同时监听多个文件描述符,适用于多种场景:

  • 客户端程序需要同时处理多个套接字。
  • 客户端要兼顾用户输入和网络连接处理。
  • TCP 服务器需同时管理监听套接字和已连接套接字。
  • 服务器要同时处理 TCP 请求和 UDP 请求。
  • 服务器需要监听多个端口。

二、select 原理

select 系统调用通过维护三个文件描述符集合(读集合、写集合和异常集合)来监视不同类型的事件。它会阻塞当前进程,直到有一个或多个文件描述符就绪(有数据可读、可写或发生异常),或者达到指定的超时时间。

三、select 使用方法

函数原型

#include <sys/select.h>  
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);  
  • nfds:需要监视的最大文件描述符编号加 1。
  • readfds:读文件描述符集合,用于监视文件描述符是否有数据可读。
  • writefds:写文件描述符集合,用于监视文件描述符是否可写。
  • exceptfds:异常文件描述符集合,用于监视文件描述符是否发生异常。
  • timeout:超时时间,指定 select 函数的最大阻塞时间。如果为 NULL,则会一直阻塞直到有文件描述符就绪。

fd_set 数据结构

fd_set 用于存储文件描述符集合,本质上是一个位图(bitmask)。每个文件描述符对应位图中的一位,若该位置为 1,则表示该文件描述符在集合内;若为 0,则表示不在集合内。

在这里插入图片描述

操作 fd_set 的宏函数

  • FD_ZERO(fd_set *set):将 fd_set 集合初始化为空,即把集合中所有位都置为 0。
  • FD_SET(int fd, fd_set *set):将指定的文件描述符 fd 添加到 fd_set 集合中,把对应位设置为 1。
  • FD_CLR(int fd, fd_set *set):从 fd_set 集合中移除指定的文件描述符 fd,将对应位设置为 0。
  • FD_ISSET(int fd, fd_set *set):检查指定的文件描述符 fd 是否在 fd_set 集合中。若在集合中则返回非零值,否则返回 0。

timeout 结构体

struct timeval {  long tv_sec; // 秒数  long tv_usec; // 微秒数  
};  

用于指定 select 函数的超时时间。

四、使用 select 实现 TCP 服务器示例代码解析

代码功能

此代码创建了一个 TCP 服务器,借助 select 函数实现 I/O 复用,能够同时处理多个客户端的连接与数据收发。服务器监听本地地址 127.0.0.16000 端口,当有新的客户端连接时会接受连接,接收客户端发送的数据,并向客户端回复 "ok"

代码逐段解析

头文件与常量定义
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <sys/select.h>  
#include <sys/socket.h>  
#include <arpa/inet.h>  
#include <netinet/in.h>  #define MAXARR 10  

引入所需头文件,并定义常量 MAXARR 表示存储文件描述符数组的最大长度。

函数声明与实现
  • socket_init 函数
    int socket_init() {  int sockfd = socket(AF_INET, SOCK_STREAM, 0);  if (sockfd == -1) {  perror("socket err");  return -1;  }  struct sockaddr_in saddr;  memset(&saddr, 0, sizeof(saddr));  saddr.sin_family = AF_INET;  saddr.sin_port = htons(6000);  saddr.sin_addr.s_addr = inet_addr("127.0.0.1");  int res = bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));  if (res == -1) {  perror("bind err");  return -1;  }  if ((res = listen(sockfd, 5) < 0)) {  perror("listen err");  return -1;  }  return sockfd;  
    }  
    
    该函数用于初始化服务器套接字,包括创建套接字、绑定地址和端口、开始监听连接。若出现错误则返回 -1
  • arr_init 函数
    void arr_init(int arr[]) {  for (int i = 0; i < MAXARR; i++) {  arr[i] = -1;  }  
    }  
    
    把存储文件描述符的数组初始化为 -1,表示数组中没有有效的文件描述符。
  • arr_add 函数
    void arr_add(int arr[], int fd) {  for (int i = 0; i < MAXARR; i++) {  if (arr[i] == -1) {  arr[i] = fd;  break;  }  }  
    }  
    
    把新的文件描述符添加到数组中,找到第一个值为 -1 的位置,将新的文件描述符存入该位置。
  • arr_del 函数
    void arr_del(int arr[], int fd) {  for (int i = 0; i < MAXARR; i++) {  if (arr[i] == fd) {  arr[i] = -1;  break;  }  }  
    }  
    
    从数组中删除指定的文件描述符,找到该文件描述符所在的位置,将其值置为 -1
  • accept_cli 函数
    void accept_cli(int sockfd, int arr[]) {  int c = accept(sockfd, NULL, NULL);  if (c == -1) {  perror("accept err");  return;  }  printf("cli(%d) accept\n", c);  arr_add(arr, c);  
    }  
    
    接受新的客户端连接,若接受成功则将客户端的套接字文件描述符添加到数组中。
  • recv_cli 函数
    void recv_cli(int fd, int arr[]) {  char buff[128] = {0};  int n = recv(fd, buff, 127, 0);  if (n < 0) {  perror("recv err");  printf("cli(%d) close\n", fd);  close(fd);  arr_del(arr, fd);  return;  }  if (n == 0) {  printf("cli(%d) close\n", fd);  close(fd);  arr_del(arr, fd);  return;  }  printf("buff(c=%d):%s\n", fd, buff);  send(fd, "ok", 2, 0);  
    }  
    
    接收客户端发送的数据,若接收出错或者客户端关闭连接,则关闭对应的套接字并从数组中删除该文件描述符;若接收到数据,则打印数据并向客户端回复 "ok"
main 函数
int main() {  int sockfd = socket_init();  if (sockfd == -1) {  exit(1);  }  int arr[MAXARR];  arr_init(arr);  arr_add(arr, sockfd);  fd_set fdset;  while (1) {  FD_ZERO(&fdset);  int maxfd = -1;  for (int i = 0; i < MAXARR; i++) {  if (arr[i] == -1) {  continue;  }  FD_SET(arr[i], &fdset);  if (arr[i] > maxfd) {  maxfd = arr[i];  }  }  struct timeval tv = {5, 0};  int n = select(maxfd + 1, &fdset, NULL, NULL, &tv);  if (n == -1) {  perror("select err");  continue;  } else if (n == 0) {  printf("TIME OUT\n");  continue;  } else {  for (int i = 0; i < MAXARR; i++) {  if (arr[i] == -1) {  continue;  } else {  if (FD_ISSET(arr[i], &fdset)) {  if (arr[i] == sockfd) {  accept_cli(arr[i], arr);  } else {  recv_cli(arr[i], arr);  }  }  }  }  }  }  
}  
  • 初始化服务器套接字,若失败则退出程序。
  • 初始化存储文件描述符的数组,并将服务器套接字文件描述符添加到数组中。
  • 进入无限循环:
    • 每次循环开始时,清空 fd_set 集合。
    • 遍历数组,将有效的文件描述符添加到 fd_set 集合中,并找出最大的文件描述符。
    • 设置 select 函数的超时时间为 5 秒。
    • 调用 select 函数进行监听,根据返回值判断情况:若返回 -1 表示出错,打印错误信息并继续循环;若返回 0 表示超时,打印超时信息并继续循环;若返回大于 0 的值,表示有文件描述符就绪。
    • 再次遍历数组,检查哪些文件描述符就绪。若为服务器套接字,则调用 accept_cli 函数接受新的连接;若为客户端套接字,则调用 recv_cli 函数接收数据。

五、select 的优缺点

优点

  • 跨平台支持select 是一种标准的系统调用,几乎所有的 Unix/Linux 系统和 Windows 系统都支持,具有良好的跨平台性。
  • 简单易用select 的接口相对简单,使用起来比较方便,对于小规模的应用场景非常适用。

缺点

  • 文件描述符数量限制select 有最大文件描述符数量的限制,一般为 1024。如果需要处理大量的文件描述符,可能会受到限制。
  • 性能问题select 需要遍历所有的文件描述符来检查其状态,时间复杂度为 O(n),当文件描述符数量较多时,性能会受到影响。
  • 内核和用户空间数据拷贝:每次调用 select 时,都需要将文件描述符集合从用户空间拷贝到内核空间,在文件描述符数量较多时,会带来一定的开销。

六、适用场景

由于 select 存在一些局限性,它适用于文件描述符数量较少、对性能要求不是特别高的场景,例如一些简单的网络服务器、嵌入式系统等。在实际应用中,若需要处理大量文件描述符或对性能有更高要求,可以考虑使用 pollepoll 等更高级的 I/O 复用机制。

通过深入理解 select 的原理、使用方法和优缺点,我们能够在网络编程中更好地运用这一技术,构建高效稳定的网络应用。

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

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

相关文章

【Linux系列】目录大小查看

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

《AI大模型应知应会100篇》第48篇:构建企业级大模型应用的架构设计

第48篇&#xff1a;构建企业级大模型应用的架构设计 摘要&#xff1a;本文将提供企业级大模型应用的端到端架构设计方案&#xff0c;从系统设计原则到技术栈选择&#xff0c;从高可用保障到安全合规&#xff0c;全面覆盖构建稳健、可扩展、安全的大模型应用所需的工程实践。适合…

人协同的自动化需求分析

多人协同的自动化需求分析是指通过技术工具和协作流程&#xff0c;让多个参与者&#xff08;如产品经理、开发人员、测试人员等&#xff09;在需求分析阶段高效协作&#xff0c;并借助自动化手段提升需求收集、整理、验证和管理的效率与质量。以下是其核心要点&#xff1a; 1. …

【战略合作】开封大学_阀门产业学院+智橙PLM

12月20日&#xff0c;在核电厂阀门系列团体标准启动会上&#xff0c;开封大学阀门产业学院与橙色云互联网设计有限公司达成战略合作。 以平台赋能行业&#xff0c;让阀门教育“有的放矢” 会议与会者包括&#xff1a; 开封大学副校长 李治 中国国际科技促进会标准化工作委员…

element-ui日期时间选择器禁止输入日期

需求解释&#xff1a;时间日期选择器&#xff0c;下方日期有禁止选择范围&#xff0c;所以上面的日期输入框要求禁止输入&#xff0c;但时间输入框可以输入&#xff0c;也就是下图效果&#xff0c;其中日历中的禁止选择可以通过【picker-options】这个属性实现&#xff0c;此属…

计算机网络:深入分析三层交换机硬件转发表生成过程

三层交换机的MAC地址转发表生成过程结合了二层交换和三层路由的特性,具体可分为以下步骤: 一、二层MAC地址表学习(基础转发层) 初始状态 交换机启动时,MAC地址表为空,处于学习阶段。 数据帧接收与源MAC学习 当主机A发送数据帧到主机B时,交换机会检查数据帧的源MAC地址。…

【开源解析】基于Python的智能文件备份工具开发实战:从定时备份到托盘监控

&#x1f4c1;【开源解析】基于Python的智能文件备份工具开发实战&#xff1a;从定时备份到托盘监控 &#x1f308; 个人主页&#xff1a;创客白泽 - CSDN博客 &#x1f525; 系列专栏&#xff1a;&#x1f40d;《Python开源项目实战》 &#x1f4a1; 热爱不止于代码&#xff0…

Windows 环境变量完全指南:系统变量、用户变量与 PATH 详解

1. 什么是环境变量&#xff1f; 环境变量&#xff08;Environment Variables&#xff09;是 Windows 系统中用于存储配置信息的键值对&#xff0c;它们可以影响系统和应用程序的行为。例如&#xff1a; PATH&#xff1a;告诉系统在哪里查找可执行文件&#xff08;如 python、j…

详解RabbitMQ工作模式之工作队列模式

目录 工作队列模式 概念 特点 应用场景 工作原理 注意事项 代码案例 引入依赖 常量类 编写生产者代码 编写消费者1代码 编写消费者2代码 先运行生产者&#xff0c;后运行消费者 先运行消费者&#xff0c;后运行生产者 工作队列模式 概念 在工作队列模式中&#x…

数据结构-非线性结构-二叉树

概述 /** * 术语 * 根节点&#xff08;root node&#xff09;&#xff1a;位于二叉树顶层的节点&#xff0c;没有父节点。 * 叶节点&#xff08;leaf node&#xff09;&#xff1a;没有子节点的节点&#xff0c;其两个指针均指向 None 。 * 边&#xff08;edge&#xff09;&…

芯片笔记 - 手册参数注释

芯片手册参数注释 基础参数外围设备USB OTG&#xff08;On-The-Go&#xff09;以太网存储卡&#xff08;SD&#xff09;SDIO 3.0(Secure Digital Input/Output)GPIO&#xff08;General Purpose Input/Output 通用输入/输出接口&#xff09;ADC&#xff08;Analog to Digital C…

力扣94. 二叉树的中序遍历

94. 二叉树的中序遍历 给定一个二叉树的根节点 root &#xff0c;返回 它的 中序 遍历 。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[1,3,2]示例 2&#xff1a; 输入&#xff1a;root [] 输出&#xff1a;[]示例 3&#xff1a; 输入&#…

深度学习:AI为老年痴呆患者点亮希望之光

引言 随着全球人口老龄化进程的加速&#xff0c;老年痴呆症已成为严重威胁老年人健康和生活质量的公共卫生问题。据世界卫生组织统计&#xff0c;全球每 3 秒钟就有 1 人被诊断为痴呆&#xff0c;预计到 2050 年&#xff0c;全球痴呆患者人数将从目前的约 5000 万激增至 1.52 亿…

抛物线法(二次插值法)

抛物线法简介 抛物线法&#xff08;Quadratic Interpolation Method&#xff09;是一种用于一维单峰函数极值搜索的经典优化方法。该方法通过在区间内选取三个不同的点&#xff0c;拟合一条二次抛物线&#xff0c;并求取这条抛物线的极值点作为新的迭代点&#xff0c;从而逐步…

FreeRTOS如何检测内存泄漏

在嵌入式系统中&#xff0c;内存资源通常非常有限&#xff0c;内存泄漏可能导致系统性能下降甚至崩溃。内存泄漏是指程序分配的内存未被正确释放&#xff0c;逐渐耗尽可用内存。 FreeRTOS作为一种轻量级实时操作系统&#xff08;RTOS&#xff09;&#xff0c;广泛应用于资源受限…

Mockoon 使用教程

文章目录 一、简介二、模拟接口1、Get2、Post 一、简介 1、Mockoon 可以快速模拟API&#xff0c;无需远程部署&#xff0c;无需帐户&#xff0c;免费&#xff0c;跨平台且开源&#xff0c;适合离线环境。 2、支持get、post、put、delete等所有格式。 二、模拟接口 1、Get 左…

如何进行APP安全加固

进行APP安全加固的关键在于代码混淆、加密敏感数据、权限管理、漏洞扫描与修复。其中&#xff0c;代码混淆能有效阻止逆向工程与篡改攻击&#xff0c;提升应用的安全防护能力。通过混淆代码&#xff0c;攻击者难以轻易理解源代码逻辑&#xff0c;从而降低被破解或攻击的风险。 …

【C++】手搓一个STL风格的string容器

C string类的解析式高效实现 GitHub地址 有梦想的电信狗 1. 引言&#xff1a;字符串处理的复杂性 ​ 在C标准库中&#xff0c;string类作为最常用的容器之一&#xff0c;其内部实现复杂度远超表面认知。本文将通过一个简易仿照STL的string类的完整实现&#xff0c;揭示其设…

辰鳗科技朱越洋:紧扣时代契机,全力投身能源转型战略赛道

国家能源局于4月28日出台的《关于促进能源领域民营经济发展若干举措的通知》&#xff08;以下简称《通知》&#xff09;&#xff0c;是继2月民营企业座谈会后深化能源领域市场化改革的关键政策&#xff0c;标志着民营经济在“双碳”目标引领下正式进入能源转型的核心赛道。 自…

Vue实现不同网站之间的Cookie共享功能

前言 最近有小伙伴在聊天室中提到这么一个需求&#xff0c;就是说希望用户在博客首页中登录了之后&#xff0c;可以跳转到管理系统去发布文章。这个需求的话就涉及到了不同网站之间cookie共享的功能&#xff0c;那么咱们就来试着解决一下这个功能。 实现方式 1. 后端做中转 …