应用——Linux FIFO(命名管道)与I/O多路复用

Linux FIFO(命名管道)与I/O多路复用编程笔记

一、基础FIFO通信

1. 基本概念

  • FIFO(命名管道):存在于文件系统中的特殊文件,用于进程间通信

  • 特点

    • 半双工通信(单向)

    • 需要读写双方都打开才能正常通信

    • 阻塞式I/O(默认)

    • 数据按先进先出顺序传输

2. 基础实现

写入端
cpp 复制代码
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    int ret = mkfifo("myfifo", 0666);
    if (-1 == ret)
    {
        // 如果是文件已存在的错误,程序就继续运行
        if (EEXIST == errno)
        {
        }
        else  //如果是其他的错误,进程结束
        {
            perror("mkfifo");
            return 1;
        }
    }
    // open 会阻塞,
    //写段先运行 ,写段会等读段出现,
    // 读段先运行 ,读段会等写段出现,
    int fd = open("myfifo", O_WRONLY);
    if (-1 == fd)
    {
        perror("open");
        return 1;
    }
    while (1)
    {
        char buf[] = "hello,friend...\n";
        write(fd, buf, strlen(buf) + 1);
        sleep(3);
    }

    close(fd);

    return 0;
}
读取端
cpp 复制代码
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    int ret = mkfifo("myfifo", 0666);
    if (-1 == ret)
    {
        // 如果是文件已存在的错误,程序就继续运行
        if (EEXIST == errno)
        {
        }
        else  //如果是其他的错误,进程结束
        {
            perror("mkfifo");
            return 1;
        }
    }
    // open 会阻塞,
    //写段先运行 ,写段会等读段出现,
    // 读段先运行 ,读段会等写段出现,
    int fd = open("myfifo", O_RDONLY);
    if (-1 == fd)
    {
        perror("open");
        return 1;
    }
    while (1)
    {
        char buf[1024] = {0};
        read(fd, buf, sizeof(buf));
        printf("fifo_w:%s\n", buf);

        fgets(buf,sizeof(buf),stdin);
        printf("term:%s",buf);
        fflush(stdout);
    }

    close(fd);

    // remove("myfifo");

    return 0;
}

3. 注意事项

  • mkfifo错误处理:EEXIST错误应忽略,其他错误需处理

  • 打开阻塞:open()会阻塞直到另一端也被打开

  • 数据边界:需要约定数据格式,示例中以'\0'作为字符串结束

二、非阻塞I/O

1. 非阻塞读取端

cpp 复制代码
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    int ret = mkfifo("myfifo", 0666);
    if (-1 == ret)
    {
        // 如果是文件已存在的错误,程序就继续运行
        if (EEXIST == errno)
        {
        }
        else  //如果是其他的错误,进程结束
        {
            perror("mkfifo");
            return 1;
        }
    }
    // open 会阻塞,
    //写段先运行 ,写段会等读段出现,
    // 读段先运行 ,读段会等写段出现,
    int fd = open("myfifo", O_RDONLY);
    if (-1 == fd)
    {
        perror("open");
        return 1;
    }
    int flag = fcntl(fd, F_GETFL);
    //把管道的读写方式设置为非阻塞
    fcntl(fd, F_SETFL, flag | O_NONBLOCK);

    flag = fcntl(fileno(stdin), F_GETFL);
    //把标准输入stdin(0)的读写方式设置为非阻塞
    fcntl(fileno(stdin), F_SETFL, flag | O_NONBLOCK);

    while (1)
    {
        char buf[1024] = {0};
        if (read(fd, buf, sizeof(buf)) >0)
        {
            printf("fifo_w:%s\n", buf);
        }

        if (fgets(buf, sizeof(buf), stdin))
        {
            printf("term:%s", buf);
            fflush(stdout);
        }
    }

    close(fd);

    // remove("myfifo");

    return 0;
}

2. 非阻塞写入端

