一、进程间通信:
主要是利用内核空间,来完成两个进程或者多个进程之间的资源和信息的传递。
进程间通信方式(7大类)
1.传统通信方式:
1.无名管道 ---- 使用的队列
2.有名管道 ---- 使用的队列
3.信号 ---------- 异步的方式
2. IPC通信方式(第五代操作系统):
1.消息队列 ---- 管道的集合
2.共享内存 ---- 地址映射的方式
3.信号灯集 ---- 信号灯的集合
3.网络通信
套接字:socket
(1)传统通信之无名管道
附加:
单工通信方式:任何时间点,只能由一方发送给另一方,方向不允许改变
半双工通信方式:同一个时间内,只允许有一方发送给另一方,具有双方通信的能力
全双工通信方式:任意时间点,双方任意可以给对方发送信息。
无名管道的介绍
无名管道是实现亲缘间进程通信的一种方式,属于半双工通信方式,类似于一个水管,只有两端,一个是数据流入段(写段),数据流出段(读段)。
这两个段都是固定的端口,遵循数据的先进先出,数据拿出来后就消失。管道是有有限长度的64*1024(64K)个字节,无名管道,不在文件系统上体现,
数据存在内存之上,进程结束后,数据就会丢失,管道文件不能使用lseek读写指针偏移。
无名管道的原理图:
函数接口
创建一个无名管道(pipe)
头文件:#include 原型:int pipe(int pipefd[2]); 功能:创建一个无名管道,会将读写端两个文件描述符分别封装到fd[0]和fd[1] 参数: fd[0] -----r fd[1] -----w 返回值: 成功返回0; 失败返回-1;
管道注意点
1.如果管道中没有数据,read读取时会阻塞等待数据的到来
#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; } char buf[123] = {0}; ssize_t ret = read(fd[0],buf,sizeof(buf)); if(-1 == ret) { perror("read"); return -1; } printf("读到的数据为%s\n",buf); return 0; }
2.管道符和先进先出的原则,数据读走后就会消失
#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; } write(fd[1],"hello world",11); char buf[123] = {0}; ssize_t ret = read(fd[0],buf,5); if(-1 == ret) { perror("read"); return -1; } printf("读到的数据为%s\n",buf); read(fd[0],buf,6); printf("读到的数据为%s\n",buf); return 0; }
3.管道的大小是64K,管道写满以后再次进行写入会阻塞等待写入,防止有效数据丢失。
#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; } int i = 0; char ch = 'a'; for(i = 0; i < 64*1024;i++) { write(fd[1],&ch,1); } printf("管道已经写满\n"); write(fd[1],&ch,1); //确定管道的大小,以及确定了管道写满之后再次写入会发生什么 return 0; }
4.如果关闭了写入端口,读发生什么情况
1.管道中有数据时,将里面的数据读出来
2.管道中无数据时,管道机制会认为写端关闭,不会再有数据到来,read在做读取时阻塞
没有任何用处,read将不会阻塞等待了,便不会影响进程运行。
#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; } char buf[123] = {0}; char buf1[123] = {0}; write(fd[1],"hello world",11); close(fd[1]); //关闭写端 read(fd[0],buf,sizeof(buf)); //管道有数据,读取管道中的数据返回 printf("buf = %s\n",buf); read(fd[0],buf1,sizeof(buf)); //管道无数据,不阻塞,直接返回 printf("buf = %s\n",buf1); return 0; }
5.如果读端关闭,在进行写入会发生“管道破裂”,
是因为:如果读端关闭,写入将没有任何意义了,并且每次调用write函数写入数据都被称为有效数据。如果写入会造成有效数据的丢失,所以在写入时会出现管道破裂的问题,结束进程
#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); //关闭读通道,写入会发生管道破裂,结束进程 printf("写入成功\n"); return 0; }
使用无名管道实现亲缘间进程通信
因为fork函数创建完子进程后,文件描述符也会被复制过去,相当于父子进程利用相同的文件描述符去操作一个文件指针,进而操作一个文件
#include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #include <stdio.h> #include <string.h> #include <stdlib.h> int main(int argc, char const *argv[]) { //创建无名管道 int fd[2]; int fd1[2]; if(-1 == pipe(fd)) //管道1用于父进程给子进程发消息 { perror("pipe1"); return -1; } if(-1 == pipe(fd1)) //管道2用于子进程给父进程发送消息 { perror("pipe2"); return -1; } pid_t pid = fork(); if(-1 == pid) { perror("fork"); return -1; } if(0 == pid) { //关闭无名管道文件描述符 close(fd[1]); close(fd1[0]); while(1) { char buf[123] = {0}; read(fd[0],buf,sizeof(buf)); if(strcmp(buf,"quit") == 0) { printf("通话结束\n"); exit(0); } printf("父进程说:%s\n",buf); //开始回复消息 printf("请子进程输入:\n"); char buf1[123] = {0}; //接收要发送的数据 fgets(buf1,123,stdin); //必须要去掉\n buf1[strlen(buf1) -1] = '\0'; write(fd1[1],buf1,strlen(buf1)); } } else if(pid > 0) { //父进程 close(fd[0]); close(fd1[1]); while(1) { printf("请父进程输入:\n"); char buf[123] ={0}; fgets(buf,123,stdin); //必须要去掉\n buf[strlen(buf) -1] = '\0'; write(fd[1],buf,strlen(buf)); if(strcmp(buf,"quit") == 0) { printf("通话结束\n"); wait(NULL); exit(0); } char buf1[123] = {0}; read(fd1[0],buf1,sizeof(buf1)); printf("收到子进程发过来的数据%s\n",buf1); } } return 0; }
(2)传统通信方式之有名管道
有名管道是建立在无名管道的基础上,为了完善无名管道只能用于亲缘间进程的缺点来延申出的一种进程间通信的方式,继承无名管道的所有点,有名管道在文件系统中属于一种特殊的管道文件,
虽然在文件系统上有所体现,但是它数据并不存放在磁盘上,而是存储在内存之上,进程结束,数据就丢失了。
有名管道作为一个文件系统上的文件,如果实现非亲缘间进程通信的话,需要open打开这个文件,那么两个进程分别需要以读,写权限打开。如果打开有名管道的释放,不足读写这两个权限。
open会阻塞等待另一个权限的到来。
创建有名管道
第一种方式:linux命令 mkfifo + 有名管道名字 第二种方式:c语言函数接口 头文件:#include #include 原型:int mkfifo(const char *pathname, mode_t mode); 功能:创建一个有名管道 参数:pathname:目标路径及名称 mode: 权限 例如:0666 返回值: 成功返回 0 失败返回 -1
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/wait.h> #include <unistd.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <errno.h> #include <fcntl.h> int main(int argc, char const *argv[]) { //创建有名管道,不具备去检测文件存在则打开文件的功能 if(-1 == mkfifo("./myfifo",0664)) { if(errno == EEXIST) { printf("文件已经存在,直接打开!\n"); } else { perror("mkfifo"); return -1; } } //打开有名管道 int fd = open("./myfifo",O_WRONLY); if(-1 == fd) { perror("open"); return -1; } printf("打开文件成功!\n"); return 0; }
(3)使用有名管道来实现非亲缘间进程之间的通信
//read.c #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/wait.h> #include <unistd.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <errno.h> #include <fcntl.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作为该进程的读取端 myfifo1作为写入端 //打开有名管道 int fd = open("./myfifo",O_RDONLY); if(-1 == fd) { perror("open"); return -1; } int fd1 = open("./myfifo1",O_WRONLY); if(-1 == fd1) { perror("open1"); return -1; } printf("打开两个管道成功!\n"); while(1) { char buf[123] = {0}; read(fd,buf,sizeof(buf)); if(strcmp(buf,"quit") == 0) { printf("通话结束\n"); exit(0); } printf("buf = %s\n",buf); //开始回复消息 printf("请输入:\n"); char buf1[123] = {0}; //接收要发送的数据 fgets(buf1,123,stdin); //必须要去掉\n buf1[strlen(buf1) -1] = '\0'; write(fd1,buf1,strlen(buf1)); if(strcmp(buf1,"quit") == 0) { printf("通话结束\n"); exit(0); } } return 0; }
//write.c #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/wait.h> #include <unistd.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <errno.h> #include <fcntl.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作为该进程的读取端 myfifo1作为写入端 //打开有名管道 int fd = open("./myfifo",O_WRONLY); if(-1 == fd) { perror("open"); return -1; } int fd1 = open("./myfifo1",O_RDONLY); if(-1 == fd1) { perror("open1"); return -1; } printf("打开两个管道成功!\n"); while(1) { //开始发送消息 printf("请输入:\n"); char buf1[123] = {0}; //接收要发送的数据 fgets(buf1,123,stdin); //必须要去掉\n buf1[strlen(buf1) -1] = '\0'; write(fd,buf1,strlen(buf1)); if(strcmp(buf1,"quit") == 0) { printf("通话结束\n"); exit(0); } //开始接收另一个进程发过来的消息 char buf[123] = {0}; read(fd1,buf,sizeof(buf)); if(strcmp(buf,"quit") == 0) { printf("通话结束\n"); exit(0); } printf("接收发送过来的数据为 %s\n",buf); } return 0; }
(4)传统通信方式之信号
信号是什么:
信号在软件层对硬件层中断的一种模拟,是一个异步信号
中断:是一种优先级高的代码事件
linux所提供的信号:
查看所有信号:kill -l
发送信号给进程:kill + -信号码 + 进程PID