进程间通信之命名管道
- 命名管道
- 1.命名管道概念
- 2.创建一个命名管道
- 2.1用命名管道实现通信
- 2.2用命名管道实现server&client通信
命名管道
1.命名管道概念
管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信,这种通信可以用匿名管道来实现。如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
1.什么是命名管道?
命名管道(named pipe)也称为FIFO,它是一种文件类型,创建一个FIFO文件类似于创建一个普通文件。FIFO解决了只有具有亲缘关系的进程间才能通信的问题。并且命名管道是一种特殊类型的文件。
2.匿名管道与命名管道的区别?
- 匿名管道由pipe函数创建并打开。
- 命名管道由mkfifo函数创建,打开用open
- FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义
2.创建一个命名管道
先认识一下mkfifo函数:mkfifo是一个系统调用,用于创建一个命名管道(named pipe),也称为FIFO。
用mkfifo创建一个管道文件:
可以看到,管道文件的标志是p,并且大小为零;因为管道文件的数据不需要刷新到磁盘里,所以管道文件的大小一直为零,用vim打开管道文件那就gg。
mkfifo也是一个函数调用,成功返回0,失败返回-1。
int mkfifo(const char *pathname, mode_t mode)
返回值int
参数pathname :创建管道的路径。也就是在这个路径下创建一个管道。不带路径,只有文件名,那么默认在当前路径下。
mode_t mode :创建管道的权限。比如0666,0664等,系统下默认掩码umask()=0002。
命名管道的打开规则:
- 如果当前打开操作是为读而打开FIFO时
O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
O_NONBLOCK enable:立刻返回成功 - 如果当前打开操作是为写而打开FIFO时
O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO
O_NONBLOCK enable:立刻返回失败,错误码为ENXIO
2.1用命名管道实现通信
要求:进程A 向管道当中写 “i am process A”,进程B 从管道当中读 并且打印到标准输出。
进程A代码如下:
#include <iostream>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
using namespace std;
int main()
{
//1.创建管道文件mkfifo
umask(0); //这个设置并不影响系统的默认配置,只会影响当前进程
int n = mkfifo("./fifo", 0666);
if (n == -1) //失败返回-1,打印错误码并且退出1
{
cout << errno << " : " << strerror(errno) << endl;
return 1;
}
//2.让进程A开启管道文件
int wfd = open("fifo", O_WRONLY);
if (wfd == -1)//失败返回-1,打印错误码并且退出2
{
cout << errno << " : " << strerror(errno) << endl;
return 2;
}
//3.向管道中写入"i am process A"
while (true)
{
char buffer[1024] = "i am process A";
ssize_t m = write(wfd, buffer, strlen(buffer));
assert(m > 0);
(void)m;
sleep(1);
}
close(wfd);
unlink("fifo");//去掉管道文件的链接,也就是删除fifo文件
return 0;
}
进程B代码如下:
#include <iostream>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
using namespace std;
int main()
{
//1.不需创建管道文件,只需要打开对应的文件即可
int rfd = open("fifo", O_RDONLY);
if (rfd == -1)//失败返回-1,打印错误码并且退出1
{
cout << errno << " : " << strerror(errno) << endl;
return 1;
}
//2.可以进行常规通信了
while (true)
{
char buffer[1024];
int n = read(rfd, buffer, sizeof(buffer) - 1);
assert(n >= 0);
cout << "我是B进程,读取到的数据是:" << buffer << endl;
sleep(1);
}
close(rfd);
return 0;
}
makefile中代码如下:
.PHONY:all
all:server client
server:server.cc
g++ -o $@ $^ -std=c++11
client:client.cc
g++ -o $@ $^ -lncurses -std=c++11
.PHONY:clean
clean:
rm -f server client
如果A进程报如下错误:是因为fifo管道文件存在,将fifo文件手动删除即可(rm -f fifo:rm删除文件;-f:强制删除;-r:递归删除,一般用于删除目录等树状结构)。(所以文件末尾需要unlink(“fifo”);//删掉创建的管道文件)
运行结果如下,可以在B进程看到A进程的消息
2.2用命名管道实现server&client通信
要求:client从键盘读入数据,并且发送到管道;server从管道读取数据,并且打印到标准输出。
comm.hpp中代码如下:
#pragma once //防止头文件重复引用
#include <iostream>
#include <cstdio>
#include <cerrno>
#include <cstring>
#include <cassert>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define NUM 1024
const std::string fifoname = "./fifo";
uint32_t mode = 0666;
server.cc中代码如下:
#include "comm.hpp"
//少年们, 我刚刚写了一个基于匿名管道的进程池
// 可不可以把它改成使用命名管道呢??
int main()
{
// 1. 创建管道文件,我们今天只需要一次创建
umask(0); //这个设置并不影响系统的默认配置,只会影响当前进程
int n = mkfifo(fifoname.c_str(), mode);
if(n != 0)
{
std::cout << errno << " : " << strerror(errno) << std::endl;
return 1;
}
std::cout << "create fifo file success" << std::endl;
// 2. 让服务端直接开启管道文件
int rfd = open(fifoname.c_str(), O_RDONLY);
if(rfd < 0 )
{
std::cout << errno << " : " << strerror(errno) << std::endl;
return 2;
}
std::cout << "open fifo success, begin ipc" << std::endl;
// 3. 正常通信
char buffer[NUM];
while(true)
{
buffer[0] = 0;
ssize_t n = read(rfd, buffer, sizeof(buffer) - 1);
if(n > 0)
{
buffer[n] = 0;
std::cout << "client# " << buffer << std::endl;
}
else if(n == 0)
{
std::cout << "client quit, me too" << std::endl;
break;
}
else
{
std::cout << errno << " : " << strerror(errno) << std::endl;
break;
}
}
// 关闭不要的fd
close(rfd);
unlink(fifoname.c_str());
return 0;
}
client.cc中代码如下:
#include "comm.hpp"
int main()
{
//1. 不需创建管道文件,我只需要打开对应的文件即可!
int wfd = open(fifoname.c_str(), O_WRONLY);
if(wfd < 0)
{
std::cerr << errno << ":" << strerror(errno) << std::endl;
return 1;
}
// 可以进行常规通信了
char buffer[NUM];
while(true)
{
std::cout << "请输入你的消息# ";
char* msg = fgets(buffer, sizeof(buffer), stdin);
assert(msg);
(void)msg;
sleep(1);
buffer[strlen(buffer) - 1] = 0;
// abcde\n\0
// 012345
if(strcasecmp(buffer, "quit") == 0) break;
ssize_t n = write(wfd, buffer, strlen(buffer));
assert(n >= 0);
(void)n;
}
close(wfd);
return 0;
}
makefile中代码如下:
.PHONY:all
all:server client
server:server.cc
g++ -o $@ $^ -std=c++11
client:client.cc
g++ -o $@ $^ -lncurses -std=c++11
.PHONY:clean
clean:
rm -f server client
运行结果如下: