Linux进程间通信通常使用的方式有很多种,其中比较常用的包括管道(pipe)和 FIFO(命名管道)。本文将介绍这两种通信方式的基本概念,并用C语言编写示例代码,来说明如何在两个进程之间使用这些IPC机制进行通信。

管道(pipe)

管道是一种半双工的通信方式,用于父进程和子进程之间的通信。在 Linux 中,管道是一种特殊的文件,有两个端点,一个读端和一个写端。管道的基本操作包括创建管道、关闭文件描述符、读取数据和写入数据等。

创建管道

在 Linux 中,我们可以使用 pipe() 系统调用来创建管道。pipe() 函数的原型如下:

#include <unistd.h>
int pipe(int pipefd[2]);

其中,pipefd 是一个数组,用于存储管道的读端和写端的文件描述符。pipe() 函数成功时返回 0,失败时返回 -1。

下面是一个创建管道的示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
    int pipefd[2];
    
    // 创建管道
    if (pipe(pipefd) == -1) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }
    
    printf("读端文件描述符:%d\n", pipefd[0]);
    printf("写端文件描述符:%d\n", pipefd[1]);
    
    exit(EXIT_SUCCESS);
}

  • 编译并运行,打印如下
读端文件描述符:3
写端文件描述符:4

管道的读写

在使用管道进行通信时,父进程和子进程可以通过管道进行数据的读取和写入。在 C 语言中,我们可以使用 read()函数和 write() 函数来读取和写入数据。read() 函数用于从管道中读取数据,write() 函数用于向管道中写入数据,使用 close() 函数关闭文件描述符。在管道的使用中,我们应该在不需要的时候关闭管道的读端和写端,以避免资源浪费。

这三个函数的原型分别如下:

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
int close(int fd);

下面是一个父进程向子进程写入数据并读取返回结果的示例代码:

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

#define BUF_SIZE 1024

int main()
{
    int pipefd[2];
    pid_t pid;
    char buf[BUF_SIZE];
    int status;
    
    // 创建管道
    if (pipe(pipefd) == -1) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }
    
    // 创建子进程
    pid = fork();
    if (pid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    }
    
    if (pid == 0) { // 子进程        
        // 从管道中读取数据
        if (read(pipefd[0], buf, BUF_SIZE) == -1) {
            perror("read");
            exit(EXIT_FAILURE);
        }
        
        printf("子进程收到消息:%s\n", buf);
        
        // 发送消息给父进程
        const char* message = "Hello, parent!";
        if (write(pipefd[1], message, strlen(message) + 1) == -1) {
            perror("write");
            exit(EXIT_FAILURE);
        }
        close(pipefd[1]); // 关闭写端
        exit(EXIT_SUCCESS);
    } else { // 父进程 
        // 发送消息给子进程
        const char* message = "Hello, child!";
        if (write(pipefd[1], message, strlen(message) + 1) == -1) {
            perror("write");
            exit(EXIT_FAILURE);
        }
        
        // 等待子进程退出
        wait(&status);
        if (WIFEXITED(status)) {
            printf("子进程退出,返回值:%d\n", WEXITSTATUS(status));
        }
        
        // 从管道中读取数据
        if (read(pipefd[0], buf, BUF_SIZE) == -1) {
            perror("read");
            exit(EXIT_FAILURE);
        }
        printf("父进程收到消息:%s\n", buf);
        close(pipefd[0]); // 关闭读端
        
        exit(EXIT_SUCCESS);
    }
}

在这个示例代码中,父进程先向子进程发送一条消息,子进程收到消息后向父进程发送一条消息,并退出。父进程在等待子进程退出后再从管道中读取子进程发送的消息。

  • 编译并运行,打印如下
子进程收到消息:Hello, child!
子进程退出,返回值:0
父进程收到消息:Hello, parent!

FIFO(命名管道)

FIFO(命名管道)是一种文件系统对象,与管道类似,也可以用于进程间通信。FIFO 是一种特殊类型的文件,它可以在文件系统中被创建,并且进程可以通过文件描述符来读取和写入数据。

