进程间通信之管道

Posted by KalosAner on January 19, 2025

一、引言

进程是内存分配的最小单位,所以每个进程之间的数据都是隔离的,但是有时候进程之间又需要通信,这时候就需要用到进程通信技术。

进程之间通信主要有几种技术:管道、消息队列、共享内存、信号量、信号、套接字、内存映射和远程过程调用。

二、管道

管道分为有名管道和无名管道。管道都是半双工通信,如果需要实现全双工通信需要使用两个管道。

shell 中的 ls -l | grep string 中的 | 其实也是一种管道。

2.1 无名管道

无名管道主要用于具有亲缘关系的进程之间传递消息。无名管道实质上是内核的一块缓存。

函数原型:

1
2
3
4
#include <unistd.h>

// filedes[0] 和 filedes[1] 分别是管道的两端。
int pipe(int filedes[2]);

无名管道通常使用 fork 函数传递。

代码:

pipe3.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
#include <unistd.h>
#define BUF_SIZE 30

int main(int argc, char *argv[]) {
    int fds1[2], fds2[2];
    char str1[] = "Who are you?";
    char str2[] = "Thank you for your message";
    char buf[BUF_SIZE];
    pid_t pid;
    
    pipe(fds1), pipe(fds2);
    pid = fork();
    if (pid == 0) {
        write(fds1[1], str1, sizeof(str1));
        read(fds2[0], buf, BUF_SIZE);
        printf("Child proc output: %s \n", buf);
    } else {
        read(fds1[0], buf, BUF_SIZE);
        printf("Parent proc output: %s \n", buf);
        write(fds2[1], str2, sizeof(str2));
        sleep(3);
    }
    return 0;
}

2.2 命名管道

命名管道是一个文件,遵循先进先出原则。

函数原型:

1
2
3
4
5
#include <sys/types.h>
#include <sys/stat.h>

// path 是创建的管道全名(包括路径),mode 是管道的读写权限,成功返回 0, 失败返回 -1
int mkfifo(const char *path, mode_t mode);

命名管道和无名管道的使用方法法基本是相同的。只是使用命名管道时,必须先调用open()将其打开。因为命名管道是一个存在于硬盘上的文件,而无名管道是存在于内存中的特殊文件。

但是调用open()打开命名管道的进程可能会被阻塞。

  • 但如果同时用读写方式( O_RDWR)打开,则一定不会导致阻塞;
  • 如果以只读方式( O_RDONLY)打开,则调用open()函数的进程将会被阻塞直到有写方打开管道;
  • 同样以写方式( O_WRONLY)打开也会阻塞直到有读方式打开管道。

代码:

server.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>

int main()
{
    umask(0);//将权限清0
    if (mkfifo("./mypipe",0666|S_IFIFO) < 0) {//创建管道
        perror("mkfifo");
        return 1;
    }

    int fd = open("./mypipe",O_RDONLY);//打开管道
    if(fd < 0){
        perror("open");
        return 2;
    }

    char buf[1024];
    while (true) {
        buf[0] = 0;
        printf("wait...\n");
        ssize_t s = read(fd,buf,sizeof(buf)-1);

        if (s > 0) {
            buf[s-1] = 0;//过滤\n
            printf("server: %s\n",buf);
        } else if (s == 0) {//当客户端退出时,read返回0
            printf("The client exit.\n");
            break;
        }
    }
    close(fd);
    return 0;
}

client.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
int main()
{

    int fd = open("./mypipe",O_WRONLY);//打开管道
    if (fd < 0) {
        perror("open");
        return 1;
    }

    char buf[1024];
    while (true) {
        printf("Client: ");
        fflush(stdout);
        ssize_t s = read(0,buf,sizeof(buf)-1);//向管道文件中写数据
        if (s > 0) {
            buf[s] = 0;//以字符串的形式写
            write(fd,buf,strlen(buf));
        }
    }
    close(fd);
    return 0;
}

代码中使用了固定的命名管道的名字,也可以通过传参的方式指定命名管道的名字。