cpp 复制代码
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    int ret = mkfifo("myfifo", 0666);
    if (-1 == ret)
    {
        // 如果是文件已存在的错误,程序就继续运行
        if (EEXIST == errno)
        {
        }
        else  //如果是其他的错误,进程结束
        {
            perror("mkfifo");
            return 1;
        }
    }
    // open 会阻塞,
    //写段先运行 ,写段会等读段出现,
    // 读段先运行 ,读段会等写段出现,
    int fd = open("myfifo", O_WRONLY);
    if (-1 == fd)
    {
        perror("open");
        return 1;
    }
    while (1)
    {
        char buf[] = "hello,friend...\n";
        write(fd, buf, strlen(buf) + 1);
        sleep(3);
    }

    close(fd);

    return 0;
}

3. 非阻塞I/O特点

  • fcntl设置 :使用F_GETFL获取标志,F_SETFL设置O_NONBLOCK

  • 行为变化

    • read()立即返回,不阻塞

    • 如果无数据,read()返回-1,errno为EAGAIN

  • CPU使用:需要忙等待或轮询

三、信号驱动I/O

1. 信号驱动读取端

cpp 复制代码
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int fd;
void myhandle(int num)
{
    char buf[1024] = {0};
    read(fd, buf, sizeof(buf));
    printf("fifo_w:%s\n", buf);
}
int main(int argc, char **argv)
{
    signal(SIGIO, myhandle);
    int ret = mkfifo("myfifo", 0666);
    if (-1 == ret)
    {
        // 如果是文件已存在的错误,程序就继续运行
        if (EEXIST == errno)
        {
        }
        else  //如果是其他的错误,进程结束
        {
            perror("mkfifo");
            return 1;
        }
    }
    // open 会阻塞,
    //写段先运行 ,写段会等读段出现,
    // 读段先运行 ,读段会等写段出现,
    fd = open("myfifo", O_RDONLY);
    if (-1 == fd)
    {
        perror("open");
        return 1;
    }
    // 获得管道文件状态标志位
    int flag = fcntl(fd, F_GETFL);
    // 在原来标志的基础上 追加信号驱动
    fcntl(fd, F_SETFL, flag | O_ASYNC); // 或受到 sigio 信号
    // 设置sigio 信号的接收者
    fcntl(fd,F_SETOWN,getpid());
    while (1)
    {
        char buf[256]={0};
        fgets(buf, sizeof(buf), stdin);
        printf("term:%s", buf);
        fflush(stdout);
    }

    close(fd);

    // remove("myfifo");

    return 0;
}

2. 信号驱动写入端

cpp 复制代码
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    int ret = mkfifo("myfifo", 0666);
    if (-1 == ret)
    {
        // 如果是文件已存在的错误,程序就继续运行
        if (EEXIST == errno)
        {
        }
        else  //如果是其他的错误,进程结束
        {
            perror("mkfifo");
            return 1;
        }
    }
    // open 会阻塞,
    //写段先运行 ,写段会等读段出现,
    // 读段先运行 ,读段会等写段出现,
    int fd = open("myfifo", O_WRONLY);
    if (-1 == fd)
    {
        perror("open");
        return 1;
    }
    while (1)
    {
        char buf[] = "hello,friend...\n";
        write(fd, buf, strlen(buf) + 1);
        sleep(3);
    }

    close(fd);

    return 0;
}

3. 信号驱动I/O特点

  • 触发机制:当数据准备好时,内核发送SIGIO信号

  • 设置步骤

    1. 设置信号处理函数

    2. 使用fcntl(fd, F_SETFL, flag | O_ASYNC)启用异步I/O

    3. 使用fcntl(fd, F_SETOWN, getpid())设置接收进程

  • 优点:不需要轮询,CPU占用率低

四、select多路复用

1. select读取端 (04select_r.c)

cpp 复制代码
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

/* According to POSIX.1-2001, POSIX.1-2008 */
#include <sys/select.h>

