进程间通信——有名管道

有名管道

匿名管道 由于没有名字,只能用于亲缘关系的进程间通信。因此提出了有名管道(FIFO),也叫命名管道或FIFO文件。
有名管道(FIFO) 提供了一个路径名字与之关联,以 FIFO 的文件形式存在于文件系统中,其打开方式与打开一个普通文件是一样的,即使与该 FIFO 的创建进程没有亲缘关系,只要能够访问该路径名就能够通过 FIFO 进行通信。
一旦打开了 FIFO 就可以使用和匿名管道或其他文件系统一样的 I/O 操作(read、write、close 等)了,FIFO 也有写入端和读出端且读出的顺序和写入的顺序是一样的,因此称为 先入先出
有名管道和匿名管道的区别:
1. FIFO 在文件系统中作为一个特殊的文件存在,但是 FIFO 的内容却在内存中。
2. 当使用 FIFO 的进程退出后,FIFO 文件将继续保存在文件系统中以便后续使用。
3. FIFO 有名字,没有亲缘关系的进程可以通过打开有名管道来进行通信。

有名管道的使用

  1. 通过命令创建有名管道:mkfifo [名字]
  2. 通过函数创建有名管道:
    #include <sys/types.h>
    #include <sys/stat.h>
    int mkfifo(const char* pPathName, mode_t Mode);
    
    参数:
    (1). pPathName:管道路径名字
    (2). Mode:管道文件的权限,和 open 一个文件中的 mode 一样,是一个八进制数。
    返回值: 成功返回0,失败返回-1并设置错误码。

一旦使用 mkfifo 创建了一个 FIFO,就可以使用常见的 I/O 操作函数(open、close、read、write 等)。
FIFO 遵循先进先出,对管道和 FIFO 的读总是从开始处返回数据,对他们的写则是添加数据到末尾,他们不支持 lseek 等文件定位操作。

两种方式创建有名管道:

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

int main()
{
    int iRet = 0;
    // 判断文件是否存在
    iRet = access("fifo_1", F_OK);
    if (iRet == -1) {
        puts("管道不存在,创建管道\n");
        iRet = mkfifo("fifo_1", 0664);
        if (iRet == -1) {
            perror("mkfifo");
            exit(0);
        }
    }
    return 0;
}

alt text

一个有名管道的读写示例:

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

//写数据
int main()
{
    //判断文件是否存在
    int ret = access("test", F_OK);
    if (ret == -1) {
        printf("管道不存在,创建管道\n");
        ret = mkfifo("test", 0664);
        if (ret == -1) {
            perror("mkfifo");
            exit(0);
        }
    }
    //以只写的方式打开管道
    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 <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

//读数据
int main()
{
    //以只读的方式打开管道
    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 data: %s\n", buf);
    }
    close(fd);
    return 0;
}

有名管道的注意事项

  1. 一个为只读打开管道的进程会阻塞,直到有另外一个进程可写的打开管道,只运行 write.c 会阻塞。
  2. 一个为只写打开管道的进程会阻塞,知道有另外一个进程可读的打开管道,只运行 read.c 会阻塞。

读写管道的特点:

  1. 读管道:
    1. 管道中有数据:read 返回实际读到的数据字节数。
    2. 管道中无数据:
      1. 没有打开的管道写端:read 返回 0 (相当于读到文件尾)。
      2. 有打开的管道写端:read 阻塞等待。
  2. 写管道:
    1. 没有打开的管道读端:进程异常终止(收到一个 SIGPIPE 信号)。
    2. 有打开的管道读端:
      1. 管道已满:write 会阻塞
      2. 管道没满:write 会写入数据,返回实际写入的数据字节数。
posted @ 2025-05-08 08:12  luckilzy  阅读(19)  评论(0)    收藏  举报