我们前面说的管道都是只能具有血缘关系的进程进行进程间通信,如果我想让两个毫不相干的进程进行通信呢?那就需要来谈谈命名管道了。
命名管道
- 管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
- 如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
- 命名管道是一种特殊类型的文件
我们前面说进程通信的本质是让不同的进程看到同一份资源,而如果我们如何让两个毫不相干的进程看到同一份资源呢?因为路径是具有唯一性的,所以,我们可以使用路径+文件名,来唯一的让不同的进程看到同一份资源。
假设有A进程和B进程,A进程要打开一个文件,那么他要为该文件创建对应的struct file,还要创建关于该文件的一些内部对象,比如说inode,缓冲区,方法集等这些东西。然后当我们在同路径下用B进程打开这个文件,操作系统还用不用重新加载该文件的inode ,缓冲区这些东西,然后再打开了呢?答案是不用再打开了,也不用再加载了,因为他已经加载到内存了,操作系统不会做重复的工作,不会将文件的这些内部对象在内存中加载很多份,但是这两个进程一个是读一个是写,要有不同的读写位置,所以要有独立的struct file,这两个struct file都指向同一个缓冲区。所以我们的A进程和B进程指向的是一个共享的区域,所以原理呢其实就是跟之前谈到的匿名管道是一模一样的。我们对应的结论就是,我们之前讲过进程之间是具有独立性的,进程间要完成通信就必须看到同一份资源,而我们这里谈到的同一份资源就是同一个缓冲区,但是管道文件就不用在磁盘上做刷新了,它是一种内存级的,然后又符合单向通信的原则的。那么这里又是通过什么方式让不同的进程看到相同的资源的呢?其实就是我们所说的通过路径+文件名这种做法。
创建一个命名管道
命名管道可以从命令行上创建,命令行方法是使用下面这个命令:
$ mkfifo filename
命名管道也可以从程序里创建,相关函数有:
int mkfifo(const char *filename,mode_t mode);
创建命名管道:
int main(int argc, char *argv[])
{
mkfifo("p2", 0644);
return 0;
}
匿名管道与命名管道的区别
- 匿名管道由pipe函数创建并打开。
- 命名管道由mkfifo函数创建,打开用open
- FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义。
命名管道的打开规则
- 如果当前打开操作是为读而打开FIFO时
- O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
- O_NONBLOCK enable:立刻返回成功
- 如果当前打开操作是为写而打开FIFO时
- O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO
- O_NONBLOCK enable:立刻返回失败,错误码为ENXIO
用命名管道实现server&client通信
源代码:
server.cc
#include<iostream>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<cerrno>
#include<cstdlib>
#include<cstring>
#include<fcntl.h>
#include"comm.h"
using namespace std;
bool MakeFifo()
{
int n = mkfifo(FILENAME,0666);
if(n<0)
{
cerr<<"errno: "<<errno<<", errstring: "<<strerror(errno)<<endl;
return false;
}
cout<<"mkfifo success ...read"<<endl;
return true;
}
int main()
{
Start:
int rfd = open(FILENAME,O_RDONLY);
if(rfd < 0)
{
cerr<<"errno: "<<errno<<", errstring: "<<strerror(errno)<<endl;
if(MakeFifo()) goto Start;
else return 1;
}
cout<<"open fifo success ..."<<endl;
char buffer[1024];
while(true)
{
ssize_t s = read(rfd,buffer,sizeof(buffer)-1);
if(s>0)
{
buffer[s] = 0;
cout<<"Client say# "<<buffer<<endl;
}
else if(s == 0)
{
cout<<"client quit , server quit too!"<<endl;
break;
}
}
close(rfd);
cout<<"close fifo success ..."<<endl;
return 0;
}
client.cc
#include<iostream>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<cerrno>
#include<cstdlib>
#include<cstring>
#include<fcntl.h>
#include"comm.h"
using namespace std;
int main()
{
int wfd = open(FILENAME,O_WRONLY);
if(wfd<0)
{
cerr<<"errno: "<<errno<<", errstring: "<<strerror(errno)<<endl;
return 1;
}
cout<<"mkfifo success ...write"<<endl;
string message;
while(true)
{
cout<<"Please Enter# ";
getline(cin,message);
ssize_t s = write(wfd,message.c_str(),message.size());
if(s<0)
{
cerr<<"errno: "<<errno<<", errstring: "<<strerror(errno)<<endl;
break;
}
}
close(wfd);
cout<<"close fifo success ..."<<endl;
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 server client .fifo
comm.h
#pragma once
#define FILENAME ".fifo"
运行结果: