使用select语句在C语言中进行I/O多路复用

在C语言中,我们通常使用阻塞式I/O函数(如read()write())来处理文件或套接字的读写操作,这些函数会阻塞程序的执行,直到数据传输完成,为了提高程序的并发性和响应性,我们可以使用非阻塞式I/O函数或者I/O多路复用技术,select语句是一种常用的I/O多路复用技术,它允许程序同时监视多个文件描述符的状态变化,从而实现在一个线程中处理多个I/O事件。

(图片来源网络,侵删)

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:等待I/O事件发生的最长时间,如果为NULL,则表示无限等待。

返回值:

成功:返回发生读、写或异常事件的总文件描述符数。

超时:返回0。

出错:返回1。

select语句的使用步骤

1、将需要监视的文件描述符添加到相应的集合中。

2、调用select函数,等待I/O事件发生。

3、检查select函数的返回值和文件描述符集合,判断哪个文件描述符上发生了I/O事件。

4、对发生的I/O事件进行处理。

5、重复步骤14,直到程序结束。

示例代码

下面是一个简单的使用select语句的示例,该程序同时监视标准输入、标准输出和标准错误三个文件描述符的读、写和异常事件:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/select.h>
#include <time.h>
int main() {
    fd_set rfds, wfds, efds;
    struct timeval tv;
    int ret;
    char buf[1024];
    char oldPath[] = "/tmp";
    char newPath[] = "/var/tmp";
    char oldPathFile[] = "/tmp/file";
    char newPathFile[] = "/var/tmp/file";
    // 初始化文件描述符集合和时间变量
    FD_ZERO(&rfds);
    FD_ZERO(&wfds);
    FD_ZERO(&efds);
    tv.tv_sec = 5; // 等待5秒
    tv.tv_usec = 0;
    // 将需要监视的文件描述符添加到相应的集合中
    FD_SET(STDIN_FILENO, &rfds); // 监视标准输入的读事件
    FD_SET(STDOUT_FILENO, &wfds); // 监视标准输出的写事件
    FD_SET(STDERR_FILENO, &efds); // 监视标准错误的异常事件(如输出到终端)
    // 循环等待I/O事件发生,并处理发生的事件
    while (1) {
        ret = select(3, &rfds, &wfds, &efds, &tv); // 监视3个文件描述符(标准输入、标准输出和标准错误)的事件,最多等待5秒
        if (ret == 1) { // select函数出错,退出程序
            perror("select");
            exit(EXIT_FAILURE);
        } else if (ret == 0) { // 超时,继续等待下一个事件循环
            continue;
        } else { // select函数返回值大于0,表示有I/O事件发生,处理发生的事件并更新时间变量和文件描述符集合
            if (FD_ISSET(STDIN_FILENO, &rfds)) { // 标准输入有读事件发生,读取数据并显示在屏幕上
                if (fgets(buf, sizeof(buf), stdin) != NULL) { // 读取成功,显示数据并清空缓冲区
                    printf("%s", buf);
                    fflush(stdout); // 确保数据已经写入到标准输出缓冲区并显示在屏幕上(如果需要的话)
                } else { // 读取失败,可能是已经读到文件末尾或发生其他错误,退出程序
                    break;
                }
            } else if (FD_ISSET(STDOUT_FILENO, &wfds)) { // 标准输出有写事件发生,将数据写入到文件中(这里只是演示,实际上不会发生写事件)
                // do nothing, just for demonstration purposes
            } else if (FD_ISSET(STDERR_FILENO, &efds)) { // 标准错误有异常事件发生(如输出到终端),关闭文件描述符并退出程序(这里只是演示,实际上不会发生异常事件)
                close(STDERR_FILENO); // 关闭标准错误文件描述符(这里只是演示,实际上不会发生异常事件)
                exit(EXIT_SUCCESS); // 正常退出程序(这里只是演示,实际上不会发生异常事件)
            } else { // select函数返回值大于0,但没有检测到任何I/O事件发生,可能是已经读到文件末尾或发生其他错误,退出程序(这里只是演示,实际上不会发生这种情况)
                break;
            }
        }
    }
    unlink(oldPathFile); // 删除旧路径下的文件(这里只是演示,实际上不会发生写事件)
    link(newPathFile, oldPathFile); // 创建新路径下的链接(这里只是演示,实际上不会发生写事件)
    unlink(newPathFile); // 删除新路径下的文件(这里只是演示,实际上不会发生写事件)
    link(oldPathFile, newPathFile); // 恢复旧路径下的文件(这里只是演示,实际上不会发生写事件)
    close(STDERR_FILENO); // 关闭标准错误文件描述符(这里只是演示,实际上不会发生异常事件) // select语句的基本用法和使用步骤就介绍到这里了,通过使用select语句,我们可以在一个线程中同时处理多个I/O事件,从而提高程序的并发性和响应性,需要注意的是,select语句只能用于Linux和类Unix系统,Windows系统使用不同的API来实现类似的功能,select语句还有一些限制和不足之处,例如不能处理大量文件描述符、不能处理网络套接字等,在实际开发中,我们通常会使用更高级的I/O多路复用技术,如epoll、kqueue等。

评论列表

李婷
李婷
2024-01-13

这篇文章详细介绍了如何在C语言中使用select语句进行I/O多路复用,让我对这一高级特性有了更深入的了解。

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。