与管道不同的是,FIFO 可以被多个进程打开,并且可以在文件系统中以路径的形式存在,因此不像管道那样只能在具有亲缘关系的进程之间使用。任何进程只要有相应的权限就可以打开 FIFO 并进行通信。

FIFO 的创建和使用

FIFO 的创建和使用也比较简单。首先需要使用 mkfifo() 函数创建 FIFO 文件,其原型如下

#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);

其中,pathname 是 FIFO 文件的路径名,mode 是文件的权限。

创建 FIFO 文件后,就可以像使用普通文件一样打开它,并使用 read() 和 write() 函数进行数据的读写。

下面是一个使用 FIFO 进行进程间通信的示例代码:

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

#define FIFO_PATH "/tmp/myfifo"
#define BUF_SIZE 1024

int main()
{
    int fd;
    char buf[BUF_SIZE];
    
    // 创建 FIFO 文件
    if (mkfifo(FIFO_PATH, 0666) == -1) {
        perror("mkfifo");
        exit(EXIT_FAILURE);
    }
    
    // 打开 FIFO 文件
    fd = open(FIFO_PATH, O_RDWR);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }
    
    // 向 FIFO 中写入数据
    const char* message = "Hello, world!";
    if (write(fd, message, strlen(message) + 1) == -1) {
        perror("write");
        exit(EXIT_FAILURE);
    }
    
    // 从 FIFO 中读取数据
    if (read(fd, buf, BUF_SIZE) == -1) {
        perror("read");
        exit(EXIT_FAILURE);
    }
    
    printf("收到消息:%s\n", buf);
    
    // 关闭文件描述符并删除 FIFO 文件
    close(fd);
    if (unlink(FIFO_PATH) == -1) {
        perror("unlink");
        exit(EXIT_FAILURE);
    }
    
    exit(EXIT_SUCCESS);
}

在这个示例代码中,程序先创建了一个 FIFO 文件 /tmp/myfifo,然后打开该文件并向其中写入一条消息。接下来从 FIFO 文件中读取数据,并将其打印出来。最后关闭文件描述符并删除 FIFO 文件。

  • 编译并运行,打印如下
收到消息:Hello, world!

小结

Linux 中管道和 FIFO 是进程间通信的重要方式。管道只能用于亲缘关系的进程间通信,而 FIFO 可以被多个进程打开,不受进程之间关系的限制。无论是管道还是 FIFO,它们的使用方式都与普通文件类似,需要使用文件描述符和 read()、write() 函数来进行数据的读写。

  1. 管道和 FIFO 只能用于同一主机上的进程间通信,不能用于跨主机通信。
  2. 管道和 FIFO 的读写操作是阻塞的,这意味着当一个进程尝试从一个空管道或 FIFO 中读取数据时,它会被阻塞,直到有数据可用为止。同样,当一个进程尝试将数据写入一个满的管道或 FIFO 时,它也会被阻塞,直到有空闲空间为止。
  3. 在使用管道和 FIFO 进行进程间通信时,需要注意文件描述符的关闭顺序。
  4. 管道和 FIFO 只能传输字节流,不能传输其他类型的数据,如结构体或指针。
  5. 如果使用管道或 FIFO 进行进程间通信时,数据量较大,需要进行分段传输,否则可能会导致阻塞或缓冲区溢出等问题。
  6. 管道和 FIFO 都是单向的,如果需要双向通信,则需要建立两个管道或 FIFO。

以上,如果觉得对你有帮助,点个赞再走吧,这样@知微之见也有更新下去的动力!

也欢迎私信我,一起交流!

作者:|知微之见|,原文链接: http://www.imooc.com/article/334568

文章推荐

Kafka的系统架构和API开发

单例模式(Singleton Pattern)

关于在 springboot 中使用 @Autowired 注解来对 TemplateEng...

ray-分布式计算框架-集群与异步Job管理

非关系型数据库---Redis安装与基本使用

Java GenericObjectPool 对象池化技术--SpringBoot sftp 连...

Docker-web项目部署

从JQuery出发总结的关于原型使用上的一些浅薄理解

CSAPP - BombLab

Spring AOP快速使用教程

关于线段树基础

基于SqlSugar的开发框架循序渐进介绍(6)-- 在基类接口中注...