/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    int ret = mkfifo("myfifo", 0666);
    if (-1 == ret)
    {
        // 如果是文件已存在的错误,程序就继续运行
        if (EEXIST == errno)
        {
        }
        else  //如果是其他的错误,进程结束
        {
            perror("mkfifo");
            return 1;
        }
    }
    // open 会阻塞,
    //写段先运行 ,写段会等读段出现,
    // 读段先运行 ,读段会等写段出现,
    int fd = open("myfifo", O_RDONLY);
    if (-1 == fd)
    {
        perror("open");
        return 1;
    }

    // 1 create set
    fd_set rdset,tmpset;  // read  set
    FD_ZERO(&rdset);
    FD_ZERO(&tmpset);

    // 2 . add fd to set
    FD_SET(fd, &tmpset);
    FD_SET(0, &tmpset);
    while (1)
    {
        char buf[1024] = {0};
       //5.clean flags
        rdset = tmpset;
        
        // 3 wait event
        select(fd + 1, &rdset, NULL, NULL, NULL);
        //4 . find fd
        if (FD_ISSET(fd, &rdset))  //管道可以读取
        {
            read(fd, buf, sizeof(buf));
            printf("fifo_w:%s\n", buf);
        }
        if (FD_ISSET(0, &rdset))  // stdin 可以读取
        {
            fgets(buf, sizeof(buf), stdin);
            printf("term:%s", buf);
            fflush(stdout);
        }
    }

    close(fd);

    // remove("myfifo");

    return 0;
}

2. select写入端

cpp 复制代码
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    int ret = mkfifo("myfifo", 0666);
    if (-1 == ret)
    {
        // 如果是文件已存在的错误,程序就继续运行
        if (EEXIST == errno)
        {
        }
        else  //如果是其他的错误,进程结束
        {
            perror("mkfifo");
            return 1;
        }
    }
    // open 会阻塞,
    //写段先运行 ,写段会等读段出现,
    // 读段先运行 ,读段会等写段出现,
    int fd = open("myfifo", O_WRONLY);
    if (-1 == fd)
    {
        perror("open");
        return 1;
    }
    while (1)
    {
        char buf[] = "hello,friend...\n";
        write(fd, buf, strlen(buf) + 1);
        sleep(3);
    }

    close(fd);

    return 0;
}

3. select使用步骤

  1. 创建fd_set :使用FD_ZERO初始化

  2. 添加文件描述符 :使用FD_SET添加需要监控的fd

  3. 调用select:阻塞等待事件发生

  4. 检查结果 :使用FD_ISSET检查哪些fd就绪

  5. 重置fd_set:select会修改fd_set,需要重新设置

五、epoll多路复用

1. epoll读取端

cpp 复制代码
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int add_fd(int epfd, int fd)
{
    struct epoll_event ev;
    ev.events = EPOLLIN;  // 关心读事件
    ev.data.fd = fd;      // 方便后期查找 fd

    int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
    if (-1 == ret)
    {
        perror("add_fd");
        return ret;
    }
    return 0;
}

int main(int argc, char **argv)
{
    int ret = mkfifo("myfifo", 0666);
    if (-1 == ret)
    {
        // 如果是文件已存在的错误,程序就继续运行
        if (EEXIST == errno)
        {
        }
        else  //如果是其他的错误,进程结束
        {
            perror("mkfifo");
            return 1;
        }
    }
    // open 会阻塞,
    //写段先运行 ,写段会等读段出现,
    // 读段先运行 ,读段会等写段出现,
    int fd = open("myfifo", O_RDONLY);
    if (-1 == fd)
    {
        perror("open");
        return 1;
    }
    // 1 create set
    struct epoll_event rev[2];
    int epfd = epoll_create(2);
    if (-1 == epfd)
    {
        perror("epoll_create");
        return 1;
    }

    // 2 add fd
    add_fd(epfd, 0);
    add_fd(epfd, fd);
    while (1)
    {
        char buf[1024] = {0};
        // 3 wait event
        int ep_ret = epoll_wait(epfd, rev, 2, -1);
        //4. 找到对应的文件描述符,并处理
        for (int i = 0; i < ep_ret; i++)
        {
            if (rev[i].data.fd == fd)
            {
                read(fd, buf, sizeof(buf));
                printf("fifo_w:%s\n", buf);
            }
            if (0 == rev[i].data.fd)
            {
                fgets(buf, sizeof(buf), stdin);
                printf("term:%s", buf);
                fflush(stdout);
            }
        }
    }

    close(fd);

    // remove("myfifo");

    return 0;
}

