【进程通信】用命名管道模拟server和client之间的通信

简介: 【进程通信】用命名管道模拟server和client之间的通信

关于命名管道

当了解了匿名管道的通信机制只能用于具有血缘关系的进程之间时,似乎是出于本能的提出疑问–如果两个进程没有任何关系呢?

假如两个进程之间没有血缘关系,彼此进程就没法轻易拥有对方的文件资源,即不能看到同一份共享资源。这时候我们需要除了pipe函数创建管道的另一种方法,可以支持任意两个进程看到同一份共享资源。于是可以考虑使用命名管道。

命名管道是一个特殊的文件。=可以使不相关的进程之间进行通信,创建管道时创建一个名字,以后其它进程就可以通过这个名字来使用这个管道的另一端。这也是为什么我们称这样的管道为命名管道。命名管道是以一个普通的文件形式出现的,包括创建管道、写管道、读管道。值得注意的是,打开普通文件建立内核级缓冲区后,操作系统会及时刷新里面的数据到磁盘文件中,而管道文件(FIFO)的缓冲区则不会。

创建命名管道

使用命令行创建命名管道(FIFO)

使用mkfifo filename指令创建命名管道,其中filename表示文件名。

命名管道文件又叫FIFO文件,其文件类型为p,表示是一个管道文件。

在程序中创建

命名管道也可以在程序中使用mkfifo函数创建。具体使用方式如下:

#include<sys/types.h>
#include<sys/stat.h>
int mkfifo(const char* pathname,mode_t mode);

其中,参数pathname表示的是创建管道文件的路径,如果pathname是相对的,那么会再去进程的环境变量中去找默认路径。mode表示管道文件的权限,即文件最终的权限为mode& ~umask。如果创建成功返回0,否则-1.

当某个进程要使用管道文件时,像普通文件那样,得先打开文件(open),且要确定以什么方式打开(write or read)。

匿名管道和命名管道的区别

  1. 从代码层面上来说,匿名管道是通过pipe函数创建并打开。而命名管道由mkfifo函数创建,打开得用open
  2. 命名管道可以使两个没有关系的进程建立通信,而匿名管道只能用于具有血缘关系的进程之间
  3. 命名管道本质是磁盘上的一个文件

除了创建方式的不同,命名管道和匿名管道具有相同的意义。

命名管道的打开规则

  • 如果当前是以读的方式打开FIFO文件,而此时没有进程以写的方式打开该FIFO文件时,读端进程就会阻塞,直到有进程以写的方式打开该FIFO。
  • 如果当前是以写的方式打开FIFO文件,而此时没有进程以读的方式打开该FIFO文件时,写端进程就会堵塞,直到有进程以读的方式打开该FIFO。

用命名管道实现server和client通信

下面通过命名管道来实现server和client的通信。具体步骤如下:

  1. 将命名管道的操作方法及其属性封装成一个类,方便代码复用
  2. server端创建管道之后接收数据
  3. client端使用管道发送数据
  4. server端回收管道

NamePipe.hpp

用来封装命名管道的操作以及属性

#include <iostream>
#include <string>
#include <cstdio>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
using namespace std;

const string comm_path = "./myfifo";
#define Creater 1//身份码,1表示创建管道者,2表示使用者
#define User 2
#define Defaultfd -1
#define Read_Mode O_RDONLY
#define Write_Mode O_WRONLY
#define BaseSize 4096//默认读管道的数据大小

class NamePipe
{
private:
  void CreatFifo()
  {
    int res = mkfifo(_fifo_path.c_str(), 0777);
    if (res != 0)
    {
      perror("mkfifo");
    }
  }

  bool OpenNameFifo(int mode)
  {
    _fd = open(_fifo_path.c_str(), mode);
    if (_fd < 0)
      return false;
    return true;
  }

public:
  NamePipe(const string &path, int who)//构造函数,根据身份码来决定谁创建管道
      : _fifo_path(path), _id(who), _fd(Defaultfd)
  {
    if (_id == Creater)
    {
      CreatFifo();//创建管道
      cout << "creat a fifo" << endl;
    }
  }

//读写操作
  bool OpenForWrite()
  {
    return OpenNameFifo(Write_Mode);
  }

  bool OpenForRead()
  {
    return OpenNameFifo(Read_Mode);
  }

  int ReadNamePipe(string &out)
  { // 向管道中读取数据
    char buffer[BaseSize];
    int n = read(_fd, buffer, sizeof(buffer) - 1);
    if (n > 0)
    {
      buffer[n] = '\0';
      out = buffer;
    }
    return n;
  }

  int WriteNamePipe(string in)
  { // 像管道中写数据
    return write(_fd, in.c_str(), in.size());
  }

  ~NamePipe()//析构,删除管道文件,关闭文件
  {
    if (_id == Creater)
    {
      int res = unlink(_fifo_path.c_str()); // 删除管道文件
      if (res != 0)
      {
        perror("unlink");
      }
    }
    if (_fd != Defaultfd)
    { // 关闭文件
      close(_fd);
    }
  }

private:
  int _fd;//FIFO描述fu
  const string _fifo_path;//管道路径
  int _id;//身份码
};

Server.cpp

服务端,读取管道的数据。

#include "NamePipe.hpp"

// Read
int main()
{
    NamePipe fifo(comm_path, Creater);
    // 如果写端没有打开,就会阻塞等待
    cout << "Server 在等待Client打开文件....." << endl;
    sleep(2);
    cout << fifo.OpenForRead() << endl;
    if (fifo.OpenForRead())
    {
        cout << "server 已经打开管道,准备通信" << endl;
        while (true)
        {
            string message = "";
            int n = fifo.ReadNamePipe(message);
            // cout<<message<<endl;
            if (n > 0)
            {
                // 读取成功
                cout << message << endl;
            }
            else if (n == 0)
            {
                // 写端已经关闭,直接退出
                break;
            }
            else
            {
                // 读取失败
                cout << "fifo.ReadNamePipe error" << endl;
                break;
            }
        }
    }
    return 0;
}

