【Linux】单机版QQ之管道中的命名管道

news2024/11/19 1:53:36

还记得上一篇的匿名管道吗?

文章目录

  • 前言
  • 一、命名管道
  • 总结


前言

命名管道是什么呢?

管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
命名管道是一种特殊类型的文件。

一、命名管道

在学习命名管道前我们先看看在命令行创建命名管道的函数mkfifo:

 fifo的意思就是first in first out也就是先进先出的意思,比如我们直接在目录下创建一个命名管道文件:

在文件的权限部分的第一个P代表的是管道文件,下面我们讲讲命名管道的原理:

同样有两个进程,上面的是父进程下面是子进程,父进程的一个3号文件描述符表中记录一个文件的地址,这也是被父进程打开的文件,当我们创建一个子进程时,想让子进程打开和父进程打开的相同的那个文件,对于操作系统来说是不会重新再创建一个相同的文件的,操作系统会查询子进程要打开的文件是否已经被打开,如果找到这个被打开的文件就把这个文件的地址填入子进程的文件描述符表中,这样子进程就指向父进程打开的这个文件了,并且在文件中会有像ret这样的变量,当我们有文件描述符指向这个文件时这个变量就会++这也就是引用计数,关闭文件后就会--。那么如何保证两个毫不相关的进程看到的是同一个文件并打开呢?其实很简单,因为文件的唯一性是用路径表示的,只要让不同的进程通过文件路径+文件名看到同一个文件并打开,就是看到了同一个资源,这就具备了进程间通信的前提

 接下来我们用代码演示命名管道,首先需要创建两个文件client.cc写客户端代码,serve.cc写服务端代码,因为这次我们要实现两个可执行程序,所以我们需要在makefile中生成两个可执行程序,makefile代码如下图:

 .PHONY后面加上all,就是说我的目标文件是all,我们让all只有依赖关系没有依赖方法,这样就会去找server和client的依赖关系,就生成了两个可执行程序。下面我们正式编写代码,还记得我们刚开始讲的mkfifo函数吗?此函数的参数需要路径和选项(下面有C库中的mkfifo函数的说明),对于路径我们直接搞一个const string类型的字符串来保存路径,因为服务端和客户端需要打开同一份文件所以我们再创建一个公共的hpp头文件,把我们刚刚定义的路径放进去:

下面我们再看一下C库中的mkfifo函数说明:

 我们可以看到此函数有两个参数,第一个参数是路径,第二个参数是mode,mode是什么呢?mode_t类型又是什么呢?实际上mode_t就是一种无符号整数,我们在讲文件的时候提到过,就是一种让你控制要创建的文件初始是什么权限,我们默认将权限给成0666。

下面我们编写服务端的代码,不知道大家还记不记得之前说过的,对于权限我们给的是0666但是经过权限掩码的影响会变成其他的,所以我们如果不想被权限掩码所影响就将默认的权限掩码设置为0。

 因为我们在创建管道文件的时候会有可能失败,所以我们用if语句判断一下,函数返回值如果等于0就是成功否则就是失败,失败我们就打印对应的错误然后返回1.下一步就是让服务端开启管道文件,开启后就可以正常通信了:

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<<"creat fifo file sucess"<<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;
    return 0;
}

 开启管道文件很简单,就是打开我们创建的管道文件,这里只需要以只读方式打开就可以。同样要判断打开失败的情况,成功后我们就打印打开管道文件成功。下面我们实现开始正常通信的代码:

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<<"creat fifo file sucess"<<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 to"<<std::endl;
            break;
        }
        else 
        {
            std::cout<<errno<<" : "<<strerror(errno)<<std::endl;
            break;
        }

    }

    //关闭不要的fd
    close(rfd);
    return 0;
}

当我们正常通信的时候需要从缓冲区读数据所以先创建一个缓冲区,缓冲区大小设置为宏放在公共头文件中,因为我们把读到的数据当字符串看,所以在调用read函数的时候要让sizeof-1不要读\0,然后把缓冲区初始化一下对于C语言,直接在0位置放个\0就会认为是空字符串。然后我们判断函数返回值,如果已经读到数据结尾我们就在最后的位置放一个\0,因为我们打印字符串的时候是按照C语言的标准打印,而C语言字符串必须以\0结尾,因为服务端接收客户端发来的消息,所以在打印字符串前面加上客户端的名称。当返回值等于0说明客户端不在写东西了,客户端已经退出了,客户端都退出了就让服务端也退出,else就是读取失败,打印失败原因即可。通信结束后我们关闭管道文件即可。下面我们实现客户端:

