使用管道完成进程间通信(匿名管道pipe、命名管道fifo)

转载自:https://blog.csdn.net/qq_36829091/article/details/80138836

每一个进程来说这个进程看到属于它的一块内存资源,这块资源是它所独占的,所以进程之间的通信就会比较麻烦,原理就是需要让不同的进程间能够看到一份公共的资源。所以交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间 拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信。一般我们采用的进程间通信方式有:

  1. 管道(pipe)和有名管道(FIFO)
  2. 信号(signal)
  3. 消息队列
  4. 共享内存
  5. 信号量
  6. 套接字(socket)

我们先来从最简单的通信方式来说起。

匿名管道

也简称管道,

管道的创建

管道是一种最基本的进程间通信机制。管道由pipe函数来创建:

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

调用pipe函数,会在内核中开辟出一块缓冲区用来进行进程间通信,这块缓冲区称为管道,它有一个读端和一个写端。

pipe函数接受一个参数,是包含两个整数的数组,如果调用成功,会通过pipefd[2]传出给用户程序两个文件描述符,需要注意pipefd [0]指向管道的读端, pipefd [1]指向管道的写端,那么此时这个管道对于用户程序就是一个文件,可以通过read(pipefd [0]);或者write(pipefd [1])进行操作。pipe函数调用成功返回0,否则返回-1..

那么再来看看通过管道进行通信的步骤:

1.父进程创建管道,得到两个文件描述符指向管道的两端

 

 

 2. 利用fork函数创建子进程,则子进程也得到两个文件描述符指向同一管道

 

 

 3. 父进程关闭读端(pipe[0]),子进程关闭写端(pipe[1]),则此时父进程可以往管道中进行写操作,子进程可以从管道中读,从而实现了通过管道的进程间通信。

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

int main()
{
    int _pipe[2];
    int ret = pipe(_pipe);
    if(ret < 0)
    {
        perror("pipe\n");
    }

    pid_t id = fork();
    if(id < 0)
    {
        perror("fork\n");
    }
    else if(id == 0)
    {
        close(_pipe[0]);
        int i = 0;
        char* msg = NULL;
        while(i < 100)
        {
            msg = "I am child";
            write(_pipe[1], msg, strlen(msg));
            sleep(1);
            ++i;
        }
    }
    else
    {
        close(_pipe[1]);
        int i = 0;
        char msg[100];
        while(i < 100)
        {
            memset(msg, '\0', sizeof(msg));
            read(_pipe[0], msg, sizeof(msg));
            printf("%s\n", msg);
            ++i;
        }
    }
    return 0;
}

pipe的特点:

1. 只能单向通信

2. 只能血缘关系的进程进行通信(父子进程、兄弟进程)

3. 依赖于文件系统

4. 生命周期随进程(在内存中,进程结束被释放)

5. 面向字节流的服务

6. 管道内部提供了同步机制(锁、等待队列、信号)

说明:因为管道通信是单向的,在上面的例子中我们是通过子进程写父进程来读,如果想要同时父进程写而子进程来读,就需要再打开另外的管道;

管道的读写端通过打开的文件描述符来传递,因此要通信的两个进程必须从它们的公共祖先那里继承管道的件描述符。 上面的例子是父进程把文件描述符传给子进程之后父子进程之 间通信,也可以父进程fork两次,把文件描述符传给两个子进程,然后两个子进程之间通信, 总之 需要通过fork传递文件描述符使两个进程都能访问同一管道,它们才能通信。

命名管道

在管道中,只有具有血缘关系的进程才能进行通信,对于后来的命名管道,就解决了这个问题。FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存储于文件系统中。命名管道是一个设备文件,因此,即使进程与创建FIFO的进程不存在亲缘关系,只要可以访问该路径,就能够通过FIFO相互通信。值得注意的是, FIFO(first input first output)总是按照先进先出的原则工作,第一个被写入的数据将首先从管道中读出。

命名管道的创建

创建命名管道的系统函数有两个: mknod和mkfifo。两个函数均定义在头文件sys/stat.h,

函数原型如下:

#include <sys/types.h>
#include <sys/stat.h>
int mknod(const char *path,mode_t mod,dev_t dev);
int mkfifo(const char *path,mode_t mode);

参数中path为创建的命名管道的全路径名: mod为创建的命名管道的指明其存取权限;

运行示例:

那么此时我们早server.c中创建命名管道并打开,对管道中进行写操作,在client.c中进行读操作,把读到的内容进行打印,就实现了我们的使用命名管道通信。

/*Server*/

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#define _PATH_NAME_ "/tmp/file.tmp"
#define _SIZE_  100

int main()
{
    int ret = mkfifo(_PATH_NAME_, S_IFIFO | 0666);
    if(ret == -1)
    {
        printf("make file error\n");
        return -1;
    }
    char buf[_SIZE_];
    memset(buf, '\0', sizeof(buf));
    int fd = open(_PATH_NAME_, O_WRONLY);
    while(true)
    {
        fget(buf, sizeof(buf)-1, stdin);
        int ret = write(fd, buf, strlen(buf)+1);
        if(ret < 0)
        {
            printf("write error\n");
            break;
        }
    }
    close(fd);
    return 0;
}
/*Client*/

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#define _PATH_NAME_ "/tmp/file.tmp"
#define _SIZE_  100

int main()
{
    int fd = open(_PATH_NAME_, O_RDONLY);
    if(fd < 0)
    {
        printf("open file error\n");
        return 1;
    }
    char buf[_SIZE_];
    memset(buf, '\0', sizeof(buf));
    while(true)
    {
        int ret = read(fd, buf, sizeof(buf));
        if(ret < 0)
        {
            printf("read error\n");
            break;
        }
        printf("%s\n", buf);
    }
    close(fd);
    return 0;
}
posted @ 2020-04-14 11:04  Rogn  阅读(2411)  评论(1编辑  收藏  举报