为什么要有进程间通信通信机制?
操作系统中,进程间是相互不可见的。操作系统在逻辑上将每个进程隔离开了。一个进程里是不可能看到真实的物理内存地址的。进程间的内存相互隔绝,操作系统这样做就是防止进程间的相互干扰,但是在一些具体的应用情况下需要多个进行相互配合,不同的进程需要进行信息的交互和状态的传递等,因此才会出现各种各样的通信机制。
比如内存映射文件,管道,邮槽等。
注:
通信目的数据传输 资源共享 通知事件 和进程控制
管道
一个进程连接到另一个进程的一个数据流称为管道,它是一种两个进程间进行单向通信的机制。
管道特点:
(1)内核缓冲区
(2) 由两个文件描述符引用,读端,写端。
(3) 数据从管道的写端流入管道,从读端流出。
管道又分为匿名管道和命名管道
匿名管道
(1)只适用于有亲缘关系的进程
(2)半双工(只能一个读一个写)
(3)生命周期随进程
(4)内置同步互斥机制
(5)面向字节流
#include<unistd.h>
功能:创建一无名管道
函数
int pipe(int fd[2]);
参数
fd:文件描述符数组,其中fd[0]表示读端,fd[1]表示写端
返回值:成功返回0,失败返回错误代码
函数调用成功返回r/w两个文件描述符。无需open,但需手动close。
用法
1. 父进程调用pipe函数创建管道,得到两个文件描述符fd[0]、fd[1]指向管道的读端和写端。
2. 父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道。
3. 父进程关闭管道读端,子进程关闭管道写端。父进程可以向管道中写入数据,子进程将管道中的数据读出。由于管道是利用环形队列实现的,数据从写端流入管道,从读端流出,这样就实现了进程间通信。
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
int fd[2] = {0};
if(pipe(fd) == -1){
perror("pipe");
exit(1);
}
while(1)
{
int pid = fork();
if(pid > 0){
//父进程
close(fd[1]);
char buf[1024] = {0};
ssize_t tmp = read(fd[0],buf,sizeof(buf)-1);
if(tmp == -1){
perror("read");
exit(1);
}else if(tmp == 0){
printf("read done\n");
exit(0);
}else{
printf("father say : %s",buf);
}
}else if(pid == 0)
{ //子进程
close(fd[0]);
char tmp[1024] = {0};
printf("child say : ");
fflush(stdout);
read(0,tmp,sizeof(tmp)-1);
if(write(fd[1],tmp,strlen(tmp)) == -1){
perror("write");
exit(1);
}
close(fd[1]);
exit(0);
}else
{ //进程失败
perror("fork");
exit(1);
}
}
return 0;
}
管道读写规则
没有数据可读时
read调用阻塞,进程暂停执行,一直等到有数据来到为止
非阻塞情况下,read调用返回-1
当管道满的时候
write调用阻塞,直到有进程读走数据
非阻塞情况下,调用返回-1
如果所有管道写端对应文件描述符关闭,read返回0
如果所有管道读端对应的文件描述符关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程退出
命名管道
命名管道是一种特殊类型的文件不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存储于文件系统中,通过网络来完成进程之间的通信。
(1)可以应用任意进程
(2)半双工
(3)生命周期随进程
(4)内置同步互斥机制
(5)面向字节流
创建一个命名管道
可以从命令行上创建
mkfifo filename
也可以从程序里创建
int mkfifo(const char* filename,mode_t mode);
参数:filename为所要创建的文件名,mode为文件权限.
返回值:成功返回0,失败返回-1
int main(int argc,char *argv[])
{
mkfifo("p2",0666)
return 0;
}
命名管道的打开规则
如果当前打开操作是为读而打开FIFO时
阻塞直到有相应进程为写而打开该FIFO
非阻塞情况下立刻返回成功
一读多写:任意一个写的内容会被这个进程读到
如果当前打开操作是为写而打开FIFO时
阻塞直到有相应的程序为读而打开FIFO
非阻塞情况下立刻返回失败
一写多读:写到管道的内容只会被某一个进程读到
用命名管道实现server&client通信
server部分
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<fcntl.h>
int main()
{
//read write
//int fd=open("./myfifo",O_RDONLY | O_NONBLOCK);//(阻塞方式O_RDONLY)
int fd=open("./myfifo",O_RDONLY );
if(fd<0){
perror("open");
return 1;
}
printf("open ok\n");
while(1){
char buf[1024]={0};
ssize_t read_size = read(fd,buf,sizeof(buf)-1);
if(read_size<0){
perror("read error");
return 1;
}if(read_size==0)
{
printf("read done\n");
sleep(3);
continue;
//return 0;
}
buf[read_size]='\0';
printf("client:%s",buf);
}
close(fd);
return 0;
}
client部分
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
int main()
{
int fd = open("./myfifo",O_WRONLY);
if(fd<0){
perror("open");
return 1;
}
printf("open ok\n");
while(1){
char buf[1024]={0};
ssize_t read_size= read(0,buf,sizeof(buf)-1);
if(read_size<0){
perror("read");
return 1;
}
if(read_size==0){
printf("read done\n");
return 0;
}
buf[read_size]='\0';
write(fd,buf,strlen(buf));
}
close(fd);
return 0;
}
匿名和命名管道的区别
(1)匿名管道由pipe函数创建并打开
(2)命名管道由mkfifo函数创建,打开用open
(3)FIFO与PIPE之间唯一的区别在它们创建与打开的方式不同,一旦这些工作完成之后,它们具有相同的语义。