select和poll

select函数

原理

select通过一个文件描述符集合(fd_set)来监控多个文件描述符。它会检查这些文件描述符是否准备好进行读、写或异常操作。fd_set是一个位数组,每个位对应一个文件描述符。select会扫描这个数组,检查每个文件描述符的状态。

使用方法

使用FD_SET宏将文件描述符添加到fd_set中,然后调用select函数。select函数会返回准备好操作的文件描述符的数量,并更新fd_set,将未准备好的文件描述符从集合中移除。

优点

  • select的API相对简单,容易理解和使用。
  • 几乎所有操作系统都支持select,具有很好的兼容性。

缺点

  • select的最大文件描述符数量通常受限于系统定义的FD_SETSIZE(通常是1024)。当监控大量文件描述符时,性能会显著下降。
  • select需要扫描整个fd_set来确定哪些文件描述符准备好,这在文件描述符数量较多时效率较低。
  • select需要为每个文件描述符分配一个位,当文件描述符数量较多时,会浪费较多的内存资源。
  • 不适用于需要处理大量文件描述符的场景。
#include <sys/select.h>
#include <sys/time.h>

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
//nfds:需要监控的文件描述符的最大值加1
//readfds:指向可读文件描述符集合的指针
//writefds:指向可写文件描述符集合的指针
//exceptfds:指向异常条件文件描述符集合的指针
//timeout:超时时间
//ret:返回集合中文件描述符的数量,调用完成后没有改变的文件描述符会被清除

#include <stdio.h>
#include <stdlib.h>
#include <sys/select.h>
#include <unistd.h>

int main() {
    // 创建两个文件描述符,用于示例
    int pipefds[2];
    if (pipe(pipefds) == -1) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }

    // 初始化文件描述符集合
    fd_set readfds;
    FD_ZERO(&readfds);
    FD_SET(pipefds[0], &readfds); // 读端
    FD_SET(STDIN_FILENO, &readfds); // 标准输入

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

    // 调用 select
    int ret = select(pipefds[0] + 1, &readfds, NULL, NULL, &timeout);
    if (ret == -1) {
        perror("select");
        exit(EXIT_FAILURE);
    } else if (ret == 0) {
        printf("Select timeout\n");
    } else {
        if (FD_ISSET(pipefds[0], &readfds)) {
            printf("Pipe read end is ready for reading\n");
        }
        if (FD_ISSET(STDIN_FILENO, &readfds)) {
            printf("Standard input is ready for reading\n");
        }
    }

    // 关闭文件描述符
    close(pipefds[0]);
    close(pipefds[1]);

    return 0;
}

poll函数

原理

poll使用一个pollfd结构数组来监控文件描述符。每个pollfd结构包含一个文件描述符、要监控的事件(如读、写)和事件状态。poll函数会检查这些结构,确定哪些文件描述符已经准备好指定的事件。

使用方法

创建一个pollfd数组,为每个文件描述符设置要监控的事件,然后调用poll函数。poll函数会返回准备好操作的文件描述符的数量,并更新每个pollfd结构的事件状态。

优点

  • poll没有像select那样的文件描述符数量限制,可以处理更多的文件描述符。
  • poll直接返回准备好操作的文件描述符,不需要像select那样扫描整个集合,效率相对较高。

缺点

  • poll需要为每个文件描述符分配一个pollfd结构,当文件描述符数量较多时,会占用较多的内存资源。
  • 虽然poll没有文件描述符数量限制,但在文件描述符数量非常多时,性能也会受到影响,因为poll需要逐个检查每个pollfd结构。
  • 不适用于高并发的场景
struct pollfd {
    int fd;        // 文件描述符
    short events;  // 需要监控的事件
    short revents; // 实际发生的事件
};
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
//fds:指向 pollfd 结构数组的指针
//nfds:pollfd 结构数组的大小。
//timeout:超时时间(毫秒)。如果设置为 -1,则表示阻塞直到有文件描述符准备好。
//ret:>0 状态改变的文件描述符的数量  =0 超时   -1,表示调用发生错误。
#include <stdio.h>
#include <stdlib.h>
#include <poll.h>
#include <unistd.h>

int main() {
    // 创建两个文件描述符,用于示例
    int pipefds[2];
    if (pipe(pipefds) == -1) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }

    // 初始化 pollfd 结构数组
    struct pollfd fds[2];
    fds[0].fd = pipefds[0]; // 读端
    fds[0].events = POLLIN;
    fds[1].fd = STDIN_FILENO; // 标准输入
    fds[1].events = POLLIN;

    // 调用 poll
    int ret = poll(fds, 2, -1); // 阻塞等待
    if (ret == -1) {
        perror("poll");
        exit(EXIT_FAILURE);
    }

    // 检查结果
    if (ret > 0) {
        if (fds[0].revents & POLLIN) {
            printf("Pipe read end is ready for reading\n");
        }
        if (fds[1].revents & POLLIN) {
            printf("Standard input is ready for reading\n");
        }
    }

    // 关闭文件描述符
    close(pipefds[0]);
    close(pipefds[1]);

    return 0;
}
posted @ 2025-05-26 21:05  LRadian  阅读(81)  评论(0)    收藏  举报