当我们的一个父进程分别以读和写的方式打开一个同一管道文件,这时我们的进程中就有了两个文件描述符指向这个管道文件。
然后让父进程创建子进程。这时父进程与子进程的内核数据结构几乎相同,此时子进程也有两个文件描述符指向管道文件而且这个管道文件与父进程指向的管道文件是同一个管道文件。
这个时候我们的父子进程就可以一个向管道文件写入数据一个从管道中读取数据,这样两个进程就可以完成通信了。
最后一个步骤就是关闭不需要的文件描述符了,如果父进程进行写入,就关闭父进程的读端,关闭子进程的写端。反之则同理
int pipe(int pipefd[2]);
若想实现父子进程或者具有血缘关系进程间通信,需将pipe()和fork()搭配使用
代码实现:
#include<iostream>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
using namespace std;
void Write(int wfd)
{
string s="hello child ,i am your father";
char buf[1024];
int cnt=0;
while(true)
{
buf[0]=0;
snprintf(buf,sizeof(buf),"%s\n",s.c_str());
//cout<<buf<<endl;
write(wfd,buf,strlen(buf));
sleep(5);
}
}
void Read(int rfd)
{
char buf[1024];
while(true)
{
buf[0]=0;
ssize_t n=read(rfd,buf,sizeof(buf));
if(n>0)
{
buf[n]=0;
cout<<"child get a message:"<<buf<<endl;
}
}
}
int main()
{
int pipefd[2]={0};
int n=pipe(pipefd);
if(n<0) return 1;
//cout<<"pipefd[0]:"<<pipefd[0]<<"pipefd[1]:"<<pipefd[1]<<endl;
pid_t pid=fork();
if(pid==0)
{
//子进程 读
close(pipefd[1]);
Read(pipefd[0]);
}
else
{
//父进程 写 i am father
close(pipefd[0]);
Write(pipefd[1]);
}
pid_t rid=waitpid(pid,nullptr,0);
if(rid<0) return 3;
return 0;
}
根据现有的知识以及代码可以总结出一些管道的特点:
1.管道只能单向通信,管道的两侧只能一个进行写入、一个读取,一旦分工确定就不能在进行更改。
2.管道的本质是文件,文件描述符fd的生命周期是跟随进程的,进程退出时,文件描述符fd也会消失,所以管道的生命周期是跟随进程的。
3.用匿名管道进行通信,这个方式只能够让有血缘关系的进程进行通信。因为没有血缘关系的进程并不知道应该打开哪一个管道文件,有血缘关系的进程可以通过继承来打开同一个管道文件。
1.读写端正常,管道为空,读端阻塞
2.读写端正常,管道写满,写端阻塞
运行结果说明了,管道写满了就不能够再进行写入了,而且也说明了管道的大小是64KB。
通过1、2这两种特殊情况,我们能够总结出管道的第五个特点:管道有一定的协同能力,让读端和写端能够按照一定的步骤进行通信(自带同步机制)
3.读端正常,突然写端关闭,读端读到0,表示读到管道结尾,不会阻塞
#include<iostream>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
using namespace std;
void Write(int wfd)
{
string s="hello father ,i am your child";
char buf[1024];
int cnt=0;
while(true)
{
if(cnt++==5) break;
char c='x';
buf[0]=0;
snprintf(buf,sizeof(buf),"%s\n",s.c_str());
write(wfd,buf,strlen(buf));
//write(wfd,&c,1);
//cout<<cnt++<<endl;
sleep(1);
}
}
void Read(int rfd)
{
char buf[1024];
int cnt=5;
while(true)
{
buf[0]=0;
ssize_t n=read(rfd,buf,sizeof(buf));
buf[n]=0;
cout<<"child get a message:"<<buf<<endl;
cout<<n<<endl;
if(n==0) break;
}
}
int main()
{
int pipefd[2]={0};
int n=pipe(pipefd);
if(n<0) return 1;
//cout<<"pipefd[0]:"<<pipefd[0]<<"pipefd[1]:"<<pipefd[1]<<endl;
pid_t pid=fork();
if(pid==0)
{
//子进程 写
close(pipefd[0]);
Write(pipefd[1]);
close(pipefd[1]);
exit(0);
}
else
{
//父进程 读
close(pipefd[1]);
Read(pipefd[0]);
}
pid_t rid=waitpid(pid,nullptr,0);
if(rid<0) return 3;
return 0;
}
4.写端正常,读端关闭,操作系统就会杀掉正在写入的进程(操作系统是不会做低效,浪费的工作)
匿名管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道,命名管道是一种特殊类型的文件。
注意:
int mkfifo(const char *pathname, mode_t mode);
pathname:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define FIFL_NAME "myfifo"
int main()
{
umask(0);
if(mkfifo(FIFL_NAME,0666)<0)
{
perror("mkfifi fail\n");
return 1;
}
return 0;
}
makefile
.PHONY:all
all:server client
client:client.cc
g++ -o $@ $^ -std=c++11
server:server.cc
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f client server
command.hpp
#include <iostream>
using namespace std;
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#define FIFL_NAME "myfifo"
int create_name_pipe()
{
umask(0);
if(mkfifo(FIFL_NAME,0666)<0)
{
perror("myfifo fail\n");
return 1;
}
return 0;
}
client.cc
#include"command.hpp"
#include<string>
int main()
{
//1.打开文件
int fd=open("./myfifo",O_WRONLY);
if(fd<0)
{
cout<<"open file fail"<<endl;
return -2;
}
//2.开始发送消息
string message;
while(true)
{
cout<<"client# "<<endl;
getline(cin,message);
if(message=="quit") break;
ssize_t n=write(fd,message.c_str(),message.size());
if(n<0)
{
cout<<"write fail"<<endl;
}
}
//3.关闭文件描述符
close(fd);
return 0;
}
server.cc
#include"command.hpp"
int main()
{
//1.创建一个有命管道
int ret=create_name_pipe();
if(ret==1) return -1;
cout<<"create fifo success"<<endl;
//2.打开管道的读端
int fd=open("./myfifo",O_RDONLY);
if(fd<0)
{
cout<<"open file fail"<<endl;
return -2;
}
cout<<"open fifo success, begin ipc"<<endl;
//3.等待客户端消息
char buffer[1024]={0};
while(true)
{
ssize_t n=read(fd,buffer,sizeof(buffer));
if(n>0)
{
buffer[n]='\0';
cout<<"server# "<<buffer<<endl;
}
else if(n==0)
{
cout<<"客户端退出了,我也退出"<<endl;
break;
}
else
{
cout<<"read fail"<<endl;
return -3;
}
}
//4.关闭文件描述符
close(fd);
//5.删除管道文件
unlink("./myfifo");
return 0;
}
代码结果: