【进程间通信-有名管道_Linux】

01 / 有名管道

◼ 匿名管道,由于没有名字,只能用于亲缘关系的进程间通信。为了克服这个缺点,提出了有名管道(FIFO),也叫命名管道、FIFO文件

有名管道(FIFO)不同于匿名管道之处在于它提供了一个路径名与之关联,以 FIFO 的文件形式存在于文件系统中,并且其打开方式与打开一个普通文件是一样的,这样即使与 FIFO 的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过 FIFO 相互通信,因此,通过 FIFO 不相关的进程也能交换数据。

◼ 一旦打开了 FIFO,就能在它上面使用与操作匿名管道和其他文件的系统调用一样的I/O系统调用了(如read()、write()和close())。与管道一样,FIFO 也有一个写入端和读取端,并且从管道中读取数据的顺序与写入的顺序是一样的。FIFO 的名称也由此而来:先入先出。

有名管道(FIFO)和匿名管道(pipe)有一些特点是相同的,不一样的地方在于:

  1. FIFO 在文件系统中作为一个特殊文件存在,但 FIFO 中的内容却存放在内存中

  2. 当使用 FIFO 的进程退出后,FIFO 文件将继续保存在文件系统中以便以后使用。

  3. FIFO 有名字,不相关的进程可以通过打开有名管道进行通信。

 创建fifo文件
 1.通过命令: mkfifo 名字
 2.通过函数:int mkfifo(const char *pathname, mode_t mode);

 #include <sys/types.h>
 #include <sys/stat.h>
 int mkfifo(const char *pathname, mode_t mode);
     参数:
         - pathname: 管道名称的路径
         - mode: 文件的权限 和 open 的 mode 是一样的
                 是一个八进制的数
     返回值:成功返回0,失败返回-1,并设置错误号

示例

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
    // 判断文件是否存在
    int ret = access("fifo1", F_OK);
    if(ret == -1) {
        printf("管道不存在,创建管道\n");        
        ret = mkfifo("fifo1", 0664);
        if(ret == -1) {
            perror("mkfifo");
            exit(0);
        }       
    }
    return 0;
}
01.1 / 有名管道案例_向管道中写数据

write.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>

int main() {

    // 1.判断文件是否存在
    int ret = access("test", F_OK);
    if(ret == -1) {
        printf("管道不存在,创建管道\n");       
        // 2.创建管道文件
        ret = mkfifo("test", 0664);
        if(ret == -1) {
            perror("mkfifo");
            exit(0);
        }       
    }

    // 3.以只写的方式打开管道
    int fd = open("test", O_WRONLY);
    if(fd == -1) {
        perror("open");
        exit(0);
    }
    // 写数据
    for(int i = 0; i < 100; i++) {
        char buf[1024];
        sprintf(buf, "hello, %d\n", i);
        printf("write data : %s\n", buf);
        write(fd, buf, strlen(buf));
        sleep(1);
    }
    close(fd);
    return 0;
}

read.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

// 从管道中读取数据
int main() {
    // 1.打开管道文件
    int fd = open("test", O_RDONLY);
    if(fd == -1) {
        perror("open");
        exit(0);
    }
    // 读数据
    while(1) {
        char buf[1024] = {0};
        int len = read(fd, buf, sizeof(buf));
        if(len == 0) {
            printf("写端断开连接了...\n");
            break;
        }
        printf("recv buf : %s\n", buf);
    }
    close(fd);
    return 0;
}
01.2 / 有名管道的注意事项:
    1.一个为只读而打开一个管道的进程会阻塞,直到另外一个进程为只写打开管道
    2.一个为只写而打开一个管道的进程会阻塞,直到另外一个进程为只读打开管道

读管道:
    管道中有数据,read返回实际读到的字节数
    管道中无数据:
        管道写端被全部关闭,read返回0,(相当于读到文件末尾)
        写端没有全部被关闭,read阻塞等待

写管道:
    管道读端被全部关闭,进行异常终止(收到一个SIGPIPE信号)
    管道读端没有全部关闭:
        管道已经满了,write会阻塞
        管道没有满,write将数据写入,并返回实际写入的字节数。
02 / 有名管道的使用

◼ 通过命令创建有名管道

mkfifo 名字

◼ 通过函数创建有名管道

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);

◼ 一旦使用 mkfifo 创建了一个 FIFO,就可以使用 open 打开它,常见的文件

I/O 函数都可用于 fifo。如:close、read、write、unlink 等。

◼ FIFO 严格遵循先进先出(First in First out),对管道及 FIFO 的读总是从开始处返回数据,对它们的写则把数据添加到末尾。它们不支持诸如 lseek() 等文件定位操作。

02.1 / 使用有名管道完成聊天的功能

图解

在这里插入图片描述

代码

chatA.c

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>

int main(){

    // 1.判断有名管道文件是否存在
    int ret = access("fifo1", F_OK);
    if(ret == -1) {
        // 文件不存在
        printf("管道不存在,创建对应的有名管道\n");
        ret = mkfifo("fifo1", 0664);
        if(ret == -1) {
            perror("mkfifo");
            exit(0);
        }
    }

    ret = access("fifo2", F_OK);
    if(ret == -1) {
        // 文件不存在
        printf("管道不存在,创建对应的有名管道\n");
        ret = mkfifo("fifo2", 0664);
        if(ret == -1) {
            perror("mkfifo");
            exit(0);
        }
    }

    // 2.以只写的方式打开管道fifo1
    int fdw = open("fifo1", O_WRONLY);
    if(fdw == -1) {
        perror("open");
        exit(0);
    }
    printf("打开管道fifo1成功,等待写入...\n");

    // 3.以只读的方式打开管道fifo2
    int fdr = open("fifo2", O_RDONLY);
    if(fdr == -1) {
        perror("open");
        exit(0);
    }
    printf("打开管道fifo2成功,等待读取...\n");

    char buf[128];
    // 4.循环的写读数据
    while(1) {
        memset(buf, 0, 128);
        // 获取标准输入的数据
        fgets(buf, 128, stdin);
        // 写数据
        ret = write(fdw, buf, strlen(buf));
        if(ret == -1) {
            perror("write");
            exit(0);
        }

        // 5.读管道数据
        memset(buf, 0, 128);
        ret = read(fdr, buf, 128);
        if(ret <= 0) {
            perror("read");
            break;
        }
        printf("buf: %s\n", buf);
    }

    // 6.关闭文件描述符
    close(fdr);
    close(fdw);

    return 0;
}

chatB.c

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>

int main() {

    // 1.判断有名管道文件是否存在
    int ret = access("fifo1", F_OK);
    if(ret == -1) {
        // 文件不存在
        printf("管道不存在,创建对应的有名管道\n");
        ret = mkfifo("fifo1", 0664);
        if(ret == -1) {
            perror("mkfifo");
            exit(0);
        }
    }

    ret = access("fifo2", F_OK);
    if(ret == -1) {
        // 文件不存在
        printf("管道不存在,创建对应的有名管道\n");
        ret = mkfifo("fifo2", 0664);
        if(ret == -1) {
            perror("mkfifo");
            exit(0);
        }
    }

    // 2.以只读的方式打开管道fifo1
    int fdr = open("fifo1", O_RDONLY);
    if(fdr == -1) {
        perror("open");
        exit(0);
    }
    printf("打开管道fifo1成功,等待读取...\n");
    // 3.以只写的方式打开管道fifo2
    int fdw = open("fifo2", O_WRONLY);
    if(fdw == -1) {
        perror("open");
        exit(0);
    }
    printf("打开管道fifo2成功,等待写入...\n");

    char buf[128];

    // 4.循环的读写数据
    while(1) {
        // 5.读管道数据
        memset(buf, 0, 128);
        ret = read(fdr, buf, 128);
        if(ret <= 0) {
            perror("read");
            break;
        }
        printf("buf: %s\n", buf);

        memset(buf, 0, 128);
        // 获取标准输入的数据
        fgets(buf, 128, stdin);
        // 写数据
        ret = write(fdw, buf, strlen(buf));
        if(ret == -1) {
            perror("write");
            exit(0);
        }
    }

    // 6.关闭文件描述符
    close(fdr);
    close(fdw);

    return 0;
}

