进程间通信 IPC :管道、信号量、共享内存、消息队列、套接字
进程间通信,两个进程间传递信息
除了套接字,前面几个主要是在同一台主机上两个进程间通信
进程间通信(IPC):信号量_♚陌上花开的博客-CSDN博客_ipc 信号量
进程间通信(IPC):共享内存_♚陌上花开的博客-CSDN博客
进程间通信( IPC):消息队列_♚陌上花开的博客-CSDN博客
管道
管道分为有名管道和无名管道
管道文件的内存大小永远为0,打开文件写入数据,实际上是写入内存中,管道在内核中
向管道写入的数据在内存中
有名管道
mkfifo fifo
使用 | , bash 可以自动创建一个有名管道
如何使用管道
还是可以使用open(打开管道,分配的空间在内存中)read(读取数据) write(写入数据)close(关闭管道)
eg:文件a和文件b,文件a负责向管道中写数据,文件b负责读数据
a.c文件
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
int main()
{
int fd = open("fifo",O_WRONLY);//以只写方式打开管道
printf("fd=%d\n",fd);
if(fd==-1)
{
exit(1);
}
write(fd,"hello",5);//写入5个数据
close(fd);//关闭管道
exit(0);
}
b.c文件
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
int main()
{
int fd = open("fifo",O_RDONLY);//以只读方式打开管道
printf("fd r =%d\n",fd);
if(fd ==-1)
{
exit(1);
}
char buff[128]={0};
int n = read(fd,buff,127);//读取管道中的数据,管道分配的空间在内存中。
printf("read:%s,n=%d\n",buff,n);
close(fd);//关闭管道
exit(0);
}
a,b文件单独执行时会被阻塞住
管道必须在两个进程间执行,单个进程是没法执行的
要求创建时必须两个进程同时打开。
将a.c文件改为从键盘获取数据
int main()
{
int fd = open("fifo",O_WRONLY);//以只写方式打开管道
printf("fd=%d\n",fd);
if(fd==-1)
{
exit(1);
}
printf("input:\n");
char buff[128]={0};
fgets(buff,128,stdin);//从键盘获取数据
write(fd,buff,strlen(buff)-1);//写入数据
close(fd);//关闭管道
exit(0);
}
a(写端)没有输入数据,b(读端)就读不到数据
a(写端)输入数据之后b(读端)才可以读取到数据,a,b正常读写数据后结束
a(写端)没有输入数据,就结束时,b(读端)也会结束
一旦写端关闭了管道,就算没有数据,读端也不会继续阻塞了,读端也会关闭(写端都关闭了,读端再也不可能收到数据,就关闭了)
read的返回值n为0,是唯一一个可以判断管道写端被关闭的条件
循环持续输入数据
b.c文件
int main()
{
int fd = open("fifo",O_RDONLY);//以只读方式打开管道
printf("fd r =%d\n",fd);
if(fd ==-1)
{
exit(1);
}
while(1)
{
char buff[128]={0};
int n = read(fd,buff,127);//读取管道中的数据,管道分配的空间在内存
if(n==0)//若输入的数据为空,结束读取数据,结束程序
{
break;
}
printf("read:%s,n=%d\n",buff,n);
}
close(fd);//关闭管道
exit(0);
}
a.c文件
int main()
{
int fd = open("fifo",O_WRONLY);//以只写方式打开管道
printf("fd=%d\n",fd);
if(fd==-1)
{
exit(1);
}
while(1)
{
printf("input:\n");
char buff[128]={0};
fgets(buff,128,stdin);//从键盘获取数据
if(strncmp(buff,"end",3)==0)
{
break;
}
write(fd,buff,strlen(buff)-1);//写入数据
}
close(fd);//关闭管道
exit(0);
}
循环获取数据,a输入一个,b输出一个
读端关闭,写端就不能再输入数据了,一旦输入数据,就会引发异常,系统会发出信号(SIGPIPE)终止程序。
写端结束,读端依旧可以将残留的数据读取出来
读端结束,写端再写会触发异常
管道 写端关闭,读 返回值为0
管道 读端关闭,写 异常(信号SIGPIPE)
管道 写满 写操作会阻塞
管道 为空 读操作会阻塞
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
#include<signal.h>
void fun(int sig)
{
printf("sig=%d\n",sig);//改变信号SIGPIP的响应方式
}
int main()
{
signal(SIGPIPE,fun);
int fd = open("fifo",O_WRONLY);//以只写方式打开管道
printf("fd=%d\n",fd);
if(fd==-1)
{
exit(1);
}
while(1)
{
printf("input:\n");
char buff[128]={0};
fgets(buff,128,stdin);//从键盘获取数据
if(strncmp(buff,"end",3)==0)
{
break;
}
write(fd,buff,strlen(buff)-1);//写入数据
}
close(fd);//关闭管道
exit(0);
}
我们可以看到,当读端关闭时,写端再写入数据本该引发异常终止,但我们改变了引发异常的信号量(SIGPIPE),读端关闭,写端再数据数据并没有引发异常终止,而是打印信号量代号
三种通讯方式
单工
数据的发送方式是单向的,数据只在一个方向上传输,不能实现双方通信。
eg:电视,
半双工
允许数据在两个方向上传输,但是同一时间数据只能在一个方向上传输,其实际上是切换的单工。
eg:对讲机
全双工
允许数据在两个方向上同时传输。
eg:打电话
管道通讯方式是 半双工
有名管道和无名管道的区别
有名管道可以在任意两个进程间通信,无名只能在父子进程间通信
无名管道
pipe
#include <unistd.h>
/* On Alpha, IA-64, MIPS, SuperH, and SPARC/SPARC64; see NOTES */
struct fd_pair {
long fd[2];
};
struct fd_pair pipe();
/* On all other architectures */
int pipe(int pipefd[2]);//传入两个字符,一个读,一个写
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <fcntl.h> /* Obtain O_* constant definitions */
#include <unistd.h>
int pipe2(int pipefd[2], int flags);
成功返回0,失败返回-1
On success, zero is returned. On error, -1 is returned, errno is set appropriately, and pipefd is left unchanged.
fd[0] 读 fd[1] 写
单个进程获得读写能力
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
int main()
{
int fd[2];
int res = pipe(fd);//fd[0] 读 fd[1] 写 固定的
if(res==-1)
{
exit(1);
}
write(fd[1],"hello",5);
char buff[128]={0};
read(fd[0],buff,127);
printf("buff=%s\n",buff);
close(fd[1]);
close(fd[0]);
exit(0);
}
父子进程间无名管道
fork一个子进程,子进程复制了父进程的读写能力,此时父子进程都具有读写能力,我们可以关闭其中一个能力,使其变为一个读一个写
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
int main()
{
int fd[2];
int res = pipe(fd);//fd[0] 读 fd[1] 写 固定的
if(res==-1)
{
exit(1);
}
pid_t pid = fork();
if(pid==-1)
{
exit(1);
}
if(pid==0)//子进程
{
close(fd[1]);//关闭写端
char buff[128]={0};
read(fd[0],buff,127);
printf("buff=%s\n",buff);
close(fd[0]);//读取完数据,关闭读端
}
else//父进程
{
close(fd[0]);//关闭读端
write(fd[1],"hello",5);
close(fd[1]);//写完数据,关闭写端
}
exit(0);
}