client.cpp

客户端,发送数据。

#include "NamePipe.hpp"

// Write
int main()
{
    NamePipe fifo(comm_path, User);
    // 如果读端没有打开,就会阻塞等待
    cout << "Client 在等待Server打开文件....." << endl;
    if (fifo.OpenForWrite())
    {
        cout << "client 已经打开管道,准备通信" << endl;
        while (true)
        {
            string message = "";
            cout << "Please Input: ";
            getline(cin, message);
            fifo.WriteNamePipe(message);
        }
    }
    return 0;
}


相关文章
|
25天前
|
存储 Unix Linux
进程间通信方式-----管道通信
【10月更文挑战第29天】管道通信是一种重要的进程间通信机制,它为进程间的数据传输和同步提供了一种简单有效的方法。通过合理地使用管道通信,可以实现不同进程之间的协作,提高系统的整体性能和效率。
|
25天前
|
消息中间件 存储 供应链
进程间通信方式-----消息队列通信
【10月更文挑战第29天】消息队列通信是一种强大而灵活的进程间通信机制,它通过异步通信、解耦和缓冲等特性,为分布式系统和多进程应用提供了高效的通信方式。在实际应用中,需要根据具体的需求和场景,合理地选择和使用消息队列,以充分发挥其优势,同时注意其可能带来的复杂性和性能开销等问题。
|
2月前
|
存储 Python
Python中的多进程通信实践指南
Python中的多进程通信实践指南
24 0
|
3月前
|
Java Android开发 数据安全/隐私保护
Android中多进程通信有几种方式?需要注意哪些问题?
本文介绍了Android中的多进程通信(IPC),探讨了IPC的重要性及其实现方式,如Intent、Binder、AIDL等,并通过一个使用Binder机制的示例详细说明了其实现过程。
347 4
|
3月前
|
消息中间件 Unix Linux
C语言 多进程编程(二)管道
本文详细介绍了Linux下的进程间通信(IPC),重点讨论了管道通信机制。首先,文章概述了进程间通信的基本概念及重要性,并列举了几种常见的IPC方式。接着深入探讨了管道通信,包括无名管道(匿名管道)和有名管道(命名管道)。无名管道主要用于父子进程间的单向通信,有名管道则可用于任意进程间的通信。文中提供了丰富的示例代码,展示了如何使用`pipe()`和`mkfifo()`函数创建管道,并通过实例演示了如何利用管道进行进程间的消息传递。此外,还分析了管道的特点、优缺点以及如何通过`errno`判断管道是否存在,帮助读者更好地理解和应用管道通信技术。
|
3月前
|
SQL 网络协议 数据库连接
已解决:连接SqlServer出现 provider: Shared Memory Provider, error: 0 - 管道的另一端上无任何进程【C#连接SqlServer踩坑记录】
本文介绍了解决连接SqlServer时出现“provider: Shared Memory Provider, error: 0 - 管道的另一端上无任何进程”错误的步骤,包括更改服务器验证模式、修改sa用户设置、启用TCP/IP协议,以及检查数据库连接语句中的实例名是否正确。此外,还解释了实例名mssqlserver和sqlserver之间的区别,包括它们在默认设置、功能和用途上的差异。
|
4月前
|
消息中间件 Linux 开发者
Linux进程间通信秘籍:管道、消息队列、信号量,一文让你彻底解锁!
【8月更文挑战第25天】本文概述了Linux系统中常用的五种进程间通信(IPC)模式:管道、消息队列、信号量、共享内存与套接字。通过示例代码展示了每种模式的应用场景。了解这些IPC机制及其特点有助于开发者根据具体需求选择合适的通信方式,促进多进程间的高效协作。
176 3
|
4月前
|
开发者 API Windows
从怀旧到革新:看WinForms如何在保持向后兼容性的前提下,借助.NET新平台的力量实现自我进化与应用现代化,让经典桌面应用焕发第二春——我们的WinForms应用转型之路深度剖析
【8月更文挑战第31天】在Windows桌面应用开发中,Windows Forms(WinForms)依然是许多开发者的首选。尽管.NET Framework已演进至.NET 5 及更高版本,WinForms 仍作为核心组件保留,支持现有代码库的同时引入新特性。开发者可将项目迁移至.NET Core,享受性能提升和跨平台能力。迁移时需注意API变更,确保应用平稳过渡。通过自定义样式或第三方控件库,还可增强视觉效果。结合.NET新功能,WinForms 应用不仅能延续既有投资,还能焕发新生。 示例代码展示了如何在.NET Core中创建包含按钮和标签的基本窗口,实现简单的用户交互。
72 0
|
4月前
|
消息中间件 存储 安全
python多进程并发编程之互斥锁与进程间的通信
python多进程并发编程之互斥锁与进程间的通信
|
5月前
|
运维 关系型数据库 MySQL
掌握taskset:优化你的Linux进程,提升系统性能
在多核处理器成为现代计算标准的今天,运维人员和性能调优人员面临着如何有效利用这些处理能力的挑战。优化进程运行的位置不仅可以提高性能,还能更好地管理和分配系统资源。 其中,taskset命令是一个强大的工具,它允许管理员将进程绑定到特定的CPU核心,减少上下文切换的开销,从而提升整体效率。
掌握taskset:优化你的Linux进程,提升系统性能