Linux进程间通信-匿名管道和与命名管道

本文介绍了Linux中的管道通信,包括匿名管道和命名管道。匿名管道通过pipe函数实现,适用于父子进程间通信,而命名管道通过特定路径的文件实现,允许不相关进程间通信。文中给出了详细的示例代码,展示了管道的创建、使用以及一些特殊情况的处理。
摘要由CSDN通过智能技术生成

管道命令(pipe)

首先提到管道,大家都不会太陌生,生活中也处处可见管道,如自来水管道,天然气管道,无疑它们的作用就是作为两个事物的中间桥梁,起着导通的作用,那么在Linux中亦是如此。

Linux命令行中,我们常使用“ | ”这个符号,因为管道的作用就是作为数据之间的导向,即大多数情况将一个进程的输出导向另外一个进程的输入。
如查看/etc下面的文件

ls -al /etc

但结果是/etc下的文件太多了,而使得我们无法正常浏览和获取到有用的讯息,那么可以试图将查询的结果放入”more”或者“less”(都用来显示文件,所不同的是less不但支持下翻页,并且支持上翻页)。

ls -al /etc | less

由此我们可以看出管道接受来自前面命令得到的数据,然后传递给后面的命令

通常和管道密切联系的命令:
选取命令:cut,grep
cut,即从表意可以看出切的意思,就是将一段数据中的某一小段切除出来。

-d:后面接分隔字符,与-f连用
-f:通过-d切分为数段后,选取具体某一段
-c:以字符为单位,取出固定的区间

举个例子即可通晓其作用:

ll | cut -d ' ' -f 10

先显示主工作目录下的文件信息,在通过空格为界限分为10段,取最后一段的文件名。
这里写图片描述

grep:分析一行数据,选取出我们需要的信息

-a:将二进制文件以test文件方式查找
-c:计算找到查找字符串的次数
-i:不区分大小写
-n:顺道打印出行号
-v:反向输出,即显示出没有所需信息的内容

也举一个简单的例子:

ll /etc |grep 'vimrc'

显示etc目录下,含有vimrc字符串的行
这里写图片描述

简单的管道命令方面应用就是这些。


匿名管道

在实际操作中,管道作为实现进程间通信的方式,常通过pipe函数来创建,原型如下:

int pipe(int filedes[2]);

函数成功返回0,失败返回-1,数组中存放着文件描述符,即0:读入和1:写入

所谓实现进程间通信,无非就是让两个进程看到同一份代码即可,而pipe函数,就是通过在内核开辟一个公共的缓冲区,来用于两个进程间的联系。
具体的演示代码如下:

#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include<sys/wait.h>
int main()
{
    int _pipe[2];
    int ret = pipe(_pipe);
    if(-1 == ret)
    {
        printf("creat pipe error!error code is:%d\n",errno);
        return 1;
    }
    pid_t id = fork();
    if(id<0)
    {
        printf("fork error!");
        return 2;
    }
    else if(id == 0)//child
    {
        close(_pipe[0]);
        int i=0;
        char* _mesg_c = "I am child";
            for(;i<10;i++)
            {
                write(_pipe[1],_mesg_c,strlen(_mesg_c));
            }   
            sleep(1);
    }
    else//father
    {
        int j=0;
        close(_pipe[1]);
        char _mesg[11];
        while(j<10)
        {
            memset(_mesg,'\0',sizeof(_mesg));
            read(_pipe[0],_mesg,sizeof(_mesg));
            printf("%s\n",_mesg);
            j++;
        }
        if(waitpid(id,NULL,0)<0)
        {
            return 3;
        }
    }

    return 0;

}

通过fork()出一个子进程,因为管道间的单向通信,所以将子进程的读端关掉,将父进程的写端关掉,然后让子进程每隔一秒将一句“I am child”写入缓冲区,
而父进程直接读取缓冲区当中的数据。
这里写图片描述
程序最后成功的由父进程输出了十条语句。

那么我们需要考虑下面几种特殊的情况:

1 .如果所有写端都被关闭,而依然有进程读取,会怎么样呢。

给出完整的测试代码:

#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include<sys/wait.h>
int main()
{
    int _pipe[2];
    int ret = pipe(_pipe);
    if(-1 == ret)
    {
        printf("creat pipe error!error code is:%d\n",errno);
        return 1;
    }
    pid_t id = fork();
    if(id<0)
    {
        printf("fork error!");
        return 2;
    }
    else if(id == 0)//child
    {
        close(_pipe[0]);
        int i=0;
        char* _mesg_c = "I am child";
            for(;i<10;i++)
            {
                write(_pipe[1],_mesg_c,strlen(_mesg_c));
            }   
        close(_pipe[1]);
            sleep(1);
    }
    else//father
    {
        int j=0;
        close(_pipe[1]);
        char _mesg[11];
        while(j<100)
        {
            memset(_mesg,'\0',sizeof(_mesg));
            read(_pipe[0],_mesg,sizeof(_mesg));
            printf("%s\n",_mesg);
            j++;
        }
        if(waitpid(id,NULL,0)<0)
        {
            return 3;
        }
    }

    return 0;

}