#include <iostream>
#include <cerrno>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include "comm.hpp"
#include <assert.h>
int main()
{
    //1.不需要创建管道文件,只需要打开对应的文件即可
    int wfd = open(fifoname.c_str(),O_WRONLY);
    if (wfd<0)
    {
        std::cerr<<errno<<":"<<strerror(errno)<<std::endl;
        return 1;
    }

    close(wfd);
    return 0;
}

客户端不需要创建管道文件,因为服务端已经创建了所以我们和服务端一样打开即可,打开后因为我们的客户端要写入消息所以以只写方式打开,当打开函数的返回值小于0直接打印报错信息,接下来我们实现通信方式:

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;
        buffer[strlen(buffer)-1] = 0;
        if (strcasecmp(buffer,"quit")==0)
        {
            break;
        }
        ssize_t n = write(wfd,buffer,strlen(buffer));
        assert(n>=0);
        (void)n;
    }
    close(wfd);
    return 0;
}

 同样我们要先创建一个缓冲区,然后直接循环写入消息,然后将写入的消息放到我们的缓冲区中,用一个指针接收客户输入的字符串,如果成功了这个指针保存的就是字符串的起始地址,我们在接收字符串的时候并不需要考虑\0,因为fgets这是C语言函数会自动加上\0的,断言一下空指针,然后将指针强转是为了防止出现在release版本变量被定义但是却没有使用的情况。buffer(len-1)是什么意思呢?这里其实是因为我们客户端输入字符串后会按一下回车,而回车也会被放入缓冲区中,这样的话在打印的时候服务端会多一行空白,所以我们把回车删掉就没有问题了。接下来我们让客户端输入quit的时候就退出程序,因为我们在服务端设置的是只要客户端退出服务端也就跟着退出了。然后我们向文件里写数据,把我们缓冲区的数据写到文件中,在这里同样不用考虑\0,因为只有C语言规定字符串后面必须加\0,而write是系统接口不会考虑\0的。做完这一步后我们断言一下函数返回值,不让返回值大于等于0的意思是如果是空串或者错误就不去写入了。然后同样强转一下刚刚的返回值n。最后我们将文件关闭即可,下面我们就试试可以运行吗:

运行的时候我们必须开两个窗口,当创建好两个可执行程序后,我们先运行服务端程序,这个时候程序会卡着不动,因为服务端要等待客户端打开同一个文件所以我们在运行客户端:

 这样我们就完成了命名管道的通信,这里还有一个问题就是当我们运行一次程序后就有了管道文件下一次运行程序会出现文件已经存在的报错,所以我们可以直接在服务端关闭文件后取消链接这个文件:

 这样的话我们每次运行程序就不用先将管道文件删除再运行了。

下面我们将这个命名管道改为用户每输入一个字符,服务端就相应的输出一个字符,大家可以理解为就像我们将手机投屏到电脑上手机上做什么操作电脑屏幕就是什么操作。

因为我们前面的实现每次需要输入回车服务端才会显示消息,现在我们需要不输入回车就写入内容的函数,这里我们用的getch函数,由于getch需要用到ncurses库,所以我们先安装这个库:

(按照ncurses库这个方法我们没有搞定,如果有搞定的小伙伴可以私信我哦~成功的方法在下面我用红字提醒出来了可直接找到后用系统方法)

 如果大家是普通用户的话前面记得加上sudo提权。安装好后我们在刚刚的代码中加上这个库的头文件:

 因为我们是在客户端完成这个操作所以只需要在client.cc文件中包上这个头文件即可。然后我们修改一下client.cc文件中的代码:

 考虑到大家可能不熟悉getch这个函数,下面我们把这个函数的文档找出来:

 修改完成后下面我们把代码运行起来:

 由于编译不通过是因为我们没有引入client的库,所以我们引入一下:

 引入后我们重新生成一下可执行文件:

 运行后我们发现服务端不能正常打印了,其实原因在于函数的返回值问题,我们刚刚看文档人家的返回值是int类型,结果我们用char类型接收了,所以就出错了,下面我们修改一下代码:

由于我们不知道返回值是什么,所以我们先在代码中直接打印一下返回值:

 通过打印我们发现返回值为-1:

 然后我们将代码修改为当返回值为-1我们就继续读取字符,但是在运行的过程中由于此方法有一些错误导致还是不能成功,所以我们下面直接用系统方法不用库方法:

 然后我们在服务端接收的时候不打印前面的客户端名称了,直接就是接收什么打印什么(在这里记得刷新缓冲区):

 然后下面我们运行起来:

 这样我们就实现了一开始的功能,通过以上管道的学习相信大家能明白如何创建命名管道以及使用,上面我们使用的系统方法也是从网络上搜的,如果有什么错误还请见谅。

