linux进程间通信--管道

2021-02-28

管道分为有名管道和无名管道

无名管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系一般指的是父子关系。无明管道一般用于两个不同进程之间的通信。当一个进程创建了一个管道,并调用fork创建自己的一个子进程后,父进程关闭读管道端,子进程关闭写管道端,这样提供了两个进程之间数据流动的一种方式。

有名管道也是一种半双工的通信方式,但是它允许无亲缘关系进程间的通信。

无名管道

创建

#include <unistd.h>

int pipe(int fd[2]);

参数:

fd参数返回两个 文件描述符

fd[1]的输出是fd[0]的输入

返回值

管道如何实现进程间的通信

  1. 父进程创建管道,得到两个件描述符指向管道的两端
  2. 父进程fork出子进程,子进程也有两个文件描述符指向同管道。
  3. 父进程关闭fd[0],子进程关闭fd[1],即子进程关闭管道读端,父进程关闭管道写端(因为管道只支持单向通信)。子进程可以往管道中写,父进程可以从管道中读,管道是由环形队列实现的,数据从写端流入从读端流出,这样就实现了进程间通信。

代码实现

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

int main()
{
    int fd[2];
    int ret = pipe(fd);

    if(ret == -1)
    {
        perror("pipe error");
        return -1;
    }
    printf("main process:%d\n", getpid());

    pid_t id = fork();

    if(id == 0)
    {
        int i = 0;
        close(fd[0]);
        printf("child process:%d\n", getpid());
        char *child = "i am child!";
        while(i<5)
        {
            write(fd[1], child, strlen(child)+1);
            sleep(2);
            i++;
        }
    }
    else if(id > 0)
    {
        close(fd[1]);
        char msg[100];
        int j = 0;
        printf("father process:%d\n", getpid());
        while(j < 5)
        {
            memset(msg, '\0', sizeof(msg));
            ssize_t s = read(fd[0], msg, sizeof(msg));
            if(s > 0)
            {
                msg[s-1] = '\0';
            }
            printf("%s\n", msg);
            j++;
        }
    }
    else
    {
        perror("fork error\n");
        return -1;
    }
    return 0;
}

执行结果: 每隔2秒打印一次i am child,共打印5次

$ ./a.out 
main process:844
father process:844
child process:845
i am child!
i am child!
i am child!
i am child!
i am child!

管道大的一些问题

  1. 读端不读,写端一直写。写端会一直写下去,写满后会导致阻塞,直到有空闲空间才能再次写入。
  2. 读端一直读,写端不写。管道中的剩余数据读完后,会等到管道中有数据读取时才会继续执行read
  3. 子进程一直在写,父进程读过一会关闭,导致写端的进程收到一个SIGPIPE信号,导致进程终止,只写不读会导致read返回0,就像读到文件末尾一样。

管道的特点

1.管道只允许具有血缘关系的进程间通信,如父子进程间的通信。 2.管道只允许单向通信。 3.管道内部保证同步机制,从而保证访问数据的一致性。 4.面向字节流 5.管道随进程,进程在管道在,进程消失管道对应的端口也关闭,两个进程都消失管道也消失。

有名管道

命名管道在底层的实现跟匿名管道完全一致,区别只是命名管道会有一个全局可见的文件名以供别人open打开使用。再程序中创建一个命名管道文件的方法有两种,一种是使用mkfifo函数,另一种是使用mknod系统调用。

mkfifo

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

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

参数:

返回值:

例子

读端代码

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

int main()
{
    int fd = 0;
    fd = mkfifo("./fifo", 0600);
    if(fd < 0) {
        perror("mkfifo error");
        //return -1;
    }
    fd = open("./fifo", O_RDONLY);

    char msg[200] = {0};
    while(1)
    {
        memset(msg, 0, sizeof(msg));

        read(fd, msg, sizeof(msg));
        printf("msg:%s\n", msg);
        sleep(2);
    }
    return 0;
}

写端代码

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

int main()
{
    int fd = 0;
    fd = mkfifo("./fifo", 0600);
    if(fd < 0) {
        perror("mkfifo error");
        //return -1;
    }
    fd = open("./fifo", O_WRONLY);

    char msg[200] = {0};
    while(1)
    {
        memset(msg, 0, sizeof(msg));

        sprintf(msg, "abcdefg");
        write(fd, msg, sizeof(msg));
        printf("msg:%s\n", msg);
        sleep(2);
    }
    return 0;
}

mknod

用于创建文件

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

int mknod(const char *pathname, mode_t mode, dev_t dev);

参数:

该函数最主要的用途就是创建FIFO文件,比如你的代码:

mknod(FIFO_FILE,S_IFIFO|0666,0);

FIFO_FILE是一个字符指针,指向文件名,S_IFIFO表示要创建一个FIFO文件,0666表示该文件的权限是所有人可读可写,0表示该文件不是一个设备文件。

words: 1363 tags: linux进程间通信 linux c