1.进程通信的概念
由于每个进程都具有独立的用户地址空间,在用户空间不能互相访问,但进程之间的内核空间实际上是共享的,内核空间作为所有进程共享的第三方,会提供相关的机制,已实现进程间数据包的转发,达到数据共享的目的
2.进程通信的方式
传统通信方式:
1.无名管道
2.有名管道
3.信号
IPC通信方式:
4.消息队列
5.共享内存
6.信号灯集
网络通信:
7.socket套接字
附加:
(1)单双工通信方式:任何时间点,只能由一方发送给另一方,方向不允许改变
(2)半双工通信方式:同一时间内,只允许一方发送给另一方,具有双方通信的能力
(3)全双工通信方式:任意时间点,双方任意给对方发送消息
3 管道
3.1传统通信之无名管道
所谓的管道,就是内核里面的一串缓存。从管道的一段写入的数据,实际上是缓存在内核中的,另一端读取,也就是从内核中读取这段数据。另外,管道传输的数据是无格式的流且大小受限。
3.11无名管道的特点
1.无名管道是实现亲缘间进程通信的一种方式,属于半双工通信方式,类似于一个水管,只有两端,一个是数据流入端(写段),一个是数据流出端(读段),这两个端是固定的的端口,遵循先进先出,数据拿出来就消失,管道是有有限长度的,64*1024(64k)个字节,无名管道,管道文件不能使用lseek读写指针偏移
我们可以使用
fork
创建子进程,创建的子进程会复制父进程的文件描述符,这样就做到了两个进程各有两个「fd[0]
与fd[1]
」文件描述符,两个进程就可以通过各自的 fd 写入和读取同一个管道文件实现跨进程通信了。
对于无名管道,它的通信范围是存在父子关系的进程。因为管道没有实体,也就是没有管道文件,只能通过 fork 来复制父进程 fd 文件描述符,来达到通信的目的
对于命名管道,它可以在不相关的进程间也能相互通信。因为命令管道,提前创建了一个类型为管道的设备文件,在进程里只要使用这个设备文件,就可以相互通信。
3.1.2无名管道的创建
头文件:#include<unistd.h>
原型: int pipe(int pipefd[2]);
功能: 创建一个无名管道,将读写端两个文件描述符分别封装到fd[0]和fd[1]
参数: fd[0]----r;
fd[1]---w;
返回值:
成功返回0
失败返回-1
#include<stdio.h>
#include<unistd.h>
int main(int argc char**argv)
{
int fd[2]={0};
int (pipe(fd)==-1)
{
perror("pipe");
return -1;
}
char buf[123]={0};
size_t ret=read(fd[0],buf,sizeof(buf));
if(-1==ret)
{
perror("read");
return -1;
}
}
注意:
(1)如果管道中没有数据,read读取时会阻塞等待数据的到来
(2)管道遵循先进先出的原则,数据读走就会消失
(3)管道的大小是64k,管道写满以后再次写入会阻塞等待写入,防止有效数据丢失
(4)管道关闭写端:
管道中有数据时,将里面的数据读出来,
管道中无数据时,管道机制认为写端已经关闭,不会在有数据到来,read将不 会阻塞等待 了,便不会影响到进程运行
(5) 管道读端关闭,在进行写入会发生管道破裂,结束进程
#include<stdio.h>
#include<unistd.h>
int main(int argc, char const* argv[])
{
int fd[2] = { 0 };
if (pipe(fd) == -1)
{
perror("pipe");
return -1;
}
close(fd[0]);
char ch = 'a';
write(fd[1], &ch, 1); 读通道已经关闭,写入会发生管道破裂,结束进程
return 0;
}
3.2传统通信方式之有名管道
有名管道是建立在无名管道的的基础上,为了完善无名管道只能用于亲缘间进程的缺点来延伸出的一种进程间通信方式,继承无名管道的所有点,有名管道在文件系统中属于一个特殊的管道文件,虽然在文件系统上有所体现,但是他的数据并不存放在内存上,进程结束,数据就丢失了。
有名管道作为一个文件系统上的文件,如果实现非亲缘间进程通信,需要open打开这个文件,那么这两个进程分别需要已读、写权限打开
3.2.1有名管道的创建
mkfifo+有名管道名字
头文件:#include<sys/types.h>
#include<sys/stat.h>
原型: int mkfifo(const char *pathame,mode_t mode);
功能: 创建一个有名管道
参数: pathame:目标路径及名称;
mode : 权限例:0666;
返回值:
成功返回0
失败返回-1
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<stdlib.h>
int main(int argc, char const* argv[])
{
//创建有名管道,不具备去检测文件存在则打开文件的功能
if (-1 == mkfifo("./myfifo", 0664))
{
if (errno == EEXIST)
{
printf("文件已经存在,直接打开");
}
else
{
perror("mkfifo");
return -1;
}
}
//打开有名管道
int fd = open("./myfifo", O_WRONLY);
if (-1 == fd)
{
perror("open");
return -1;
}
printf("打开文件成功1\n");
return 0;
}
3.2.2使用有名管道来实现非亲缘间进程的通信
进程1:
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/wait.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<fcntl.h>
#include<errno.h>
int main(int argc,char const*argv[])
{
//创建有名管道
if(-1==mkfifo("./myfifo",0664))
{
if(errno==EEXIST)
{
printf("文件已经存在,直接打开\n");
}
else
{
perror("mkfifo");
return -1;
}
}
if(-1==mkfifo("./myfifo1",0664))
{
if(errno==EEXIST)
{
printf("文件已经存在,直接打开\n");
}
else
{
perror("mkfifo1");
return -1;
}
}
//如果进行两个进程间的双方通信,还需要两个有名管道
//myfifo作为该进程的读取端,myfifo作为写入端
int fd=open("./myfifo",O_WRONLY);
if(-1==fd)
{
perror("open");
return -1;
}
int fd1=open("./myfifo1",O_RDONLY);
if(-1==fd)
{
perror("open");
return -1;
}
printf("打开两个管道成功\n");
while(1)
{
//开始发送消息
printf("请输入:\n");
char buf[100]={0}; //临时储存你要发送的数据
fgets(buf,100,stdin);
buf[strlen(buf)-1]= '\0';
write(fd,buf,strlen(buf));
if(strcmp(buf,"quit")==0)
{
printf("通话结束\n");
exit(0);
}
//开始接收另一个进程发送过来的消息
char buf1[100]={0};
read(fd1,buf1,sizeof(buf1));
if(strcmp(buf1,"quit")==0)
{
printf("通话结束\n");
exit(0);
}
printf("接收到发送过数据为:%s\n",buf1);
}
return 0;
}
进程2:
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/wait.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<fcntl.h>
#include<errno.h>
int main(int argc,char const*argv[])
{
//创建有名管道
if(-1==mkfifo("./myfifo",0664))
{
if(errno==EEXIST)
{
printf("文件已经存在,直接打开\n");
}
else
{
perror("mkfifo");
return -1;
}
}
if(-1==mkfifo("./myfifo1",0664))
{
if(errno==EEXIST)
{
printf("文件已经存在,直接打开\n");
}
else
{
perror("mkfifo1");
return -1;
}
}
//如果进行两个进程间的双方通信,还需要两个有名管道
//myfifo作为该进程的读取端,myfifo作为写入端
int fd=open("./myfifo",O_RDONLY);
if(-1==fd)
{
perror("open");
return -1;
}
int fd1=open("./myfifo1",O_WRONLY);
if(-1==fd)
{
perror("open");
return -1;
}
printf("打开两个管道成功\n");
while(1)
{
//开始接收另一个进程发送过来的消息
char buf1[100]={0};
read(fd,buf1,sizeof(buf1));
if(strcmp(buf1,"quit")==0)
{
printf("通话结束\n");
exit(0);
}
printf("接收到发送过数据为 %s\n",buf1);
//开始发送消息
printf("请输入:\n");
char buf[100]={0}; //临时储存你要发送的数据
fgets(buf,100,stdin);
buf[strlen(buf)-1]= '\0';
write(fd1,buf,strlen(buf));
if(strcmp(buf,"quit")==0)
{
printf("通话结束\n");
exit(0);
}
}
return 0;
}
总结
由于每个进程的用户空间都是独立的,不能相互访问,这时就需要借助内核空间来实现进程间通信,原因很简单,每个进程都是共享一个内核空间。
Linux 内核提供了不少进程间通信的方式,其中最简单的方式就是管道,管道分为「匿名管道」和「命名管道」。
无名管道顾名思义,它没有名字标识,无名管道是特殊文件只存在于内存,没有存在于文件系统中,shell 命令中的「
|
」竖线就是无名管道,通信的数据是无格式的流并且大小受限,通信的方式是单向的,数据只能在一个方向上流动,如果要双向通信,需要创建两个管道,再来无名管道是只能用于存在父子关系的进程间通信,无名管道的生命周期随着进程创建而建立,随着进程终止而消失。有名管道突破了无名管道只能在亲缘关系进程间的通信限制,因为使用有名管道的前提,需要在文件系统创建一个类型为 p 的设备文件,那么毫无关系的进程就可以通过这个设备文件进行通信。另外,不管是无名管道还是有名管道,进程写入的数据都是缓存在内核中,另一个进程读取数据时候自然也是从内核中获取,同时通信数据都遵循先进先出原则,不支持 lseek 之类的文件定位操作。