0


【Linux】进程间通信 -- 命名管道 | mkfifo调用

小实验

1.我们首先创建两个文件

client.cpp

:

#include<iostream>usingnamespace std;intmain(){
    cout<<"hello client"<<endl;return0;}
server.cpp

:

#include<iostream>usingnamespace std;intmain(){
    cout<<"hello server"<<endl;return0;}

然后创建

Makefile

使得我们更方便的去编译:

.PHONY:all
all:server client

server:server.cpp
    g++ -o$@ $^ -std=c++11
client:client.cpp
    g++ -o$@ $^ -std=c++11

.PHONY:clean
clean:
    rm-f server client

这样我们使用一条指令编译两个文件:
在这里插入图片描述

2.我们使用创建命名管道的命令mkfifo

在这里插入图片描述

[AMY@VM-12-15-centos lesson_15]$ mkfifo named_pipe
[AMY@VM-12-15-centos lesson_15]$ ll -i
total 12664845 -rw-rw-r-- 1 AMY AMY 102 Jun 2915:17 client.cpp
664846 -rw-rw-r-- 1 AMY AMY 157 Jun 2915:19 Makefile
658324 prw-rw-r-- 1 AMY AMY   0 Jun 2915:29 named_pipe
664847 -rw-rw-r-- 1 AMY AMY 102 Jun 2915:17 server.cpp
[AMY@VM-12-15-centos lesson_15]$ 

我们可以发现我们创建的

named_pipe

是以

p

开头而且有自己独立的

inode

,说明它是一个独立的管道文件

3.开始执行

我们执行下面脚本,主要的功能就是使用

echo

循环输出

hello world!

到管道文件

named_pipe
cnt=0;while:;doecho"hello world! -> $cnt";let cnt++;sleep1;done> named_pipe

在这里插入图片描述
我们发现,我执行输出到管道文件,但是管道文件里面的文件大小始终没有变化且为0

我们再使用cat命令将管道文件

named_pipe

的内容,重定向输出到显示器,能看到脚本循环输出的值,且是从0开始的
在这里插入图片描述

命名管道是如何做到让不同的进程,先看到了同一份资源?可以让不同的进程打开指定名称(路径+文件名)的同一个文件
原因是:路径+文件名=唯一性(匿名管道是通过继承)

在语言层面使用命名管道实现通信

1.创建命名管道-函数mkfifo

在这里插入图片描述

comm.hpp

创建一个管道文件

#pragmaonce#include<iostream>#include<string>#include<cstring>#include<cerrno>#include<cassert>#include<unistd.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#defineNAMED_PIPE"/tmp/my_named_pipe"//创建指定路径上的命名管道boolcreateFifo(const std::string &path){umask(0);//将权限掩码设置为0int n =mkfifo(path.c_str(),0600);if(n ==0)returntrue;else{
        std::cout <<"errno: "<< errno <<" err string: "<<strerror(errno)<< std::endl;returnfalse;}}//删除指定路径上的命名管道voidremoveFifo(const std::string &path){int n =unlink(path.c_str());assert(n ==0);(void)n;}

解释:函数

createFifo

的作用是创建一个匿名管道,而它传入的参数

path

则是创建的命名管道的路径。函数调用

mkfifo(path.c_str(), 0600)

来创建一个命名管道。

mkfifo()

是一个系统调用,用于创建一个新的命名管道。它的第一个参数是一个指向管道路径的字符串,第二个参数是一个表示权限位的八进制数。在这个例子中,权限被设置为"0600",表示只有管道的所有者可以读写,其他用户没有任何访问权限。

返回值:如果

mkfifo()

调用成功,它将返回0,函数将返回true表示管道创建成功。如果

mkfifo()

调用失败,它将返回-1,并设置全局变量

errno

来指示错误的类型。函数将打印错误信息并返回false表示管道创建失败。

strerror(errno)

用来获取对应错误码的错误信息。

2.写入端代码

client.cpp

:

#include"comm.hpp"intmain(){
    std::cout <<"client begin"<< std::endl;int wfd =open(NAMED_PIPE, O_WRONLY);//仅以写入模式打开
    std::cout <<"client end"<< std::endl;if(wfd <0)exit(1);//writechar buffer[1024];while(true){
        std::cout <<"Please Say# ";fgets(buffer,sizeof(buffer),stdin);// abcd\nif(strlen(buffer)>0) buffer[strlen(buffer)-1]=0;
        ssize_t n =write(wfd, buffer,strlen(buffer));assert(n ==strlen(buffer));(void)n;}close(wfd);return0;}

代码功能:从标准输入

