- 使用 Spring Initializr 创建 Spring Boot 应用程序
- 在Spring Boot中配置Cassandra
- 在 Spring Boot 上配置 Tomcat 连接池
- 将Camel消息路由到嵌入WildFly的Artemis上
进程间通信 (Inter-Process Communication,IPC)则是多进程协作的基础。 一般而言,IPC至少需要两方 (如两个进程)参与。 根据信息流动的方向,这两方通常被称为发送者和接收者。
进程间通信的本质其实是:让不同的进程看到了同一份资源。
通过进程的学习我们知道进程之间是具有独立性的,各自进程的数据对方是看不到的,就算是父子进程随意他们的数据是共享的但一旦方式写入。就会发生写时拷贝,数据各自私有一份,所以了进程间进行通信是很困难的。
这也就意味着进程之间要想进行通信,一定要借助第三方资源。这个第三方资源不属于这些进行通信进程中的任何一个。有了这个第三方资源这些进程可以向这个资源里面写入数据或者读取数据,进而实现进程间通信。而这个第三方资源通常是OS提供的内存区域。因此我们可以得出结论:进程间通信的本质是让不同进程看到同一份资源。
管道是Unix中最古老的进程间通信的形式。我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”。
例如:我们使用统计一个文件中代码的行数。
其中cat指令和wc指令是两个程序当他们运行起来时就变成进程了cat通过标准输出将数据放到管道中wc再从管道中拿数据,至此就完成了进程间通信。
注意:用wc指令我们可以计算文件的Byte数、字数、或是列数,若不指定文件名称、或是所给予的文件名为"-",则wc指令会从标准输入设备读取数据。
匿名管道是用于具有血缘关系的进程之间进行通信(多用于父子进程通信)
我们在上面说进程间通信的本质是让不同的进程看到同一份资源,那么使用匿名管道也一定要让父子进程看到同一份资源。匿名管道其实是让父子进程看到了同一份被打开的文件资源,本质其实是一段内核缓冲区。然后了父子进程就可以对这个资源进行读写操作了,进而实现了进程间通信。
注意:
pipe函数是用于创建匿名管道,pipe函数原型如下:
int pipe(int pipefd[2]);
pipe函数的参数是一个输出型参数,数组pipefd是用来获取读端和写端的文件描述符
数组元素 | 含义 |
---|---|
pipefd[0] | 管道读端的文件描述符 |
pipefd[1] | 管道写端的文件描述符 |
pipe函数调用成功返回0失败返回-1.
在创建匿名管道实现父子进程通信的过程中需要pipe函数和fork创建子进程搭配起来。具体步骤如下:
1.父进程调用pipe函数创建匿名管道
2.父进程创建子进程
上面我们说了管道是内核里面的一串缓存。从管道的一段写入的数据,实际上是缓存在内核中的,另一端读取,也就是从内核中读取这段数据。另外,管道传输的数据是无格式的流且大小受限。
看到这,你可能会有疑问了,这两个描述符都是在一个进程里面,并没有起到进程间通信的作用,怎么样才能使得管道是跨过两个进程的呢?
我们可以使用 fork 创建子进程,创建的子进程会复制父进程的文件描述符,这样就做到了两个进程各有两个「 fd[0] 与 fd[1]」,两个进程就可以通过各自的 fd 写入和读取同一个管道文件实现跨进程通信了。
3.管道只能一端写入,另一端读出,所以上面这种模式容易造成混乱,因为父进程和子进程都可以同时写入也都可以读出。那么,为了避免这种情况,通常的做法是:父进程关闭写端,子进程关闭读端。
下面我们通过一段代码进行演示父子进程进行通信
#include <iostream>
#include <unistd.h>
#include <cstring>
#include <stdlib.h>
#include <cstdio>
using namespace std;
//进程间通信
> 这里是引用
int main()
{
// pipefd[2]是一个输出型参数,我们想通过这个输出型参数得到打开的两个fd
// int pipe(int pipefd[2]);
int pipefd[2] = {0}; //让两个进程看到同一份资源
//注意0下标代表的是读取端,1下标代表的写入端
if (pipe(pipefd) != 0)
{
cerr << "pipe erro" << endl;
}
//目的让父进程进行读取,子进程进行写入
if (fork() == 0)
{ //子进程
close(pipefd[0]);
const char *msg = "hello ksy";
int cnt=0;
while (true) //父进程没有sleeep
{
//只要pipe里面有空间就一直写入
// write(pipefd[1], msg, strlen(msg)); //注意不要写入\0;
write(pipefd[1], msg, strlen(msg));
}
}
//父进程
close(pipefd[1]);
while (true)
{
char buffer[64] = {0};
//只要pipe里面有数据就读
ssize_t s = read(pipefd[0], buffer, sizeof(buffer) - 1); //如果read的返回值为0说明子进程将文件描述符给关闭了
if (s > 0)
{
buffer[s] = 0;
printf("child process say to :%s\n", buffer);
}
else if (s == 0)
{
break;
}
else
{
cout << "read fail" << endl;
}
}
//管道是一个只能单向通信的通信信道
//管道是面向字节流的
//仅限于具有亲缘关系的进程
//管道自带同步机制原子性写入
return 0;
}
1.管道的生命周期是随进程的
管道的本质是通过文件进行通信,也就是说管道依赖于文件系统,打开文件的进程退出后该打开的文件会被释放掉。也就是管道的生命周期随进程
2.管道自带同步和互斥机制
我们将多个执行流共同看到的资源叫做临界资源,管道在一个时刻只允许一个进程对其写入和读取。临界资源需要保护,如果我们不对临界资源进行保护,就可能出现一个时刻有多个执行流对同一个管道进行操作。会导致同时读或者写和交叉问题以及数据不一致。
为了避免这些问题OS会对管道的操作进行同步和互斥
3.管道是流式服务
进程A向管道写入数据,进程B向管道里面读取数据是任意的。这就叫做流式服务,与之对应的有数据服务:数据服务有明确的分割拿数据按照报文段拿
1.读端进程将读端关闭而写端进程还一直在向管道写入数据,这个时候OS会将写端进程杀死。
2.写端进程不向管道中写入了,而读端进程一直在读那么此时读端进程会因为管道内没有数据被挂起直到管道内有数据了读端进程才会被唤醒。
3.读端进程不读而写端进程一直在往管道里面写入数据当管道被写满时写端进程会被挂起直到读端进程读取一定量的数据之后写端进程才会向管道中写入数据。
4.写端进程将写端关闭那么读端进程将管道里面的数据读取完毕之后会继续执行后续代码不会被挂起。
下面我们来看一下上面四种情况中的情况一:
读端进程已经将读端给关闭了也就意味着没有进程读取了,此时写端进程都写入也就变得没有什么意义了。OS将其杀死也是非常合理的所以了此时写端进程被异常终止属于异常退出说明写端进程必然收到了某种信号。下面我们来验证一下写端进程收到了什么信号
对应测试代码:
#include <iostream>
#include <unistd.h>
#include <cstring>
#include <stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include <cstdio>
using namespace std;
//进程间通信
int main()
{
// pipefd[2]是一个输出型参数,我们想通过这个输出型参数得到打开的两个fd
// int pipe(int pipefd[2]);
int pipefd[2] = {0}; //让两个进程看到同一份资源
//注意0下标代表的是读取端,1下标代表的写入端
if (pipe(pipefd) != 0)
{
cerr << "pipe erro" << endl;
}
//目的让父进程进行读取,子进程进行写入
if (fork() == 0)
{ //子进程
close(pipefd[0]);
const char *msg = "hello ksy";
int cnt=0;
while (true) //父进程没有sleeep
{
//只要pipe里面有空间就一直写入
// write(pipefd[1], msg, strlen(msg)); //注意不要写入\0;
write(pipefd[1], msg, strlen(msg));
sleep(1);
}
}
//父进程
close(pipefd[1]);
sleep(1);
close(pipefd[0]);//父进程关闭读端
//管道是一个只能单向通信的通信信道
//管道是面向字节流的
//仅限于具有亲缘关系的进程
//管道自带同步机制原子性写入
int status=0;//获取子进程的退出状态
waitpid(-1,&status,0);
printf("child process get signal:%d\n",status&0x7F);
return 0;
}
// sleep(10);
// char c[1024*4+1]={0};
// ssize_t s=read(pipefd[0],c,sizeof(c));
// c[s]=0;
// cout<<c[0]<<endl;
下面我们将程序跑起来:
我们发现它收到了13号信号,下面我们使用kill -l查看13号信号。
因此当发生情况一时OS会向子进程发生SIGPIPE信号将子进程终止
方法一:使用 ulimit -a 查看
方法二:通过man 手册
man 7 pipe
方法三:自己测试
通过上面两种方法我么看到了管道的容量,下面我们自己测试一下管道的容量是多少。下面我么让写端进程一直写入而读端进程不读,这样一来当管道满了写端进程就会被挂起。这样我们就能测试出管道容量的大小了。
对应代码:
#include <iostream>
#include <unistd.h>
#include <cstring>
#include <stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include <cstdio>
using namespace std;
//进程间通信
int main()
{
// pipefd[2]是一个输出型参数,我们想通过这个输出型参数得到打开的两个fd
// int pipe(int pipefd[2]);
int pipefd[2] = {0}; //让两个进程看到同一份资源
//注意0下标代表的是读取端,1下标代表的写入端
if (pipe(pipefd) != 0)
{
cerr << "pipe erro" << endl;
}
//目的让父进程进行读取,子进程进行写入
if (fork() == 0)
{ //子进程
close(pipefd[0]);
int cnt=0;
while (true) //父进程没有sleeep
{
//只要pipe里面有空间就一直写入
// write(pipefd[1], msg, strlen(msg)); //注意不要写入\0;
char ch='k';
write(pipefd[1], &ch, 1);
cnt++;
cout<<cnt<<endl;
}
close(pipefd[1]);
exit(0);
}
//父进程
close(pipefd[1]);
//管道是一个只能单向通信的通信信道
//管道是面向字节流的
//仅限于具有亲缘关系的进程
//管道自带同步机制原子性写入
int status=0;//获取子进程的退出状态
waitpid(-1,&status,0);
printf("child process get signal:%d\n",status&0x7F);
return 0;
}
通过测试可以发现我这个云服务器上写端进程最多写入65536个字节就会被OS挂起也就是说当前这个云服务器上管道的最大容量是65536字节
上面我们说匿名管道只能用于具有血缘关系的进程之间进行通信通常是用于父子进程。一般都是一个进程创建管道然后使用fork创建子进程然后父子进程就可以通信了。但是这具有很强的局限性,如果我们想要两个毫不相关的进程之间进行通信,就要使用命名管道。命名管道是一种特殊的文件,进程打开同一个命名管道文件,此时打开这个文件的进程就看到了同一份资源,所以了他们就可以进程通信了。
注意:
普通文件很难做到通信即使做到了也解决不了一些安全问题
命名管道和匿名管道是一样的都是内存文件并将都不会将数据刷新到磁盘中。
我们可以使用mkfifo创建一个命名管道:
mkfifo fifo
我们可以发现此时创建出来的文件类型是p类型代表该文件是命名管道,下面我们使用这个命名管道实现进程A和进程B直接的通信(其中进程A和进程B是毫不相关的两个进程)
我们在一个终端下每隔一秒将字符串“hello ksy"重定向到管道中,然后新建一个中端让其从管道中读取数据完成毫不相关的两个进程之间进行通信。
同样的我们之前在匿名管道中说过如果读端进程退出写端进程再向管道中写入数据就会变得毫无意义,此时再命名管道这里也是成立的。
在程序中创建命名管道我们可以使用mkfifo函数。mkfifo函数原型如下:
int mkfifo(const char*pathname,mode_t mode);
函数解释:第一个参数
其中mkfifo的第一个参数pathname表示的是要创建命名管道的文件。
注意:
如果pathname以路径的方式给出那么命名管道将在pathnaem路径下创建
如果pathname以文件名的形式给出那么命名管道将会在当前路径下创建(当前路径在文件哪里已经详细解释过了)
第二个参数:
mkfifo的第二个参数mode是管道文件的权限比如我们创建管道文件时将管道文件的权限设置为0666
此时我们发现管道文件对应的权限并不是0666而是0644.这是为什么了。这其实和我们学习指令时的权限掩码有关即umask。实际创建出来的文件的权限其实是mode&(~umask).而umask在普通用户一般是0002所以了实际创建出来的管道文件的权限是0644.如果我们想要创建出来的文件的权限不受umask的影响我们可以在创建管道文件之前使用umask函数将权限掩码设置为0即umask(0)。注意:如果是在某个进程中使用umask(0)只在当前进程有效。不会影响系统的权限掩码
函数返回值:
1.创建成功返回0
2.失败返回-1
下面我们来实现一下服务端和客户端之间的通信,注意进行通信之前先要启动服务端,因为我们要服务端把命名管道创建出来并且以读的方式打开这个文件,而客户端以写的方式打开管道文件并且以写的方式打开。这样服务端就可以收到客户端发过来的数据了。
服务端对应代码如下:
#include"comm.h"
int main(){
//umask(0);//注意尽在当前进程有效不会影响系统的权限掩码
if(mkfifo(MY_FIFO,0666)<0){
cerr<<"mkfifo fail"<<endl;
return 1;
}
//只需要文件操作即可
int fd=open(MY_FIFO,O_RDONLY);
if(fd<0){
cerr<<"open fail"<<endl;
return 2;
}
//业务逻辑可以进行对应的读写
while(true){
char buffer[64]={0};
ssize_t s=read(fd,buffer,sizeof(buffer)-1);
if(s>0){
//会多一个/n
buffer[s]=0;
printf("client:%s\n",buffer);
}
else if(s==0){//对方关闭
cout<<"client quit"<<endl;
break;
}
else{
cout<<"读取失败"<<endl;
break;
}
}
close(fd);
return 0;
}
此时我们将客户端跑起来之后命名管道就已经被创建。所以客户端只需要用写的方式打开这个命名管道即可,这样就可以实现客户端和服务端之间的通信
对应客户端代码:
#include"comm.h"
int main(){
//管道文件已经被创建只需要打开就可以了
int fd=open(MY_FIFO,O_WRONLY);
//不需要创建了
if(fd<0){
cerr<<"open fail"<<endl;
}
//业务逻辑
while(true){
printf("请输入:###########################");
fflush(stdout);
char buffer[64]={0};
//先把标准输入拿道client进程的内部
ssize_t s=read(0,buffer,sizeof(buffer)-1);
if(s>0){
buffer[s-1]=0;
cout<<buffer<<endl;
write(fd,buffer,strlen(buffer));
}
}
close(fd);
return 0;
}
在这里客户端和服务端共有一个头文件,该头文件提供共用管道的文件名
客户端和服务端同时打开
对应comm.h
#pragma once
#include <iostream>
#include<string.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
using namespace std;
#define MY_FIFO "./fifo"
代码编写完毕之后我们先让服务端先跑起来然后了我们就可以看到我们命名管道被创建出来,然后我们在让客户端允许起来。然后我们就可以进行通信了:
其中客户端和服务端的退出关系是:
当客户端退出时服务端将数据读取完毕之后就读不到数据此时服务端也就去执行其他代码去了,当服务端退出之后客户端的写入也就没有任何意义了此时操作系统会向客户端发送13号信号SIGPIPE信号将客户端终止。
注意:命名管道的文件的大小是不会变的因为不会将数据刷新到磁盘中
两个进程之间进行通信并不是只能发送字符串服务端可以对客户端的发送过来的数据进行处理
在这里我们只需要将上面的代码稍微改一下就可以了,改变服务端处理信息的方式即可。
对应server.cc代码:
#include"comm.h"
#include<stdlib.h>
#include<wait.h>
int main(){
//umask(0);//注意尽在当前进程有效不会影响系统的权限掩码
if(mkfifo(MY_FIFO,0666)<0){
cerr<<"mkfifo fail"<<endl;
return 1;
}
//只需要文件操作即可
int fd=open(MY_FIFO,O_RDONLY);
if(fd<0){
cerr<<"open fail"<<endl;
return 2;
}
//业务逻辑可以进行对应的读写
while(true){
char buffer[64]={0};
ssize_t s=read(fd,buffer,sizeof(buffer)-1);
if(s>0){
//会多一个/n
buffer[s]=0;
if(strcmp(buffer,"show")==0){
if(fork()==0){
//子进程
execl("/usr/bin/ls","ls","-l",NULL);
exit(1);
}
waitpid(-1,NULL,0);
}
else if(strcmp(buffer,"run")==0){
if(fork()==0){
//子进程
execl("/usr/bin/sl","sl",NULL);
exit(1);
}
waitpid(-1,NULL,0);
}
else {
printf("client:%s\n",buffer);
}
}
else if(s==0){//对方关闭
cout<<"client quit"<<endl;
break;
}
else{
cout<<"读取失败"<<endl;
break;
}
}
close(fd);
return 0;
}
对应client代码:
#include"comm.h"
int main(){
//管道文件已经被创建只需要打开就可以了
int fd=open(MY_FIFO,O_WRONLY);
//不需要创建了
if(fd<0){
cerr<<"open fail"<<endl;
}
//业务逻辑
while(true){
printf("请输入:###########################");
fflush(stdout);
char buffer[64]={0};
//先把标准输入拿道client进程的内部
ssize_t s=read(0,buffer,sizeof(buffer)-1);
if(s>0){
buffer[s-1]=0;
cout<<buffer<<endl;
write(fd,buffer,strlen(buffer));
}
}
close(fd);
return 0;
}
comm.h
#pragma once
#include <iostream>
#include<string.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
using namespace std;
#define MY_FIFO "./fifo"
大多数语言都支持双向进程通信。例如,在 Python 中,我可以(草率地)执行以下操作: >>> from subprocess import * >>> p = Popen('nslookup',
致力于使用 C++ 在 arduino 和 PC (Win 7) 之间进行通信。使用 WriteFile 和 ReadFile 创建通信或简单地发送或接收数据没有问题。但是当我想以某种方式“协调”沟通
我们正在开发一个基于微服务的应用程序。它们将使用 Helm Package Manager 部署到 kubernetes,并且它们都存储了自己的存储库和 helm chart。以下是我们微服务的名称。
我正在开发一个大型 MVVM 应用程序。我为此使用了 MVVM 轻量级工具包。该应用程序就像一个带有后退和前进按钮的网络浏览器。主视图是一个用户控件。我在主视图用户控件中放置了后退和前进按钮。主视图又
我在 java 和 freepascal(lazarus) 应用程序之间的通信有问题。我使用套接字。它们正确连接。一切都很顺利,直到我想从一个应用程序向另一个应用程序发送一些东西。在java而不是“a
我已经使用客户端套接字和服务器套接字使用C#编写了群聊。 当我使用VS 2017在自己的PC中运行程序(服务器和客户端)时,客户端和服务器之间的通信工作正常。 当我在笔记本电脑中运行客户端程序,并在自
Kubernetes 中两个不同 Pod 之间的通信是如何发生的? 就我而言,我有两个 Pod:前端和后端,它们都有不同的容器。 我希望我的前端 pod 与后端 pod 通信,但我不想使用后端 pod
我正在尝试在浏览器中嵌入的 flash 实例与在 C# WinForms 应用程序中运行的 flash 实例之间进行通信...我收到一个编译错误,内容为: 1119 Access of possibl
鉴于网络上缺乏信息,请问一个问题:我要在 Android 中创建一个应用程序,使用一个数据库应用程序 rails 。为此,我需要一个手动 session 。所以如果有人准备好了示例/教程显示通信 an
我正在编写一个应用程序,它将通过 MySQL 数据库对用户进行身份验证。我已经用 Java (android) 编写了它,但现在正在移植到 Windows 手机。 PHP 文件使用 $get 然后回显
是否可以通过互联网在两个不同设备上的两个不同应用程序之间建立通信。我想从设备 A 上的应用程序点击一个设备 B 上的应用程序,然后从设备 B 上的应用程序获取数据到设备 A 上的应用程序。如果可能,如
这是脚本: 它被放置在其他网站上。 com 并显示一个 iframe。如果有人点击 iframe 中的某个内容,脚本应该将一个 div 写入 othersite 。 com. 所以我的问题是如何做到
你好我是 php 的新手,我用 c++ 编写了整个代码并想在 php 中使用这段代码。所以我为我的代码制作了 dll 以使用它。但是我不能在 php 中使用这个 dll,可以谁能给我完整的代码来使用
我确定之前已经有人问过(并回答过)此类问题,所以如果是这样,请将我链接到之前的讨论... 在 C++ 中,假设我有一个 ClassA 类型的对象,其中包含一个 ClassB 类型的私有(private
我正在尝试使用 ATmega32 进行串行通信。首先,我使用 RS232,使用 USB-to-RS232 建立使用串行终端的接收和传输(在我的例子中是 tera 术语)。无论我从串行终端 Atmega
我找不到适用于 Ruby 的 SSL 实现。 我的部分项目需要服务器和客户端之间的安全通信链接,我希望为此使用 SSL 以创建安全 session 。 谢谢 最佳答案 如果你使用 Ruby 1.9.x
我正在尝试在客户端/服务器之间进行 SSL 通信。 到目前为止,我已经从 keystore 创建了 java.security.cert.X509Certificate。接下来我应该怎么做才能使这次沟
我在与 Windows 上的 USB 设备 通信时遇到问题。我不能使用 libusb 或 WinUSB,因为我有一个特定的驱动程序(Silabs USB 到 UART,这是一个 USB 到串口的桥接器
按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visit the
我发现 xcom 实际上是将数据写入数据库并从其他任务中提取数据。我的数据集很大,将其腌制并写入数据库会导致一些不必要的延迟。有没有办法在不使用 xcom 的情况下在同一 Airflow Dag 中的
我是一名优秀的程序员,十分优秀!