Linux 下 select 详解

在Linux中,select 函数是用于监控多个文件描述符(file descriptors)的系统调用。它允许程序员同时等待多个I/O事件的发生,如文件读取、写入或异常状态。它在处理多路复用I/O时非常常用,尤其适合编写网络服务器或客户端程序。

select 函数的定义

#include <sys/select.h>

int select(int nfds,

fd_set *readfds,

fd_set *writefds,

fd_set *exceptfds,

struct timeval *timeout);

参数解释:
  • nfds:指定监控的文件描述符数量。它应该是所有监控的文件描述符集合中最大值加1,因为文件描述符是从0开始计数的。
  • readfds :指向一个文件描述符集合,用于监控是否有文件可读。可以使用宏函数 FD_SET() 将描述符添加到集合。
  • writefds:指向一个文件描述符集合,用于监控是否有文件可写。
  • exceptfds:指向一个文件描述符集合,用于监控异常状态。
  • timeout :指定 select 等待的时间,可以是:
    • NULLselect 将无限期等待,直到有文件描述符准备好。
    • 0秒的时间 :表示非阻塞模式,select 立即返回。
    • 自定义时间 :例如等待5秒,可以通过 struct timeval 指定。
返回值:
  • 返回值为大于0的数值表示有多少文件描述符准备好。
  • 返回0表示超时。
  • 返回-1表示出错,并且设置 errno

使用步骤

  1. 清空文件描述符集合 : 在使用 select 之前,首先需要初始化或清空文件描述符集合。

    FD_ZERO(&readfds);

    FD_ZERO(&writefds);

    FD_ZERO(&exceptfds);

  2. 设置需要监控的文件描述符 : 使用 FD_SET() 函数将需要监控的文件描述符加入到集合中。

    FD_SET(fd, &readfds);

  3. 调用 select 函数 : 通过调用 select 来监控多个文件描述符。

    int ready = select(nfds, &readfds, &writefds, &exceptfds, &timeout);

  4. 检查哪些文件描述符已准备好select 返回后,可以使用 FD_ISSET() 函数检查哪些文件描述符已经准备好。

    if (FD_ISSET(fd, &readfds)) { // 该文件描述符可读 }

示例代码

以下是一个简单的例子,演示如何使用 select 同时监控标准输入和一个网络套接字的读事件:

cpp 复制代码
#include <stdio.h>
#include <sys/select.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int main() {
    int sockfd;
    struct sockaddr_in server;
    fd_set readfds;
    struct timeval timeout;

    // 创建一个socket
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    server.sin_family = AF_INET;
    server.sin_port = htons(8080);
    server.sin_addr.s_addr = inet_addr("127.0.0.1");

    // 连接服务器
    connect(sockfd, (struct sockaddr *)&server, sizeof(server));

    while (1) {
        // 清空集合并添加文件描述符
        FD_ZERO(&readfds);
        FD_SET(STDIN_FILENO, &readfds);  // 标准输入
        FD_SET(sockfd, &readfds);        // 套接字

        // 计算 nfds
        int nfds = sockfd + 1;

        // 设置超时时间,5秒
        timeout.tv_sec = 5;
        timeout.tv_usec = 0;

        // 调用 select 函数
        int ready = select(nfds, &readfds, NULL, NULL, &timeout);
        
        if (ready == -1) {
            perror("select error");
            return 1;
        } else if (ready == 0) {
            printf("Timeout, no data available\n");
        } else {
            // 检查是否标准输入可读
            if (FD_ISSET(STDIN_FILENO, &readfds)) {
                char buffer[256];
                read(STDIN_FILENO, buffer, sizeof(buffer));
                printf("Input: %s", buffer);
            }

            // 检查套接字是否可读
            if (FD_ISSET(sockfd, &readfds)) {
                char buffer[256];
                int bytes = read(sockfd, buffer, sizeof(buffer));
                if (bytes > 0) {
                    printf("Received from server: %s\n", buffer);
                } else {
                    printf("Server closed connection\n");
                    break;
                }
            }
        }
    }

    close(sockfd);
    return 0;
}

文件描述符集合的操作

为了管理 select 函数的文件描述符集合,使用了以下几个宏:

  • 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 是否在集合中,返回非0值表示在集合中。

select 的限制

  1. 文件描述符的数量select 对于监控的文件描述符数量有限制,通常为 FD_SETSIZE,在许多系统中默认为1024。对于大量连接的场景,推荐使用 pollepoll

  2. 性能问题 : 当文件描述符数量非常多时,select 的性能会急剧下降,因为它需要遍历所有的描述符来检查哪些可用。

相关系统调用

  • poll :与 select 类似,但没有文件描述符数量限制,并且性能在大文件描述符集上更好。
  • epoll :Linux特有的多路复用系统调用,性能高于 selectpoll,适合处理大规模并发连接。

总结

select 函数是一个经典的多路复用I/O处理函数,适用于监控少量文件描述符的场景。尽管有一定的限制和性能瓶颈,但在简单的网络编程场景下仍然常用。对于需要处理大量并发连接的场景,推荐使用 epoll 等更现代化的系统调用。

相关推荐
滴水之功21 分钟前
VMware OpenWrt怎么桥接模式联网
linux·openwrt
夏木~31 分钟前
Oracle 中什么情况下 可以使用 EXISTS 替代 IN 提高查询效率
数据库·oracle
W215534 分钟前
Liunx下MySQL:表的约束
数据库·mysql
黄名富39 分钟前
Redis 附加功能(二)— 自动过期、流水线与事务及Lua脚本
java·数据库·redis·lua
ldinvicible39 分钟前
How to run Flutter on an Embedded Device
linux
言、雲43 分钟前
从tryLock()源码来出发,解析Redisson的重试机制和看门狗机制
java·开发语言·数据库
YRr YRr1 小时前
解决Ubuntu 20.04上编译OpenCV 3.2时遇到的stdlib.h缺失错误
linux·opencv·ubuntu
认真学习的小雅兰.1 小时前
如何在Ubuntu上利用Docker和Cpolar实现Excalidraw公网访问高效绘图——“cpolar内网穿透”
linux·ubuntu·docker
一个程序员_zhangzhen1 小时前
sqlserver新建用户并分配对视图的只读权限
数据库·sqlserver
zfj3212 小时前
学技术学英文:代码中的锁:悲观锁和乐观锁
数据库·乐观锁··悲观锁·竞态条件