2. epoll写入端

cpp 复制代码
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    int ret = mkfifo("myfifo", 0666);
    if (-1 == ret)
    {
        // 如果是文件已存在的错误,程序就继续运行
        if (EEXIST == errno)
        {
        }
        else  //如果是其他的错误,进程结束
        {
            perror("mkfifo");
            return 1;
        }
    }
    // open 会阻塞,
    //写段先运行 ,写段会等读段出现,
    // 读段先运行 ,读段会等写段出现,
    int fd = open("myfifo", O_WRONLY);
    if (-1 == fd)
    {
        perror("open");
        return 1;
    }
    while (1)
    {
        char buf[] = "hello,friend...\n";
        write(fd, buf, strlen(buf) + 1);
        sleep(3);
    }

    close(fd);

    return 0;
}

3. epoll使用步骤

  1. 创建epoll实例epoll_create()

  2. 注册事件 :使用epoll_ctl(EPOLL_CTL_ADD)添加监控的fd

  3. 等待事件epoll_wait()阻塞等待事件

  4. 处理事件:遍历返回的事件数组进行处理

4. epoll vs select

特性 select epoll
最大fd数 受FD_SETSIZE限制(通常1024) 仅受系统资源限制
效率 O(n)线性扫描 O(1)事件通知
内存复制 每次调用都需要复制fd_set 内核与用户空间共享内存
触发模式 仅支持水平触发 支持水平触发和边缘触发

六、总结对比

I/O模型对比

模型 原理 优点 缺点 适用场景
阻塞I/O 同步等待数据 编程简单 效率低,无法处理多路 简单应用
非阻塞I/O 轮询检查状态 不会阻塞进程 CPU占用高 低延迟应用
信号驱动 信号通知就绪 异步处理,CPU占用低 编程复杂,信号可能丢失 低负载应用
select 多路复用轮询 可监控多个fd fd数有限,效率随fd增加下降 跨平台,fd数少
epoll 事件通知机制 高效,支持大量fd Linux特有 高性能服务器

编程要点

  1. FIFO创建:注意EEXIST错误处理

  2. 打开方式:O_RDONLY或O_WRONLY,会阻塞直到另一端打开

  3. 数据边界:需要明确数据结束标志

  4. 资源清理:程序退出前关闭fd,可考虑删除FIFO文件

最佳实践建议

  1. 简单应用:使用阻塞I/O

  2. 多路监控:Linux下优先使用epoll,跨平台考虑select

  3. 高性能:epoll + 非阻塞I/O

  4. 信号安全:信号处理函数中避免复杂操作

相关推荐
无奈笑天下2 小时前
麒麟V10SP1虚拟机安装vmtool-参考教程
linux·运维·服务器·个人开发
郝学胜-神的一滴2 小时前
Linux多线程编程:深入理解pthread_cancel函数
linux·服务器·开发语言·c++·软件工程
好奇龙猫2 小时前
大学院-筆記試験練習:数据库(データベース問題訓練) と 软件工程(ソフトウェア)(1)
学习·大学院
studyForMokey2 小时前
【跨端技术】React Native学习记录一
javascript·学习·react native·react.js
代码游侠2 小时前
复习——网络编程基础
linux·服务器·网络·笔记·网络协议
Trouvaille ~2 小时前
【C++篇】让错误被温柔对待(下):异常高级特性与最佳实践
运维·开发语言·c++·异常·raii·编程实践·基础入门
先生沉默先2 小时前
串口通信学习,使用winform读取串口发送数据,(2)
学习·c#·串口
霜!!2 小时前
openssh升级
linux·运维·服务器
Vect__2 小时前
25.12.27 理解文件本质+文件系统调用接口+fd+重定向
linux