因为子进程写入10条就关闭了写端,而父进程会读取100,那么结果如何呢?

这里写图片描述
它会将缓存区内的剩余数据读取完,待下次read()时,便会如读到文件结尾一样退出。

2 . 如果所有的读端都被关闭,依然有进程写入数据。

那么该进程会收到信号,导致进程异常终止。

3 . 如果读端没关闭,但是并不读取数据,而写端一直在写入

当缓冲区被写满时会堵塞,知道缓冲区有新的位置为止

4 .如果写端没关闭,但是又不写入数据,而读端一直在读取数据

当剩余数据被读取完后会堵塞,知道有新的数据可供读取

后三个代码与第一个大致相同,只不过就是需要关闭或开启输入端或输出端,这里不做赘述了。

以上就是匿名管道的知识了,但是它有一个很严重的短板,就是只能在具有血缘的进程间进行通信,通常即父子间进程,这样就导致了它的局限性,那么下面就来说一下另外一种管道方式。


命名管道

正是由于上面匿名管道的缺陷,所以后来有了命名管道的出现,它没有了只能在亲缘进程间才能通信的限制。
它是通过一条特定路径创建一个管道文件,而进程双方都可以通过该路径来访问该管道文件,从而实现管道间通信的,可以看出,通过这种方式,即使毫不相干的两个进程也可以进行信息间的交流。

创建管道文件的函数原型:

int mknod(const char *path,mode_t mod,dev_t dev);
int mkfifo(const *path,mode_t,mode);

第一个函数与第二个函数不同在于多了最后一个参数,即设备值,该值取决于文件创建的种类,只有在创建设备文件时才会使用到。

第一个参数为所要创建管道的全路径名,第二个参数为创建管道的权限,这里需要注意的是,该值会受到umask值的影响,所以最好先将umask的值设置为000。

那么接下来给出测试代码,一个进程在一端进行数据输入,另外一端实时接收数据。


server.c

#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
    umask(0);
    if(mkfifo("./mypipe", 0666 |S_IFIFO) < 0 )//初始权限为666
    {
        perror("mkfifo");
        return 1;               
    }

    int fd = open("./mypipe",O_RDONLY);//以只读方式打开
    if(fd<0)//管道打开失败
    {
        perror("open");
        return 2;
    }
    char buf[1024];//定义一块缓冲区
    while(1)
    {
        ssize_t s = read(fd,buf,sizeof(buf)-1);//将数据从管道文件读取到buf中
        if(s>0)
        {
            buf[s-1]=0;
            printf("client say# %s\n",buf);
        }
        else
        {
            printf("client quit,server quit!");
            break;
        }
    }
    close(fd);
    return 0;
}

client.c

#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
    int fd = open("./mypipe",O_WRONLY);
    if(fd<0)
    {
        perror("open");
        return 1;
    }
    char buf[1024];
    while(1)
    {       
        printf("Please Enter:");
        fflush(stdout);
        ssize_t s = read(0,buf,sizeof(buf)-1);//文件描述符0为标准输入端,即将键盘上输入的数据读取到buff中
        {
            buf[s]=0;
            write(fd,buf,strlen(buf));//然后将buff中的数据又写入到管道文件中
        }
    }
    close(fd);
}

顺便给出上面代码的Makefile

.PHONY:all
all:client server

client:client.c
    gcc -o $@ $^
server:server.c
    gcc -o $@ $^
.PHONY:clean
clean:
    rm -f client server mypipe

那么通过上面的代码,就可以实现client程序将键盘上输入的数据读进buff里,然后从buff写入到管道文件,server程序将数据从管道文件读取到buf中,然后输出buf中的数据。

这里写图片描述

最终的测试如下,在一端输入,另一端接收。


那么系统分配的管道大小是多少呢?我们可以一直给管道中写东西,一直到写满为止来测试它的最大容量,也可以通过查询系统中的文件。

命令行指令:

cat /proc/sys/kernel/msgmax

这里写图片描述

然后我们在通过小代码测试一下:

#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include<sys/wait.h>
int main()
{
    int _pipe[2];
    int ret = pipe(_pipe);
    if(-1 == ret)
    {
        printf("creat pipe error!error code is:%d\n",errno);
        return 1;
    }
    pid_t id = fork();
    if(id<0)
    {
        printf("fork error!");
        return 2;
    }
    else if(id == 0)//child
    {
        close(_pipe[0]);
        long long count = 0;
        char* _mesg_c = "1";
        while(1)
            {
                write(_pipe[1],_mesg_c,1);
                printf("count = %d\n",++count);
            }   
    }
    else//father
    {
        char _mesg[10];
        while(1)
        {
            memset(_mesg,'\0',sizeof(_mesg));   
        }
    }

    return 0;

}

这里写图片描述

可以看到测试的管道大小和系统给定的一样,说明没有问题喔。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值