成果截图
在这里插入图片描述
在这里插入图片描述

02.2 / 上面的只能一发一收,所以咱们用父子进程来将他们包装一下,这样就可以实现简易的聊天功能

chat1.c

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/wait.h>

int main(){
    // 1.判断有名管道文件是否存在
    int ret = access("chat1", F_OK);
    if(ret == -1) {
        // 文件不存在
        printf("管道不存在,创建对应的有名管道\n");
        ret = mkfifo("chat1", 0664);
        if(ret == -1) {
            perror("mkfifo");
            exit(0);
        }
    }

    ret = access("chat2", F_OK);
    if(ret == -1) {
        // 文件不存在
        printf("管道不存在,创建对应的有名管道\n");
        ret = mkfifo("chat2", 0664);
        if(ret == -1) {
            perror("mkfifo");
            exit(0);
        }
    }
    // 2.以只写的方式打开管道chat1
    int fdw = open("chat1", O_WRONLY);
    if(fdw == -1) {
        perror("open");
        exit(0);
    }
    printf("打开管道chat1成功,等待写入...\n");

    // 3.以只读的方式打开管道chat2
    int fdr = open("chat2", O_RDONLY);
    if(fdr == -1) {
        perror("open");
        exit(0);
    }
    printf("打开管道chat2成功,等待读取...\n");


    pid_t pid = fork();
    //父进程写chat1
    if(pid > 0){
        char buf[128] = { 0 };
        while(1) {
            memset(buf, 0, 128);
            // 获取标准输入的数据
            fgets(buf, 128, stdin);
            // 写数据
            ret = write(fdw, buf, strlen(buf));
            if(ret == -1) {
                perror("write");
                exit(0);
            } 
        }
        wait(NULL);  
    }
    //子进程,读chat2
    else if(pid == 0){
        char buf[128];
        while (1)
        {
            // 5.读管道数据
            memset(buf, 0, 128);
            ret = read(fdr, buf, 128);
            if(ret <= 0) {
                perror("read");
                break;
            }
            printf("bufB: %s\n", buf);
        }  
    }

    close(fdr);
    close(fdw);

    return 0;
}

chat2.c

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/wait.h>

int main(){
    // 1.判断有名管道文件是否存在
    int ret = access("chat1", F_OK);
    if(ret == -1) {
        // 文件不存在
        printf("管道不存在,创建对应的有名管道\n");
        ret = mkfifo("chat1", 0664);
        if(ret == -1) {
            perror("mkfifo");
            exit(0);
        }
    }

    ret = access("chat2", F_OK);
    if(ret == -1) {
        // 文件不存在
        printf("管道不存在,创建对应的有名管道\n");
        ret = mkfifo("chat2", 0664);
        if(ret == -1) {
            perror("mkfifo");
            exit(0);
        }
    }
    // 2.以只读的方式打开管道chat1
    int fdr = open("chat1", O_RDONLY);
    if(fdr == -1) {
        perror("open");
        exit(0);
    }
    printf("打开管道chat1成功,等待写入...\n");

    // 3.以只写的方式打开管道chat2
    int fdw = open("chat2", O_WRONLY);
    if(fdw == -1) {
        perror("open");
        exit(0);
    }
    printf("打开管道chatA2成功,等待读取...\n");

    pid_t pid = fork();
    //父进程读chat1
    if(pid > 0){
        char buf[128];
        while (1)
        {
            // 5.读管道数据
            memset(buf, 0, 128);
            ret = read(fdr, buf, 128);
            if(ret <= 0) {
                perror("read");
                break;
            }
            printf("bufA: %s\n", buf);
        }
        wait(NULL);  
    }
    //子进程写chat2
    else if(pid == 0){
        char buf[128] = { 0 };
        while(1) {
            memset(buf, 0, 128);
            // 获取标准输入的数据
            fgets(buf, 128, stdin);
            // 写数据
            ret = write(fdw, buf, strlen(buf));
            if(ret == -1) {
                perror("write");
                exit(0);
            } 
        }
    }
    close(fdr);
    close(fdw);   
    return 0;
}

成果截图

在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

千北@

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值