当前位置: 首页 > backend >正文

解析 select 函数

解析 select 函数

select 函数是 Unix/Linux 系统中用于多路复用的系统调用,主要用于在多个文件描述符(file descriptors)上等待事件的发生。它允许程序同时监视多个 I/O 通道,并在任意一个通道准备好进行 I/O 操作时通知程序,从而实现高效的 I/O 处理。

函数原型
#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,则 select 会一直阻塞直到有事件发生。
fd_set 结构

fd_set 是一个文件描述符集合,通常使用以下宏来操作:

  • FD_ZERO(fd_set *set): 清空文件描述符集合。
  • FD_SET(int fd, fd_set *set): 将文件描述符 fd 添加到集合中。
  • FD_CLR(int fd, fd_set *set): 从集合中移除文件描述符 fd
  • FD_ISSET(int fd, fd_set *set): 检查文件描述符 fd 是否在集合中。

工作原理

select 的工作流程如下:

  1. 初始化文件描述符集合: 使用 FD_ZERO 初始化 readfdswritefdsexceptfds
  2. 添加监视的文件描述符: 使用 FD_SET 将需要监视的文件描述符添加到相应的集合中。
  3. 调用 select: select 会阻塞,直到至少有一个文件描述符准备好进行 I/O 操作,或者超时。
  4. 检查结果: 使用 FD_ISSET 检查哪些文件描述符准备好了,并根据需要进行处理。

应用场景

1. 网络服务器

描述: 网络服务器需要同时处理多个客户端连接。select 可以监视所有客户端的套接字,检测哪些套接字有数据可读或可写。

示例:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/select.h>#define PORT 8080
#define MAX_CLIENTS 1024int main() {int server_fd, new_socket, client_sockets[MAX_CLIENTS];fd_set read_fds;struct sockaddr_in address;int opt = 1;int addrlen = sizeof(address);int max_sd;// 初始化客户端套接字数组for (int i = 0; i < MAX_CLIENTS; i++) {client_sockets[i] = 0;}// 创建服务器套接字if ((server_fd = socket(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_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(PORT);if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {perror("bind failed");exit(EXIT_FAILURE);}// 监听if (listen(server_fd, 3) < 0) {perror("listen");exit(EXIT_FAILURE);}while (1) {// 清空文件描述符集合FD_ZERO(&read_fds);// 添加服务器套接字到集合中FD_SET(server_fd, &read_fds);max_sd = server_fd;// 添加客户端套接字到集合中for (int i = 0; i < MAX_CLIENTS; i++) {int sd = client_sockets[i];if (sd > 0) {FD_SET(sd, &read_fds);}if (sd > max_sd) {max_sd = sd;}}// 调用 selectint activity = select(max_sd + 1, &read_fds, NULL, NULL, NULL);if (activity < 0) {perror("select error");exit(EXIT_FAILURE);}// 检查是否有新连接if (FD_ISSET(server_fd, &read_fds)) {if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {perror("accept");exit(EXIT_FAILURE);}printf("New connection, socket fd is %d\n", new_socket);// 添加到客户端数组中for (int i = 0; i < MAX_CLIENTS; i++) {if (client_sockets[i] == 0) {client_sockets[i] = new_socket;break;}}}// 检查客户端套接字是否有数据可读for (int i = 0; i < MAX_CLIENTS; i++) {int sd = client_sockets[i];if (FD_ISSET(sd, &read_fds)) {char buffer[1024] = {0};int valread = read(sd, buffer, 1024);if (valread == 0) {// 客户端断开连接printf("Connection closed, socket fd is %d\n", sd);close(sd);client_sockets[i] = 0;} else {// 处理数据printf("Received message: %s\n", buffer);// 回显send(sd, buffer, valread, 0);}}}}return 0;
}
2. 终端应用

描述: 终端应用需要同时处理用户输入和定时事件。select 可以监视标准输入和定时器文件描述符。

