📝前言:
这篇文章我们来讲讲Linux 进程间通信(三)------命名管道
🎬个人简介:努力学习ing
📋个人专栏:Linux
🎀CSDN主页 愚润求学
🌄其他专栏:C++学习笔记,C语言入门基础,python入门基础,C++刷题专栏
这里写目录标题
- 一,命名管道介绍
-
- [1. 原理介绍](#1. 原理介绍)
- [2. 简单创建](#2. 简单创建)
- [3. 简单使用](#3. 简单使用)
- [4. 管道文件特殊的读写规则](#4. 管道文件特殊的读写规则)
- [5. 命名管道删除](#5. 命名管道删除)
- 二,使用示例
一,命名管道介绍
之前我们介绍过了匿名管道,匿名管道通常用于有血缘关系的进程。那如果两个毫无关系的进程之间要进行通信,这时候就需要用到我们的命名管道。
1. 原理介绍
首先,想要进行进程间通信,就要让两个进程看到同一份资源!这份资源就是文件!
命名管道的原理和匿名管道基本相同,都是要让两个进程看到同一份文件。那两个没有血缘关系的进程是如何做到的呢?

- 当两个进程打开同一路径下的同一个文件的时候,对应的文件描述符会被写入对应进程的文件描述符表中
- 而对应文件的
struct file
其实是会被拷贝一份的(尽管两个struct file
描述的都是c.txt
文件,但是两个进程可能会在文件中有不同的读写位置不同的struct file
就会记录) - 但是文件的 inode 和 文件内核缓冲区(这是文件在内存中真正的主体)是不会拷贝的,因为都是同一个文件的。
- 即:因为路径具有唯一性,两个进程利用路径的唯一性看到了同一个文件(资源)。因为文件有文件名,所以叫做命名管道。
问题是,对于普通文件来说,IO会往磁盘刷新!
所以,OS提供了一种特殊的文件:管道文件,这种文件不会往磁盘刷新。这样两个进程就可以通过读写这个文件来实现通信!
2. 简单创建
我们用mkfifo
来创建管道文件,命令和库函数同名
命令:
库函数:
- 参数:(文件路径,创建文件时的权限)
- 返回值:成功:
0
,失败:-1
示例(命令行创建):
bash
mkfifo fifo.txt

这个p
就代表是管道文件
示例(库函数创建):
cpp
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
int main()
{
int ret = mkfifo("fifo2.txt", 0666);
if(ret == 0)
std::cout << "创建管道文件fifo2.txt成功" << std::endl;
return 0;
}
当命名管道创建好后,和普通文件的使用放个一模一样。先打开,然后再读写操作。
3. 简单使用
命令行输入:echo "hello world" > fifo.txt
结果:
就像被卡柱一样,直到我们cat fifo.txt
才有反应:
这是因为,管道文件有特殊的读写规则。
4. 管道文件特殊的读写规则
管道文件的读写端必须都打开了才能工作,不然一段会端塞。
以上面的例子为例,
-
当使用
echo "hello world" > fifo.txt
往管道写数据时,必须有另一个进程在管道的读端等待读取数据,否则写操作会被阻塞。 -
同理,如果先打开读端,但是写端没有打开,读端也会阻塞等待写端打开。
5. 命名管道删除
使用unlink
:
当然,命令行也可以用rm
删
系统调用删,常用于代码中
二,使用示例
错误和退出宏
- 当一个操作错误时,C语言会设置对应的错误码在
errno
,我们可以通过perror
来打印对应错误码的错误信息。 - 如果我们想在打印错误信息后立刻终止程序,可以添加
exit
操作
上述两个操作,我们可以用一个宏 + do...while(0)
来包装一下
cpp
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
do...while(0)
:只是为了提供一个能用于代码块包装的{}
- 后续写代码时,我们直接调用
ERR_EXIT(m)
,m
为我们传入的字符串提示信息 - 因为宏是要写到一行的,
\
是为了换行 EXIT_FAILURE
是标准库<stdlib.h>
中已经定义的宏(通常值为1
)
示例1
思路
重建两个进程,利用命名管道进行文件的拷贝:把file1.txt
的 内容拷贝到 file2.txt
基本思路:
- 一个进程往命名管道里面写入
file1.txt
的内容 - 另一个进程从命名管道读内容,写入
file2.txt
代码
WriteN 文件:
cpp
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <cstdlib>
#include <fcntl.h>
#include <unistd.h>
using namespace std;
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)
int main()
{
// 创建命名管道
int m = mkfifo("tp", 0666);
if (m < 0)
ERR_EXIT("mkfifo"); // 代表mkfifo这个行为发生错误
// 打开原有文件
int infd = open("file1.txt", O_RDONLY);
if (infd < 0)
ERR_EXIT("open");
// 打开命名管道
int outfd = open("tp", O_WRONLY);
if (outfd < 0)
ERR_EXIT("open");
// 把原有文件的内容输出到命名管道
char buffer[1024];
int n;
while ((n = read(infd, buffer, sizeof(buffer))) > 0) // 当还读到有数据时
{
write(outfd, buffer, n);
}
close(infd);
close(outfd);
return 0;
}
ReadN文件:
cpp
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <cstdlib>
#include <fcntl.h>
#include <unistd.h>
using namespace std;
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)
int main()
{
// 打开命名管道
int infd = open("tp", O_RDONLY);
if(infd < 0)
ERR_EXIT("open");
// 打开要写入的文件
int outfd = open("file2.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);
if (outfd < 0)
ERR_EXIT("open");
// 把命名管道的内容写到要拷贝到的文件
char buffer[1024];
int n;
while((n = read(infd, buffer, sizeof(buffer))) > 0) // 当还读到有数据时
{
write(outfd, buffer, n);
}
close(infd);
close(outfd);
// 删除命名管道
unlink("tp");
return 0;
}
运行
一个终端先运行writer,另一个终端再运行reader,就完成了文件拷贝工作
示例2
思路
- 利用命名管道建立一个服务段和客户端对话的窗口。
- 客户端往命名管道里面写数据。
- 服务段从命名管道里面读数据。
代码
comm.hpp文件:
cpp
#pragma once
#include <string>
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#define PATH "."
#define FILENAME "fifo"
using namespace std;
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)
class NamedPipe
{
public:
NamedPipe(string path, string name)
: _path(path), _name(name)
{
_fifoname = _path + "/" + _name;
int m = mkfifo(_fifoname.c_str(), 0666);
if (m < 0)
ERR_EXIT("mkfifo");
cout << "mkfifo " << _fifoname << "sucess" << endl;
}
~NamedPipe()
{
int u = unlink(_fifoname.c_str());
if (u < 0)
ERR_EXIT("unlink");
cout << "unlink " << _fifoname << "sucess" << endl;
}
private:
string _path;
string _name;
string _fifoname;
};
class Link // 服务端和用户端通过这个Link对象来进行对话
{
public:
Link(const string& path, const string& name) // 这个构造只是为了把上面创建的命名管道的信息拿下来
: _path(path), _name(name)
{
_fifoname = _path + "/" + _name;
cout << "Link " << _fifoname << " -> sucess" << endl;
}
void LinkForWrite()
{
// 当命名管道创建好了以后,就当做普通文件一样访问
_fd = open(_fifoname.c_str(), O_WRONLY | O_TRUNC);
if (_fd < 0)
ERR_EXIT("open");
cout << "Now you can write something to server " << endl;
}
void LinkForRead()
{
_fd = open(_fifoname.c_str(), O_RDONLY);
if (_fd < 0)
ERR_EXIT("open");
cout << "Now you can read from client " << endl;
}
void Write()
{
while (true)
{
string message;
getline(cin, message);
if(message == "quit")
{
break;
}
write(_fd, message.c_str(), message.size());
}
}
void Read()
{
while (true)
{
char buffer[1024];
int n = read(_fd, buffer, sizeof(buffer) - 1);
buffer[n] = '\0';
if (n > 0)
{
cout << buffer << endl;
}
else if (n == 0)
{
cout << "cilent quit... now server quit" << endl;
exit(EXIT_SUCCESS);
}
else
{
ERR_EXIT("read");
}
}
}
void Close()
{
close(_fd);
}
private:
string _path;
string _name;
string _fifoname;
int _fd;
};
client.cpp文件:
cpp
#include "comm.hpp"
int main()
{
Link Client(PATH, FILENAME);
Client.LinkForWrite();
Client.Write();
Client.Close();
}
server.cpp文件
cpp
#include "comm.hpp"
int main()
{
// 创建命名管道
// 构造时创建,进程结束时,自动析构
NamedPipe fifo(PATH, FILENAME);
Link Server(PATH, FILENAME);
Server.LinkForRead();
Server.Read();
Server.Close();
}
Makefile文件:
bash
.PHONY:all
all:client server
client:client.cpp
g++ -o $@ $^ -std=c++11
server:server.cpp
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f client server
- 伪目标
all
(不对应实际文件,仅用于执行命令),依赖于client
和server
make
执行all
的时候,就会去执行client
和server
目标
运行


🌈我的分享也就到此结束啦🌈
要是我的分享也能对你的学习起到帮助,那简直是太酷啦!
若有不足,还请大家多多指正,我们一起学习交流!
📢公主,王子:点赞👍→收藏⭐→关注🔍
感谢大家的观看和支持!祝大家都能得偿所愿,天天开心!!!