进程间通讯:管道

 
一、首先来总览一下进程间通讯的方式。 

进程间通讯的方式:

1.信号 : 数量有限,无法传递数据

2.管道: 分为无名管道和有名管道,本篇博客重点讲解 

3.信号量:用于进程间同步控制

4.消息队列:进程间发送数据块,每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构。

5.共享存储(共享内存): 共享内存并未提供同步机制,需要其他机制来同步

6.套接字(socket):用于网络间通讯

通过对信号的学习,我们可以知道,使用信号传递的信息只是一个信号值,这在进程间通讯时,有时会感到不足。而使用另外一种进程间通信的方式管道,通过它进程间可以交换更多数据。


二、什么是管道?

在现实生活中,管道的作用是将资源从A地运往B地,相似的在Linux中,管道将进程A写入的数据交给进程B。

即进程之间数据单向传递,类似于一个信箱,进程A将信件放入位于内存上的信箱,进程B从信箱中读取信件。

由于管道缓存于内存上,并不占用实际的磁盘空间。



三、有名管道

即命名管道,它是FIFO特殊类型的文件,在文件目录树中占有文件标识,但是不占用空间。

1.创建:以命名的方式,函数为

#include <sys/types.h>  

#include <sys/stat.h>  

int mkfifo(const char *filename, mode_t mode);  filename指定文件名,mode指定文件的读写权限

2.打开:调用open函数来打开,但是要注意打开方式,以只读或者只写打开,不能以读写打开,负责进程将会自己进行管道文件的读写,不满足进程间通讯。

open(const char *path, O_RDONLY);  接受信息的进程端,以只读方式打开

open(const char *path, O_WRONLY);  发送信息的进程端,以只写方式打开

需要强调的是,对于以只写方式(O_WRONLY)打开的FIFO文件,open调用将被阻塞,直到有一个进程以只读方式打开同一个FIFO文件为止,正是这种阻塞运行让管道在传递信息时更加可靠。

3.读与写:和文件操作类似,调用write()和read()函数,需要注意的是,read()函数也会阻塞运行,直到写端写入数据或者所有的写端都关闭,并且read()读取数据会将内存上的已读数据清空。

4.关闭:FIFO也是文件,在使用结束后需要关闭,调用close()函数。


通过一个例子来说明,进程A读取键盘输入字符串,进程B统计进程A读取的字符个数,代码如下:


进程A读取键盘输入字符串

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <fcntl.h>


void main()
{
int fd = open("FIFO",O_WRONLY);
if(fd == - 1)
{
exit(0);
}
while(1)
{
char buff[256]={0};
fgets(buff,255,stdin);
write(fd, buff,strlen(buff)-1);
if(strncmp(buff,"end",3)==0)
{
break;
}
}
close(fd);
}


进程B统计进程A读取的字符个数

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <fcntl.h>

void main()
{
int n=0;
int fd = open("FIFO",O_RDONLY);
if(fd == -1)
{
exit(0);
}
while(1)
{
char buff[256]={0};
read(fd,buff,256);
if(strncmp(buff,"end",3)==0)
{
break;
}
n+=strlen(buff);
printf("size = %d\n len = %d\n",strlen(buff),n);
}
close(fd);
}


四、无名管道

相对于有名管道而言的,无名管道在使用时产生,不使用后释放,并不会在系统上  留下痕迹。 无名管道因其使用前没有任何的标示,所以他只能应用于父子进程之间子进程会复制父进程的文件表数组(浅拷贝)

1.创建、打开:使用pipe()函数

#include <unistd.h>  

int pipe(int file_descriptor[2]);  

pipe函数的参数为一个俩个元素的数组,例如int fd[2];其fd[0]来标志读,fd[1]来标志写;

   2.读与写:

    read(fd[0], buff,size);

    write(fd[1], buff,len);

   3.关闭:需要分别关闭,close(fd[0]); close(fd[1]);

无名管道只能用于父子进程,父进程在 fork 产生子进程后,两个进程分别有一对读写,所以,要在父子进程分别关闭读或写。

以一个例子来说明,父进程读取键盘输入,子进程统计字符的个数:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <fcntl.h>
#include <pwd.h>


void main()
{


int fd[2] = {0};
pipe(fd);
pid_t pid = fork();
assert(pid!=-1);


if(pid == 0)
{
int num = 0;
close(fd[1]);
while(1)
{
char buff[128]={0};
read(fd[0],buff,127);
if(strncmp(buff,"end",3)==0)
{
break;
}
num +=strlen(buff);
printf("size = %d\n len = %d\n",strlen(buff),num);
}
close(fd[0]);
}
else
{


close(fd[0]);
while(1)
{
char buff[128]={0};
//printf("pleas input:");
fgets(buff,127,stdin);
write(fd[1],buff,strlen(buff)-1);
if(strncmp(buff,"end",3)==0)
{
break;
}
}
close(fd[1]);
}
}


五、拓展

1.管道在内核实现时,借助了文件系统的file结构和VFS的索引节点inode。通过将两个 file 结构指向同一个临时的 VFS 索引节点,而这个 VFS 索引节点又指向一个物理页面而实现的。有两个 file 数据结构,但它们定义文件操作例程地址是不同的,其中一个是向管道中写入数据的例程地址,而另一个是从管道中读出数据的例程地址。这样,用户程序的系统调用仍然是通常的文件操作,而内核却利用这种抽象机制实现了管道这一特殊操作。

2.管道有着默认64KB字节的大小限制,一次写入的大小限制为4KB。


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值