进程间通信之匿名管道和命名管道的理解和实现【Linux】

简介: 进程间通信之匿名管道和命名管道的理解和实现【Linux】

进程间通信

进程间通信的目的

  • 数据传输:一个进程需要将它的数据发送给另一个进程
  • 资源共享:多个进程之间共享同样的资源。
  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
  • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

进程间通信的本质理解

  • 进程间通信的前提,首先需要让不同的进程都能够看到同一块“内存”。
  • 这个“内存”不能隶属于任何进程应该强调共享。

进程间通信的必要性

  单进程无法使用并发能力,更加无法实现多进程协同。

什么是管道

管道是进程通信的其中一种,管道分为两种:

管道

匿名管道pipe

命名管道

  • 管道是Unix中最古老的进程间通信的形式。
  • 我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”。
  • 管道只能单向通信。

  父进程fork之后,父进程会copy一份files_struct给子进程,但是父子进程的文件描述符依旧是指向同一份文件,而这个文件就叫做管道。

  管道某种意义上来说不是文件,是一种方式,进行交流的数据作为临时数据放在内存,实际上是由内核维护的一个缓冲区,在内核中被称为管道缓存,当交流结束后,临时数据就会被释放。所以也可以把管道看作是一个缓冲区。

匿名管道

#include <unistd.h>

功能:创建一无名管道

原型

int pipe(int fd[2]);

参数

fd文件描述符数组,其中fd[0]表示读端, fd[1]表示写端

返回值:成功返回0,失败返回错误代码

管道的实现

  1. 分别以读写的方式打开同一个文件。
  2. fork创建子进程。
  3. 双方进程各自关闭自己不需要的文件描述符(为了实现单向通信,但是如果想实现全双工通信,可以创建多个管道来实现)。

如何做到让不同的进程看到同一份资源?

:fork后让子进程继承—>能够让具有血缘关系的进程进行进程间通信—>常用于父子进程

代码实现

下面这是一个简易的匿名管道的实现,当使用pipe函数时,管道文件是通过 pipe 系统调用创建的。具体来说,pipe 系统调用会在内核中创建一个管道,并返回两个文件描述符,这两个文件描述符分别用于读取和写入管道。

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <cstdlib>
int main()
{
    int fd[2];
    int retpp = pipe(fd);
    if (retpp == -1)
    {
        perror("pipe");
        exit(1);
    }
    int fk = fork();
    if (fk < 0)
    {
        perror("fork");
        exit(2);
    }
    
    if (fk == 0)
    {
        // child: 作读端,首先得关闭写端
        close(fd[1]);
        char buf[1024];
        read(fd[0], buf, sizeof(buf));
        printf("child: %s\n", buf);
    }
    if (fk > 0)
    {
        // parent:作写端,首先得关闭读端
        close(fd[0]);
        char *bufp = "qwweereewrrere";
        write(fd[1], bufp, strlen(bufp));
        return 0;
    }
}

可不可以改变管道文件呢?

下面的代码将文件t.txt作为管道文件。

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <cstdlib>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
    int fd[2];
    int retpp = pipe(fd);
    if (retpp == -1)
    {
        perror("pipe");
        exit(1);
    }
    int fk = fork();
    if (fk < 0)
    {
        perror("fork");
        exit(2);
    }
    int fd1 = open("F:\\t.txt", O_RDWR | O_CREAT, 0666);
    if (fd1 < 0)
    {
        perror("open");
        exit(3);
    }
    if (fk == 0)
    {
        // child: 作读端,首先得关闭写端
        close(fd[1]);
        char buf[1024];
        dup2(fd[0], fd1);
        read(fd[0], buf, sizeof(buf));
        printf("%s\n", buf);
    }
    if (fk > 0)
    {
        // parent:作写端,首先得关闭读端
        close(fd[0]);
        const char *bufp = "qwweereewrrere";
        dup2(fd[1], fd1);
        write(fd[1], bufp, strlen(bufp));
        return 0;
    }
}

用fork来共享管道原理

站在文件描述符角度-深度理解管道

站在内核角度-管道本质

管道的读写规则

  • 当没有数据可读时
  • O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。
  • O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。
  • 当管道满的时候
  • O_NONBLOCK disable: write调用阻塞,直到有进程读走数据
  • O_NONBLOCK enable:调用返回-1,errno值为EAGAIN
  • 如果所有管道写端对应的文件描述符被关闭,则read返回0
  • 如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程
    退出
  • 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
  • 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。

管道提供了访问控制

  • 写端快,读端慢, 写端满了就不能再写了。
  • 写端慢,读端快, 管道没有数据时,读端必须等待。
  • 写端关闭,读端没有数据可读,标识读到了文件结尾。
  • 读端关闭,写端继续写,OS终止写端进程。

管道特点

  • 只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创
    建,然后该进程调用fork,此后父、子进程之间就可应用该管道。
  • 管道提供流式服务
  • 一般而言,进程退出,管道释放,所以管道的生命周期随进程
  • 一般而言,内核会对管道操作进行同步与互斥
  • 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道

命名管道

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

创建命名管道

命名管道可以从命令行上创建,命令行方法是使用下面这个命令:

mkfifo filename

命名管道也可以从程序里创建,相关函数有:

int mkfifo(const char *filename,mode_t mode);

比如我们可以这么创建命名管道:

int main(int argc, char *argv[])
{
  mkfifo("p2", 0644);
  return 0;
}

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

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

命名管道的打开规则

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

代码实现

  下面的代码实现了客户端向命名管道中写入信息,服务端从命名管道中将数据读取出来并打印在终端屏幕上。

// server.cpp
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <cstdlib>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
int main()
{
    srand((unsigned int)time(NULL));
    if (mkfifo("shard", 0666) < 0)
    {
        perror("mkfifo");
        exit(1);
    }
    int sfd = open("shard", O_WRONLY, 0666);
    if (sfd < 0)
    {
        perror("server_open");
        exit(2);
    }
    while (1)
    {
        int a = rand() % 1000;
        char server_put[1024] = {'\0'};
        sprintf(server_put, "%d", a);
        printf("server: %s\n", server_put);
        write(sfd, server_put, sizeof(server_put));
        sleep(2);
    }
    return 0;
}
// client
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <cstdlib>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
int main()
{
    srand((unsigned int)time(NULL));
    int cfd = open("shard", O_RDONLY, 0666);
    if (cfd < 0)
    {
        perror("client_open");
        exit(2);
    }
    while (1)
    {
        char client_in[1024];
        read(cfd, client_in, sizeof(client_in));
        printf("client: %s\n", client_in);
        sleep(1);
    }
    return 0;
}
// makefile
.PHANY:all
all: server client
server:server.cpp 
  g++ -o $@ $^ 
client:client.cpp 
  g++ -o $@ $^
.PHANY:clean
clean:
  rm -f client server shard


    😄 创作不易,你的点赞和关注都是对我莫大的鼓励,再次感谢您的观看😄

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