进程在各自运行时具有独立性,
表现之一就是每个进程都占有一份独立的虚拟空间,如何让两个进程进行通信
有很多方法,这里介绍其中最常见的方式之一 管道
管道的特性与介绍
管道的名字就已经很形象了, 就像水龙头,水从一端进入,一端出去
,而且是单向流动,不可能出现打开水龙头又发现水倒着流入的状况
同样流入的水和流出的水没有有显著的区分
比如不可能把流入水管里的水划分出ABCD段,然后一段一段流出
那么把水龙头换成管道,两个进程 A B通过管道通信,假设A是发送方,B是接收方
A就从管道的一端写入,B就可以在管道的另一端接受消息.
通过以上的描述,我们可以归纳出进程通信的管道有一下特点
1)管道是半双工的,数据只能向一个方向流动,一端规定为管道的写入端,一端规定为管道的读出端
2)管道是面向字节流的,发送方和接收方不规定信息的具体格式,由接收方接受到消息后自行判断
3)管道本身具有同步互斥的概念,表现在 :如果写入方停止写入,读出方在读完管道内容后将会进入阻塞状态.两端任何一端停止活动.另一端也会停止活动
4)管道的生命周期同进程,进程结束,管道关闭.
管道的原理
要明白管道的原理,我们首先应该知道文件描述符这个概念,
匿名个管道的创建
我们创建一个文件Pipe,用两个文件描述符fd[2] 都指向这一个文件
再使用fork()函数创建子进程,注意子进程会继承已经分配的文件描述符和环境变量
那么让父进程关闭f[0] 使用系统调用write从 f[1] 写入Pipe,子进程关闭 f[1] ,使用read从f[0]
Pipe读出数据
就完成匿名管道的通信
创建管道的函数如下
#include <unistd.h>
int pipe(int pipefd[2]);//成功返回0 失败返回-1
一个简单的例子
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
int main()
{
char buf[64];
int fd[2];
int r = pipe(fd);
if ( r == -1 )
{
perror("make pipe\n");
return 2;
}
pid_t pid = fork();
if( pid == 0 )
{
close(fd[0]);
while(1)
{
int n = read(0, buf,sizeof(buf) - 1);
write(fd[1],buf,n);
}
}
if( pid > 0 )
{
close(fd[1]);
/*sleep(5);*/
int cnt = 5;
while(cnt--)
{
ssize_t s = read(fd[0],buf,sizeof(buf) - 1);
buf[s] = '\0';
printf("%s",buf);
}
close(fd[0]);
/*sleep(5);*/
}
return 0;
}
本段程序 从键盘输入字符串放入管道中,再从管道中读出数据
并且只从管道读了5次,那么五次读完后,输入端也关闭了管道(同步机制)
输入结果如下:
我们发现,匿名管道又有一个最大的缺陷是只支持具有血缘关系的进程进行通信(如父子进程,兄弟进程)
如果我们希望两个八竿子打不着的进程进行通信,就需要用到fifo管道
fifo管道
fifo管道又称作命名管道,他是一个可以被文件系统管理起来的特殊文件,既然它是一个文件,那么我们就可以通过它的路径找到它
那么两个进程即使没有血缘关系,只要他们可以访问到该路径下的管道文件,那么就可以通信
值得注意的是fifo管道如同队列一般,总是先写入的先被读出来
Liunx下有两种创建fifo管道的方式
1.在shell下使用 | (不做过多解释)
2.使用系统调用函数来创建管道.
我们主要研究第二种方式创建的fifo管道
函数原形为
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
pathname 就是要创建文件的路径名
其中的mode_t 我们已经很熟悉了,就是表明打开该文件的属性
常用的比如O_WRONLY O_WRONLY
一个小例子
两个进程通过fifo管道,将<<双城记>>txt 文件 拷贝到目标文件desrfile中
写入管道文件 write.c 文件
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
int main()
{
mkfifo("pipe",0644);
int infd;
infd = open("a_tab_of_two_cities",O_RDONLY);
if( infd == -1 )
ERR_EXIT("open readfile");
int outfd;
outfd = open("pipe",O_WRONLY);
if( outfd == -1 )
ERR_EXIT("open pipe");
char buf[4096];
int n;
while((n=read(infd,buf,sizeof(buf)))>0)
{
write(outfd,buf,n);
}
close(infd);
close(outfd);
return 0;
}
从管道读出 read.c文件
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
int main()
{
int infd;
infd = open("pipe",O_RDONLY);
if( infd == -1 )
ERR_EXIT("open read");
int outfd;
//O_TRUNC 如果文件存在属性不变
outfd = open("dst_file",O_WRONLY | O_CREAT | O_TRUNC,0644);
if( outfd == -1 )
ERR_EXIT("open write");
char buf[4096];
int n;
while((n = read(infd,buf,sizeof(buf) - 1)) > 0)
{
if(n > 0)
{
//注意 放到buf里 最后一个是\0 表示是c风格字符串
buf[n] = '\0';
printf("%s\n",buf);
}
//写入的时候只写 n个\0不写入目标文件中
write(outfd,buf,n);
}
close(infd);
close(outfd);
//删除管道文件
unlink("pipe");
return 0;
}
先运行write.c (src)进入阻塞状态
然后运行read.c(dst) 从管道读出数据,写入数据文件中
如何在运行两个程序?
1在一个终端运行程序时使用 &命令让它在后台运行
2 新创建一个终端 运行另外一个
由于双城记字数较多 不好人工比较复制成功与否
使用diff命令发现两者没有区别,证明复制成功
多个进程使用同一个管道问题
# 多个写端
因为使用的是FIFO管道,所以肯定是先被写入的先被读出来
多个读端
管道内置了同步与互斥机制, 当有多个读端时,会给进程”排队”,按加入读端的次序进行轮流读取