进程通信(一) 无名管道和有名管道

时间:2021-08-19 15:12:39

进程通信 :进程通信是指进程之间的信息交换。

《王道考研复习指导》
管道通信是消息传递的一种特殊方式。所谓“管道”,是指用于连接一个读进程和一个写进程以实现它们之间通信的一个共享文件,又名pipe文件。向管道(共享文件)提供输入的发送进程(即写进程),以字符流的形式将大量的数据送入(写)管道;而接受管道输出的接受进程(即读进程),则从管道接受(读)数据。为了协调双方的通信,管道机制必须提供一下三个方面的协调能力:互斥、同步和确定对方存在。
下面以linux的管道为例进行说明。在linux中,管道是一种频繁使用的通信机制。从本质上讲,管道也是一种文件,但它又和一般的文件有所不同,管道可以克服使用文件通信的两个问题,具体表现为:
1)限制管道的大小。实际上,管道是一个固定大小的缓冲区。在Linux中,该缓冲区的大小为4KB,使得它不像文件那样不加检验的增长。使用单个固定缓冲区也会带来问题,比如在写管道时可能变满,当这种情况发生时,随后对写管道的write()调用将默认的阻塞,等待某些数据被读取,以便腾出足够的空间供write()调用写。
2)读进程也可能工作的比写进程快。当所有当前进程数据已被读走时,管道变空。当这种情况发生时,一个随后的read()调用将默认设置为阻塞,等待某些数据被写入,这解决了read()调用返回文件结束的问题。
注意 :从管道读数据是一次性操作,数据一旦被读走,它就从管道中被抛弃,释放空间以便写更多的数据。管道只能采用半双工通信,即在某一时刻只能单向传输。要实现父子进程双方互动,需要定义两个管道。

在LINUX操作下使用 ulimit -p命令可以查看默认管道的大小。

1.无名管道(匿名管道)

函数原型: int pipe(int pipefd[2])

参数: 文件描述符数组,其中fd[0]代表读端fd[1]代表写端。即管道的两端,注意fd是传出参数。当程序中调用pipe函数,操作系统会创建内核缓冲区,fd作为传出参数,这样才 可以对内核缓冲区进行操控。

返回值: 如果匿名缓冲区被成功创建,返回值为0;否则,返回-1,errno全局变量被设置为相应的错误。

适用范围: 有血缘关系的进程间通信,如父子进程,兄弟进程。

下面举例分析如何使用无名管道在父子进程间进行通信:
进程通信(一) 无名管道和有名管道

前面已经讲过,由于管道是半双工通信,即在某一时=时刻只能单向传输。

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

int main()
{
    int fd[2];    //创建文件描述符数组,fd[0]对应管道读端,fd[1]对应管道写端
    int res = pipe(fd);//fd作为传出参数,以便对内核缓冲区进行操作
    aasert(-1 != res);
    pid_t pid = fork();//创建子进程
    if(-1 == pid)
    {
        perror("fork()");
        exit(0);
    }
    //子进程
    if(pid == 0)
    {
        close(fd[0]);//关闭读端
        write(fd[1],"hello",5);//向内核缓冲区写入字符串hello
    }
    else
    {
        close(fd[1]);//关闭写端
        char buf[128] = {0};
        read(fd[0],buf,sizeof(buf));//将内核缓冲区的内容写入buf中
        printf("%s \n",buf);//将写入buf中的内容输出到屏幕上
    }
}
gcc pipe.c -o pipe
./pipe
hello

进程通信(一) 无名管道和有名管道
可以看出,使用匿名管道完成了父子进程的通信,子进程作为写进程输入信息hello,父进程作为读进程读取信息并输出到屏幕上。

2.有名管道

由于无名管道的局限性,仅限于有血缘关系的进程间通信,所以当需要在不同进程(无血缘关系的进程)之间通信,pipe就不能被使用了。取而代之是有名管道(fifo)。
特点:
(1)在磁盘上有这样一个文件,使用ls -l命令可以查看管道文件的文件类型为p。
(2)伪文件,其大小永远为0。
(3)在内核中有一个对应的缓冲区。
(4)半双工的通信方式
进程通信(一) 无名管道和有名管道

使用场景
(1)没有血缘关系的进程间通信

创建方式
(1)mkfifo 管道名
进程通信(一) 无名管道和有名管道
(2)调用函数 mkfifo
函数原型: int mkfifo(const char *pathname,mode_t mode);
参数:路径 权限

下面举例应用mkfifo实现两个无血缘关系进程之间的通信
简单描述:在A进程中向内核缓冲区输入字符串,输入end表示结束进程。
在B进程中将内核缓冲区的内容保存在命令行参数argv[1]指定的文件中。
进程A : fifoa.c

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

int main()
{
    int fd = open("FIFO",O_WRONLY);//默认是阻塞的
    if(fd == -1)
    {
        int n = mkfifo("FIFO",0664);//创建管道文件FIFO
        if(-1 == n)
        {
            perror("mkfifo");
            exit(0);
        }
        fd = open("FIFO",O_WRONLY);
    }
    assert(-1 != fd);
    printf("open success!\n");
    while(1)
    {
        printf("please input:");
        char buf[128] = {0};
        fgets(buf,128,stdin);

        if(strncmp(buf,"end",3) == 0)//输入end用以结束进程
        {
            break;
        }
        write(fd,buf,strlen(buf) - 1);//strlen(buf)-1减去回车符占用的一个字节长度,否则输出到屏幕上的值为乱码
    }
    close(fd);
}

进程B fifob.c

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

int main(int argc,char *argv[])
{
    int fd = open("FIFO",O_RDONLY);//浼氶樆濉炶繍琛?
    if(fd < 0)
    {
        int n = mkfifo("FIFO",0664);
        if(-1 == n)
        {
            perror("mkfifo");
            exit(0);
        }
        fd = open("FIFO",O_RDONLY);
        assert(-1 != fd);
    }
    int fd1 = open(argv[1],O_WRONLY | O_CREAT,0664);
    assert(fd1 != -1);
    while(1)
    {
        char buf[128] = {0};
        int n = read(fd,buf,127);
        if(0 == n)
        {
            break;
        }
        write(fd1,buf,n);
    }
    close(fd);
    close(fd1);
}
gcc fifoa.c -o fifoa
gcc fifob.c -o fifob
./fifoa
./fifob a.txt

A,B进程必须同时执行,否则被执行的进程会出现阻塞。且看管道机制中的一条,必须确认对方的从在,否则就会阻塞,等待对方的到来。
进程通信(一) 无名管道和有名管道
进程通信(一) 无名管道和有名管道

在A进程中输入字符串:
进程通信(一) 无名管道和有名管道
查看由B进程保存的a.txt文件:
进程通信(一) 无名管道和有名管道

正式我们刚刚在A进程中输入的字符串。