Linux-进程间通信(进程间通信介绍、匿名管道原理及代码使用、命名管道原理及代码使用)

server/2024/11/14 14:37:55/

一、进程通信介绍

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

进程=内核数据结构+代码和数据

每个进程都拥有自己独立的PCB与数据,由于进程与进程间是相互独立的,互相看不到也不想看到对方的数据,所以进程间通信的前提是让不同进程看到同一份资源。进程间通信一定是某个进程需要通信,让操作系统创建一个共享资源,而用户不能直接操控操作系统,所以操作系统需要提供许多的系统调用,系统调用不同就会创建出不同的共享资源,那么进程间的通信方式也会不同

1.3进程间通信的方式

管道

  • 匿名管道pipe
  • 命名管道

System V IPC

  • System V 消息队列
  • System V 共享内存
  • System V 信号量

POSIX IPC

  • 消息队列
  • 共享内存
  • 信号量
  • 互斥量
  • 条件变量
  • 读写锁

二、匿名管道

2.1匿名管道原理

当一个进程以读和写的方式分别打开一个文件,由于打开方式的不同,就会创建两个struct file,每个文件都有自己的属性、数据、更重要的是存在内核级的文件缓冲区,两个struct file除了打开方式字段不同,其余信息都是相同的,所以操作系统不会将文件的信息在拷贝一份,两个struct file指向的文件信息是一份。当该进程创建子进程,子进程会继承父进程的文件描述符表,此时父子进程可以对同一个文件进行读取操作,该文件中的缓冲区就相当于一个共享资源,这个缓冲区也成为道,两个进程间就可以进行通信了

当我们想让父进程写入数据,子进程读取数据时,我们只需要关闭父进程的读文件,关闭子进程的写文件即可,这样就可以做到子进程向管道中写数据,父进程向文件中读数据了

【注意】:

  1. 管道只能进行通单向信,及只能一个进程进行读一个进程写,不能让两个进程既可以读也可以写,所以我们需要关闭对应不需要功能的文件描述符(不关闭也是可以的,但是建议关掉,防止误写误读)
  2. 写端写入的数据会保存在内核级文件缓冲区中,直到被读走

2.2 pipe接口(创建管道)
#include <unistd.h>
功能:创建一无名管道
原型
int pipe(int fd[2]);
参数
fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
返回值:成功返回0,失败返回错误代码

int fd[ ]为输出型参数,会带回以读和写方式打开的文件的fd,由于我们知道文件的fd不知道文件的名字,所以称为匿名管道

2.3 代码理解(父进程读,子进程写)
#include<iostream>
#include<unistd.h>
#include<cerrno>
#include<cstring>
#include <sys/types.h>
#include<string>
#include<sys/wait.h>
#include <sys/types.h>
#define SIZE 1024void chilewrite(pid_t wfd)
{while(1){std::string message="father I am your child process!";write(wfd,message.c_str(),message.size());sleep(2);}
}void fathread(pid_t rfd)
{char buffer[SIZE];while(1){int n=read(rfd,buffer,sizeof(buffer)-1);if(n>0){buffer[n]='\0';std::cout<<buffer<<std::endl;}else if(n==0){// 如果read的返回值是0,表示写端直接关闭了,我们读到了文件的结尾std::cout<<"写端关闭了"<<std::endl;break;}else{std::cerr<<"read err"<<std::endl;break;}}
}int main()
{//创建管道int pipefd[2];int n=pipe(pipefd);if(n<0){std::cerr<<"errno:"<<errno<<" errstring:"<<strerror(errno)<<std::endl;return 1;}//创建子进程pid_t id=fork();if(id==0){//子进程std::cout<<"子进程准备就绪,准备写入数据了"<<std::endl;close(pipefd[0]);chilewrite(pipefd[1]);close(pipefd[1]);exit(0);}//父进程std::cout<<"父进程准备就绪,准备读取数据了"<<std::endl;close(pipefd[1]);fathread(pipefd[0]);close(pipefd[0]);//进程等待int status=0;pid_t rid=waitpid(id,&status,0);if(rid>0){std::cout << "wait child process done, exit sig: " << (status&0x7f) << std::endl;std::cout << "wait child process done, exit code(ign): " << ((status>>8)&0xFF) << std::endl;}return 0;
}
2.4 管道的四种情况
  1. 如果管道内部是空的并且写端没有关闭,此时读取条件不具备,读端会被阻塞,等待写入数据
  2. 如果管道内部被写满了并且读端没有关闭,此时写入条件不具备,写端会被阻塞,等待读端读取数据
  3. 如果管道一直在读,但是已经关闭了写端,此时读端read返回值会一直读到0,表示读到了文件末尾
  4. 如果管道一直在写,但是已经关闭了读端,此时OS会直接使用13号信号杀死进程,代表进程异常
2.5 管道的五种特征
  1. 匿名管道只能进行有血缘关系的进程之间进行通信,因为匿名管道是依靠子进程继承父进程的文件描述符表实现的(通常用于实现父子进程之间的通信)
  2. 管道内部自带进程的同步机制,多执行流执行代码时具有明显的顺序性,写入管道的数据直到被读取之前会保持在管道缓冲区中(如果缓冲区未满),而读取操作则会等待直到有数据可读,这种机制避免了同时读写导致的数据损坏问题
  3. 管道文件的生命周期是随进程的,当所有打开该文件的进程都退出后,该文件资源也会被释放
  4. 管道文件在进行通信时是面向字节流的,读与写的次数不是一 一匹配的,数据没有明确的分割,一次拿多少数据都行
  5. 管道通信是一种特殊的半双工模式。半双工通信允许数据在两个方向上传输,但不能同时进行。这意味着在任何时候,数据只能在一个方向流动。一旦一方开始发送数据,另一方必须等待接收完毕后才能开始发送。全双工通信允许数据同时在两个方向上进行传输,无需等待。由于半双工模式是可以双向传输数据的,但是管道只能单向通信,所以是特殊的半双工模式
2.6 管道的读写规则
  • 当没有数据可读时

        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将不再保证写入的原子性。

PIPE_BUF一般至少为512个字节,在Linux下,PIPE_BUF为4096个字节,当写入的的数据量不大于PIPE_BUF时,写入操作是安全的,不会发生写一半数据就被读走的情况,如果写入的数据量大于PIPE_BUF,则可能会发写入的数据提前被读走一部分

三、命名管道

匿名管道只能实现具有血缘关系进程之间的通信,而命名管道可以实现两个毫无相关的进程之间的通信,下面先介绍一下命名管道的原理

3.1命名管道原理

        当一个进程以读的方式打开一个文件,该进程会有自己的进程PCB和文件描述符表,同时会创建一个struct file

        当另一个进程以写的方式也打开这个文件,该进程也会有自己的进程PCB、文件描述符表,并且也会创建一个struct file,但是由于这两个进程打开的文件是一样的,而struct file也就是打开文件的方式不同,所以两个struct file都是指向的同一份文件信息,也指向了同一个内核级文件缓冲区,那么这两个毫无关系的进程就指向了同一段空间,就可以进行通信了。

        怎么确保两个进程打开的是同一个文件呢?---------文件的路径

3.2 mkfifo接口
  • 命名管道可以从命令行上创建,命令行方法是使用下面这个命令:
mkfifo filename

  • 命名管道也可以从程序里创建,相关函数有:
int mkfifo(const char *filename,mode_t mode);#参数:filename为文件名 mode为创建文件的权限
#头文件:#include <sys/types.h>#include <sys/stat.h>#返回值:成功返回0,失败返回-1,并且设置错误码
3.2 代码使用举例

namedpipe.hpp:

#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <cerrno>
#include <unistd.h>
#include <iostream>
#include <fcntl.h>#define Defaultfd -1
#define Creater 1
#define User 2
#define SIZE 128
#define Read O_RDONLY
#define Write O_WRONLYconst std::string path = "./myfifo";
class NamedPipe
{
private:bool OpenNamePipe(int mode){_fd = open(_fifo_path.c_str(), mode);if (_fd < 0)return false;return true;}public:NamedPipe(const std::string path, int who): _fifo_path(path), _id(who), _fd(Defaultfd){if (_id == Creater){int ret = mkfifo(_fifo_path.c_str(), 0666);if (ret != 0){std::perror("mkfifo");}std::cout << "Creater creat namedpipe!" << std::endl;}}bool OpenforRead(){return OpenNamePipe(Read);}bool OpenforWrite(){return OpenNamePipe(Write);}int ReadNamedPipe(std::string *out){char buffer[SIZE];int n = read(_fd, buffer, sizeof(buffer) - 1);if (n > 0){buffer[n] = '\0';*out = buffer;}return n;}int WriteNamedPipe(std::string& in){return write(_fd,in.c_str(),in.size());}~NamedPipe(){if (_id == Creater){int ret = unlink(_fifo_path.c_str());if (ret != 0){std::perror("unlink");}std::cout << "Creater free namedpipe!" << std::endl;}}
private:std::string _fifo_path;int _id;int _fd;
};

client.cpp:

#include "namedpipe.hpp"
int main()
{// 以使用者的身份打开NamedPipe fifo(path, User);if (fifo.OpenforWrite()){std::cout << "Client open fifopipe for write!" << std::endl;while (true){std::cout << "Please enter message" << std::endl;std::string message;std::getline(std::cin, message);fifo.WriteNamedPipe(message);}}return 0;
}

server.cpp:

#include "namedpipe.hpp"
int main()
{// 以创建者的身份打开NamedPipe fifo(path, Creater);if (fifo.OpenforRead()){std::cout << "Server open fifopipe for read!" << std::endl;while (true){std::string message;int n = fifo.ReadNamedPipe(&message);if (n > 0){std::cout << "Client:" << message << std::endl;}else if (n == 0){std::cout << "写端关闭了,读端也要关闭!" << std::endl;break;}else if (n < 0){std::perror("read"); break;}}}return 0;
}

结果展示:


http://www.ppmy.cn/server/25751.html

相关文章

数据结构-树和森林之间的转化

从树的二叉链表的定义可知&#xff0c;任何一棵和树对应的二叉树&#xff0c;其根节点的右子树必为空。这里我们举三个树&#xff0c;将这个由三个树组成的森林组成二叉树是这个样子的。 下面我们说明一下详细过程&#xff0c;首先将每个树转化为二叉的状态&#xff0c;如图所示…

HTTP网络协议,接口请求的内容类型 content-type(2024-04-27)

1、简介 Content-Type&#xff08;内容类型&#xff09;&#xff0c;一般是指网页中存在的 Content-Type&#xff0c;用于定义网络文件的类型和网页的编码&#xff0c;决定浏览器将以什么形式、什么编码读取这个文件&#xff0c;这就是经常看到一些 PHP 网页点击的结果却是下载…

jquery的基本使用和优缺点

jQuery是一个快速、简洁的JavaScript库&#xff0c;它简化了HTML文档遍历、事件处理、动画和Ajax交互。jQuery的目标是“Write less, do more”&#xff0c;即用更少的代码实现更多的功能。官网&#xff1a;https://api.jquery.com/ 基本使用 下面举一个简单的例子来说明jQue…

k8s:精通 Pod 操作的关键命令

在Kubernetes&#xff08;K8s&#xff09;中&#xff0c;Pod是最基本的部署单元&#xff0c;包含了运行应用所需要的容器、存储、网络等资源。精通Pod操作的关键命令对于有效地管理和维护Kubernetes集群至关重要。以下是一些关键的Pod操作命令&#xff1a; 查看Pod列表&#x…

qt环境下给lineEdit设置数值精度为0.5

在Qt环境中&#xff0c;要为QLineEdit控件设置数值输入的精度为0.5&#xff0c;即允许用户输入以0.5为步进单位的数值&#xff0c;通常并不直接通过QLineEdit本身来实现&#xff0c;因为QLineEdit默认用于接收任意文本输入。为了达到您的需求&#xff0c;您可以采取以下两种方法…

小红书笔记的规则权重算法7个要点

1.笔记原创度 小红书平台非常重视用户创作的独特性和原创性。因此&#xff0c;在评估笔记的权重时&#xff0c;原创度是一个重要的考量因素。用户可以通过提供独特的观点、个人经验和创意内容来提高笔记的原创度。 2.笔记内容是否违规 小红书作为一个社区平台&#xff0c;对用户…

月之暗面Kimi推出的全新智能体功能“Kimi+”

Kimi昨晚推出的全新智能体功能“Kimi”&#xff0c;这款产品在设计时考虑得非常周到&#xff0c;首批功能就已经展现出了极高的实用性和创新性。 首先&#xff0c;Kimi的商品挑选功能“什么值得买驱动”非常符合现代消费者的需求。在海量信息中筛选出有价值、符合个人喜好的商…

关于Modbus TCP 编码及解码方式分析

一.Modbus TCP 基本概念 1.基本概念 ①Coil和Register   Modbus中定义的两种数据类型。Coil是位&#xff08;bit&#xff09;变量&#xff1b;Register是整型&#xff08;Word&#xff0c;即16-bit&#xff09;变量。 ②Slave和Master与Server和Client   同一种设备在不同…