示例:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/select.h>int main() {fd_set read_fds;struct timeval timeout;while (1) {// 清空文件描述符集合FD_ZERO(&read_fds);// 添加标准输入到集合中FD_SET(STDIN_FILENO, &read_fds);// 设置超时时间为10秒timeout.tv_sec = 10;timeout.tv_usec = 0;int activity = select(STDIN_FILENO + 1, &read_fds, NULL, NULL, &timeout);if (activity < 0) {perror("select");break;} else if (activity == 0) {printf("Timeout occurred!\n");} else {if (FD_ISSET(STDIN_FILENO, &read_fds)) {char buffer[100];int n = read(STDIN_FILENO, buffer, sizeof(buffer));if (n > 0) {buffer[n] = '\0';printf("You entered: %s\n", buffer);}}}}return 0;
}

优缺点

优点
  1. 简单易用: select 提供了一种简单的方法来监视多个文件描述符。
  2. 跨平台: 在大多数 Unix/Linux 系统上都有实现,具有良好的可移植性。
  3. 灵活性: 可以监视不同类型的 I/O 事件(读、写、异常)。
缺点
  1. 性能问题: select 的性能在处理大量文件描述符时较差,因为每次调用都需要遍历所有监视的文件描述符。
  2. 文件描述符数量限制: select 通常有一个上限(通常是 1024),对于需要监视大量连接的应用不适用。
  3. 可读性差: 使用 select 的代码通常较为复杂,难以维护。

替代方案

由于 select 的局限性,现代应用中更常使用以下替代方案:

  1. poll:

    • 描述: poll 提供了与 select 类似的功能,但使用不同的接口,支持更大的文件描述符集合。
    • 优点: 没有文件描述符数量限制,性能优于 select
    • 缺点: 仍然需要遍历所有文件描述符,效率提升有限。
  2. epoll (Linux):

    • 描述: epoll 是 Linux 特有的高效 I/O 多路复用机制,适用于处理大量并发连接。
    • 优点: 高效,支持边缘触发和水平触发,事件驱动。
    • 缺点: 仅适用于 Linux 系统。
  3. kqueue (BSD/macOS):

    • 描述: kqueue 是 BSD 系统(如 macOS)上的高效 I/O 多路复用机制。
    • 优点: 高效

select 函数在以下情况下会检测写就绪:

  1. 发送缓冲区有足够空间

    • 当发送缓冲区中有足够的空间来容纳要写入的数据时,select 会将对应的文件描述符标记为写就绪。这意味着对文件描述符执行写操作不会阻塞【5†source】【8†source】。
  2. 写操作被关闭

    • 当对文件描述符的写操作被关闭(例如,通过 closeshutdown 函数)时,对这个写操作被关闭的文件描述符进行写操作会触发 SIGPIPE 信号。在这种情况下,select 会将文件描述符标记为写就绪【5†source】【8†source】。
  3. 非阻塞 connect 操作完成

    • 对于非阻塞的 connect 操作,当连接成功或失败时,select 会将文件描述符标记为写就绪【8†source】。
  4. 异常事件

    • 虽然主要与写操作相关,但 select 也会检测异常事件。例如,当 socket 收到带外数据时,select 会将文件描述符标记为异常就绪【8†source】。

select监听写就绪

  • 发送缓冲区空间

    • 当发送缓冲区的空闲空间大于或等于低水位标记 SO_SENDLOWAT 时,select 会将文件描述符标记为写就绪。这意味着程序可以无阻塞地写入数据【5†source】。
  • 写操作关闭

    • 如果对文件描述符的写操作被关闭,尝试写入数据会导致 SIGPIPE 信号。在这种情况下,select 会将文件描述符标记为写就绪,以便程序可以处理这种情况【5†source】。
  • 非阻塞 connect

    • 对于非阻塞的 connect 操作,select 可以用来检测连接是否成功或失败。当连接操作完成时,select 会将文件描述符标记为写就绪【8†source】。

示例代码

以下是一个简单的示例,展示了如何使用 select 来检测写就绪:

#include <stdio.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>int main() {int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0) {perror("socket");return 1;}struct sockaddr_in addr;memset(&addr, 0, sizeof(addr));addr.sin_family = AF_INET;addr.sin_port = htons(8080);addr.sin_addr.s_addr = inet_addr("127.0.0.1");// 尝试连接int ret = connect(sockfd, (struct sockaddr *)&addr, sizeof(addr));if (ret < 0) {perror("connect");close(sockfd);return 1;}fd_set writefds;struct timeval timeout;timeout.tv_sec = 5;timeout.tv_usec = 0;FD_ZERO(&writefds);FD_SET(sockfd, &writefds);ret = select(sockfd + 1, NULL, &writefds, NULL, &timeout);if (ret < 0) {perror("select");close(sockfd);return 1;} else if (ret == 0) {printf("Timeout occurred!\n");} else {if (FD_ISSET(sockfd, &writefds)) {printf("Socket is ready for writing!\n");}}close(sockfd);return 0;
}

在这个示例中,select 会等待最多5秒钟,直到 sockfd 准备好进行写操作。如果 sockfd 准备好,select 会返回,并通过 FD_ISSET 检查是否就绪。

总结

select 函数通过监视文件描述符的读写和异常状态,提供了一种有效的多路复用机制。

http://www.xdnf.cn/news/1690.html

相关文章:

  • Obsidian和Ollama大语言模型的交互过程
  • Kotlin Multiplatform--02:项目结构进阶
  • Kafka 命令行操作与 Spark-Streaming 核心编程总结
  • Python3 基础:变量、数据类型和基本运算
  • 驱动开发系列53 - 一个OpenGL应用程序是如何调用到驱动厂商GL库的
  • 济南国网数字化培训班学习笔记-第二组-5节-输电线路设计
  • vue3--手写手机屏组件
  • 【工具】使用 MCP Inspector 调试服务的完全指南
  • 关于nginx,负载均衡是什么?它能给我们的业务带来什么?怎么去配置它?
  • 服务器的演进与应用:从物理设备到云端革命
  • 前端出现的一些新技术或者升级的技术汇总
  • Git多人协作与企业级开发模型
  • 两段文本比对,高亮出差异部分
  • 【多智能体系统】特点解析与高效组织策略
  • Milvus(6):Collection 管理分区、管理别名
  • 深度解析 Kubernetes 配置管理:如何安全使用 ConfigMap 和 Secret
  • 字典与集合——测试界的黑话宝典与BUG追捕术
  • C语言编程--16.删除链表的倒数第n个节点
  • 触觉智能RK3506核心板,工业应用之RK3506 RT-Linux实时性测试
  • arm64适配系列文章-第九章-arm64环境上sentinel的部署
  • 【mysql】windows mysql命令
  • Verilog 语法 (一)
  • springboot在eclipse里面运行 run as 是Java Application还是 Maven
  • Java面试场景篇:分布式锁的实现与组件详解
  • MCP‌和LangGraph‌结合2
  • 基于Vue3 的 h5监听从左到右手滑返回上一页
  • 开源模型应用落地-语音合成-MegaTTS3-零样本克隆与多语言生成的突破
  • 从工作到娱乐:Codigger Desktop 让桌面环境更智能
  • c#-命名和书写规范
  • k8s基于角色的访问控制(RBAC)