stdin

读取用户输入,并将其写入到一个命名管道中
解释:

fgets(buffer, sizeof(buffer), stdin);

用于从标准输入读取一行文本。fgets()是一个标准库函数,用于从指定的文件流中读取一行文本。它的第一个参数是一个字符数组,用于存储读取的文本,第二个参数是数组的长度,第三个参数是要读取的文件流。在本例中,fgets()将从标准输入(stdin)中读取一行文本,并将其存储到buffer数组中。

if(strlen(buffer) > 0) buffer[strlen(buffer)-1] = 0;

用于去掉输入文本中的换行符

\n

,因为fgets()函数会将输入文本中的换行符也读取进来。如果输入文本的长度大于0,则将最后一个字符设为0,以去掉换行符。

ssize_t n = write(wfd, buffer, strlen(buffer));

用于将去掉换行符的输入文本写入到命名管道中。write()是一个系统调用,用于向文件描述符中写入数据。它的第一个参数是文件描述符,第二个参数是要写入的数据,第三个参数是要写入的数据长度。在本例中,wfd是命名管道的文件描述符,buffer是要写入的数据,strlen(buffer)是要写入的数据长度。

assert(n == strlen(buffer));

用于确保写入操作成功,并且写入的数据长度与预期的长度相同。assert()是一个断言宏,用于在条件不满足时引发一个断言错误。在本例中,如果写入操作失败或者写入的数据长度与预期的长度不同,assert()将引发一个断言错误,并停止程序的执行。

(void)n;

用于避免编译器警告未使用变量n的情况。(因为Linux默认编译是release版本的,而release版本的assert会失效)

3.读取端代码

server.cpp

:

#include"comm.hpp"intmain(){//创建管道并检查是否创建好bool r =createFifo(NAMED_PIPE);assert(r);(void)r;

    std::cout <<"server begin"<< std::endl;int rfd =open(NAMED_PIPE, O_RDONLY);//打开文件以只读方式
    std::cout <<"server end"<< std::endl;if(rfd <0)exit(1);//readchar buffer[1024];while(true){
        ssize_t s =read(rfd, buffer,sizeof(buffer)-1);if(s >0){
            buffer[s]=0;
            std::cout <<"client->server# "<< buffer << std::endl;}elseif(s ==0){
            std::cout <<"client quit, me too!"<< std::endl;break;}else{
            std::cout <<"err string: "<<strerror(errno)<< std::endl;break;}}close(rfd);removeFifo(NAMED_PIPE);return0;}

代码功能:使用一个无限循环,从一个命名管道中读取数据,并将读取到的数据打印到标准输出

stdout


解释:

ssize_t s = read(rfd, buffer, sizeof(buffer)-1);

用于从命名管道中读取数据。read()是一个系统调用,用于从文件描述符中读取数据。它的第一个参数是文件描述符,第二个参数是用于存储读取数据的缓冲区,第三个参数是缓冲区的长度。在本例中,rfd是命名管道的文件描述符,buffer是用于存储读取数据的缓冲区,sizeof(buffer)-1是缓冲区的长度。

if(s > 0)

用于判断是否成功读取到数据。如果s大于0,则说明成功读取到数据。

buffer[s] = 0;

用于将读取到的数据转换为字符串,并在字符串末尾添加一个空字符,以便将其打印到标准输出上。

std::cout << "client->server# " << buffer << std::endl;

用于将读取到的数据打印到标准输出上。

else if(s == 0)

用于判断是否已经读取到了命名管道的末尾。如果s等于0,则说明已经读取到了命名管道的末尾,即客户端已经关闭了管道。此时程序将打印一条消息并退出循环。

else

用于处理读取数据时发生的错误。如果s小于0,则说明读取数据时发生了错误。

strerror(errno)

用于获取错误信息,并将其打印到标准输出上。break用于退出循环。

小细节:

    std::cout <<"server begin"<< std::endl;int rfd =open(NAMED_PIPE, O_RDONLY);
    std::cout <<"server end"<< std::endl;

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

我们发现

./server

运行后输出

"server begin"

之后到open阻塞了,而是要等

./client

后才会继续执行,因为读取端打开要等写入端也打开才会继续运行,也就是说两者都要同时打开才能运行这个管道


如有错误或者不清楚的地方欢迎私信或者评论指出🚀🚀


本文转载自: https://blog.csdn.net/DEXTERFUTIAN/article/details/131457236
版权归原作者 侠客cheems 所有, 如有侵权,请联系我们删除。

“【Linux】进程间通信 -- 命名管道 | mkfifo调用”的评论:

还没有评论