匿名管道与命名管道的区别:
匿名管道由 pipe 函数创建并打开。
命名管道由 mkfififo 函数创建,打开用 open
FIFO (命名管道)与 pipe (匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义。

命名管道的打开规则:

如果当前打开操作是为读而打开 FIFO
O_NONBLOCK disable :阻塞直到有相应进程为写而打开该 FIFO
O_NONBLOCK enable :立刻返回成功
如果当前打开操作是为写而打开 FIFO
O_NONBLOCK disable :阻塞直到有相应进程为读而打开该 FIFO
O_NONBLOCK enable :立刻返回失败,错误码为 ENXIO


总结

命名管道可用于同一主机上的任意进程间通信,并且管道通信的本质是通过内核中一块缓冲区(内存)时间数据传输,而命名管道的管道文件只是一个标识符,用于让多个进程能够访问同一块缓冲区,并且管道是半双工通信,是可以选择方向的单向通信。命名管道和匿名有一个相同点,就是他们的本质都是内核中的一块缓冲区。同时补充一点,管道的生命周期是随进程的,本质是内核中的缓冲区,命名管道文件只是标识,用于让多个进程找到同一块缓冲区,删除后,之前已经打开管道的进程依然可以通信。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/492367.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一百零七、MySQL数据库的数据备份与数据恢复

MySQL数据库的数据备份与恢复主要有3种方法&#xff0c;前两种都是MySQL dump命令&#xff0c;第三种则是用Navicat工具直接备份。相比而言&#xff0c;第三种方法更加简单&#xff01; 1 方法一&#xff08;MySQL dump命令&#xff09; 1.1 登录MySQL [roothurys22 ~]# mysq…

Maya云渲染如何使用,Maya云渲染流程实操!

Maya 是一款专业的 3D 软件&#xff0c;用于创建逼真的角色和大片的效果&#xff0c;Maya可以加速工作流程&#xff0c;帮助您专注于创造力并按时完成任务。也可以为角色和场景添加精美的细节&#xff0c;并提供让客户满意的优质作品。更有无数业内顶级艺术家依靠 Maya来创作更…

【Halcon】新建程序 读取图片 路径设置

文章目录 1 新建程序2 读取一张图片3 图片路径4 图片格式读取报错5 快速添加 绝对路径 1 新建程序 点击新程序图标&#xff0c;即可新建&#xff1b; 程序另存为&#xff0c;会弹出保存路径 2 读取一张图片 read_image(Image,fabrik)此时工程路径下并没有图片&#xff1b; …

SpringBoot2 集成 ELK 实现日志收集

目录 一 简介 二 ELK 各组件作用 三 ELK 各组件安装 四 Spring Boot2 集成 logstash 一 简介 ELK 即 Elasticsearch、Logstash、Kibana 组合起来可以搭建线上日志系统&#xff0c;本文主要讲解使用ELK 来收集 SpringBoot2 应用产生的日志。 二 ELK 各组件作用 Elasticsea…

基于FPGA和Matlab实现的FFT功能验证

一 、FFT设计验证思路 1、基于Matlab与FPGA的混频sin信号的FFT验证&#xff0c;分别在Matlab和FPGA开发环境上实现相同的FFT功能设计。 2、Matlab平台开发&#xff0c;使用自带的fft函数与相关操作函数&#xff0c;绘制出混频sin信号&#xff0c;经过fft功能处理后的频谱图。 3…

2022 ios APP最新开发测试教程

转载&#xff1a;2022 ios APP最新开发测试教程1.本文详细介绍最新的在windows上进行ios app开发编译打包安装到手机测试的完整流程。介绍ios开发经常遇到的问题和解决方法&#xff0c;包括ios开发证书&#xff0c;ios开发描述文件等。http://kxdang.com/topic/appuploader/ios…

IP报文结构

文章目录 IP报文结构分片 IP报文结构 4位版本号(version): 指定IP协议的版本, 对于IPv4来说, 就是4. 4位头部长度(header length): 类似于TCP4位首部长度&#xff0c;通常填的是0101&#xff08;十进制5&#xff09; 16位总长度(total length): IP数据报整体占多少个字节.这用…

大数据企业应用合作解决方案案例

打造产教融合的就业育人的综合服务平台&#xff0c;给予十余年的数据智能产业实践经验&#xff0c;专注于大数据和人工智能方向。 目前合作的企业案例包括&#xff1a;信访大数据平台解决方案、工业废水处理解决方案、找齐远程监控解决方案、道路运输安全、广电用户服务大数据解…

VS2019 c++ cmake项目 打包并使用 (lib\dlll)

背景 最近项目中经常调用第三方库、带头文件、lib和dll的库&#xff0c;需要使用cmake进行项目管理&#xff0c;之前一直比较糊涂这方面&#xff0c;在这里做一个整理总结 编译汇编过程 静态链接方式&#xff1a; 把lib里面编译好的东西&#xff08;函数、变量等&#xff09…

【Yolo】YoloV5训练自定义模型

【Yolo】Jetson Orin Nano下部署 YoloV5 上一篇博文主要记录了在Jetson Orin Nano下部署YoloV5环境&#xff0c;并运行了yoloV5n.pt模型&#xff0c;本篇在上一篇的基础上&#xff0c;进一步记录如何训练自己的目标模型&#xff0c;我们以一根口香糖盒子为训练对象进行说明。 …

Kali Linux 操作系统安装详细步骤——基于 VMware 虚拟机

1. Kali 操作系统简介 Kali Linux 是一个基于 Debian 的 Linux 发行版&#xff0c;旨在进行高级渗透测试和安全审计。Kali Linux 包含数百种工具&#xff0c;适用于各种信息安全任务&#xff0c;如渗透测试&#xff0c;安全研究&#xff0c;计算机取证和逆向工程。Kali Linux 由…

学习笔记(4)页面开发

目录 1&#xff0c;页面开发1.1&#xff0c;标签类1.2&#xff0c;资源引用1.3&#xff0c;页面跳转 2&#xff0c;开发规范2.1&#xff0c;应用生命周期2.2&#xff0c;页面生命周期&#xff1a;2.3&#xff0c;条件编译 3&#xff0c;注意事项 1&#xff0c;页面开发 1.1&am…

真实业务场景使用-模板模式+策略模式组合

模板和策略设计模式一般是使用最频繁的设计模式&#xff0c;模板的场景主要是处理一系列相同的流程&#xff0c;将这些流程放到模板里&#xff0c;每个流程里的处理可能有一些不一样的地方&#xff0c;则可以抽象出一个方法&#xff0c;由每一个有实际意义的子类实现。 策略模…

从供应链角度看进销存:区别与联系

供应链和进销存是两个紧密相关的概念&#xff0c;它们都涉及到企业在商品贸易中的运作过程。虽然它们有一些相似之处&#xff0c;但是它们也有一些显著的区别。本文将从几个方面探讨供应链和进销存的区别。 一、概念定义 供应链的定义&#xff1a;供应链是一系列的活动&#…

WPF 多媒体MediaElement 的使用(一)

本章讲述MediaElement的简单使用&#xff1a; WPF 中对于多媒体的支持非常完整&#xff0c;可以使用MediaElement 为应用程序添加媒体播放控件&#xff0c;以完成播放音频、视频功能。MediaElement 属于UIElement&#xff0c;同时也支持鼠标及键盘的操作。 想以交互方式停止、…

通达信N字形态选股公式,突破前期高点发出信号

行情经历一波上涨之后回调&#xff0c;然后再次上涨&#xff0c;形态类似于字母N&#xff0c;这就是N字形态。该形态在不同的分析方法中均有描述&#xff0c;如123法则、波浪理论等&#xff0c;只是名称不同而已。 本文的N字形态选股公式&#xff0c;以突破前期波段高点发出信号…

2023年留学基金委(CSC)青年骨干教师出国研修项目解读及建议

5月4日&#xff0c;国家留学基金委&#xff08;CSC&#xff09;公布了2023年青年骨干教师出国研修项目通知&#xff0c;知识人网小编现将其选派工作流程、选派办法、申请材料及说明原文转载并加以解读、提出建议。 知识人网解读及建议 一、2023年的通知精神与往年相比&#xf…

MySQL索引、事务与存储引擎

数据库索引 是一个排序的列表&#xff0c;存储着索引值和这个值对应的物理地址&#xff0c;在这个列表中存储着索引的值和包含这个值的数据所在行的物理地址&#xff08;类似于C语言的链表通过指针指向数据记录的内存地址&#xff09;无需对整个表进行扫描&#xff0c;而是先通…

C++图文安装教程,计算机零基础都能懂

系统&#xff1a;win11 软件&#xff1a;code blocks&#xff06;DEV-CPP 文章目录 一、Dev-Cpp安装步骤1.点击安装包2.选择我们的语言为English3.选择我同意即可4.点击next就可以&#xff0c;最后一个框除非是你之前有写过C的项目&#xff0c;想要清除&#xff0c;否则我们不用…

【Java|golang】2432. 处理用时最长的那个任务的员工

共有 n 位员工&#xff0c;每位员工都有一个从 0 到 n - 1 的唯一 id 。 给你一个二维整数数组 logs &#xff0c;其中 logs[i] [idi, leaveTimei] &#xff1a; idi 是处理第 i 个任务的员工的 id &#xff0c;且 leaveTimei 是员工完成第 i 个任务的时刻。所有 leaveTimei…