1.为什么要进程间通信?
- 数据传输:一个进程需要将它的数据发送给另一个进程
- 资源共享:多个进程之间共享同样的资源。
- 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
- 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
2.进程间通信是如何做到的?
进程运行时具有独立性,进程间通信,一定要借助第三方(OS)资源。OS一定要提供一段内存区域,能够被双方进程看到。通信的本质就是“数据的拷贝”。
进程A->数据“拷贝”给OS ->OS->数据“拷贝”给进程B
3.管道
我们把从一个进程连接到另一个进程的一个数据流称为一个管道。所谓的管道,就是内核里面的一串缓存。从管道的一端写入的数据,实际上是缓存在内核中的,另一端读取,也就是从内核中读取这段数据。
3.1匿名管道
管道只能 进行单方向通信
上面所说的管道没有名字,称为匿名管道。想要创建匿名管道,需要使用函数pipe();
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main()
{
int fd[2] = {0};//用来保存pipe返回的两个文件描述符
if(pipe(fd) < 0)
{
//管道创建失败
perror("pipe!");
return 1;
}
// printf("fd[0]: %d\n", fd[0]);
// printf("fd[1]: %d\n", fd[1]);
pid_t id = fork();
//以子进程写,父进程读为例
if(id == 0)
{
//child
const char* msg = "hello father, I am child!";
close(fd[0]);//子进程关闭读端,保留写端
int count = 30;
while(count--)
{
sleep(1);
write(fd[1], msg, strlen(msg));
}
close(fd[1]);
exit(0);
}
//father
close(fd[1]);//父进程关闭写端,保留读端
char buff[64];
while(1)
{
ssize_t s = read(fd[0], buff, sizeof(buff));//ssize_t是有符号整数,size_t是无符号整数
if(s > 0)
{
buff[s] = '\0';
printf("child send to father# %s\n", buff);
}
else if (s == 0)
{
printf("read file\n");
break;
}
else
{
perror("read\n");
break;
}
}
int status = 0;
waitpid(id, &status, 0);
printf("child quit!: sig: %d\n", status & 0x7F);
return 0;
}
父子进程通信时,能不能创建全局缓冲区来完成通信呢?
- 绝对不可以的,进程运行具有独立性。任何一方对数据的修改另一方是不可以看到的。
管道的特点
-
管道内部已经自动提供了互斥与同步机制,就不会出现写到一半就开始读
read->管道里面没有数据或write->管道里面没有空间,对应进程会被挂起
-
如果写端关闭了,读端就会read到返回值0代表文件结束
-
如果打开文件的进程退出了,所打开的文件也就会被释放掉。文件的生命周期随进程
-
管道是提供流式服务
-
管道是半双工通信
全双工(人在吵架的时候,你说我也说) 半双工(人在正常沟通的时候,一个人说,一个人听)
-
匿名管道,适合具有血缘关系的进程进行进程间通信,常用于父子
不write,一直read,read阻塞。
不read,一直write,write阻塞
write写完,关闭写端,read返回值0
read端关闭,一直写,写方(child)被OS杀掉,因为写入无意义。子进程属于异常退出,收到了信号。
管道是一段内存缓冲区,肯定是有大小的,下面我么来试试管道有多大
经过测试,我当前系统的管道大小为65536字节
3.2命名管道
上面的匿名管道通信,适用于具有血缘关系的进程间通信。那么两个互不相干的进程怎么实现进程间通信呢?
这就用到了命名管道。命名管道有名字,那么不同进程就可以通过名字打开同一个文件了,也就看到了同一份资源。
创建命名管道方式1命令行创建
创建命名管道方式2程序里面创建
-
测试代码1
common.h
#pragma once #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #define FILE_NAME "myfifo"
server.h
#include "common.h" int main() { if(mkfifo(FILE_NAME, 0644) < 0) { perror("mkfifo"); return 1; } int fd = open(FILE_NAME, O_RDONLY); if(fd < 0) { //open失败 perror("open error!\n"); return 2; } char msg[128]; while(1) { msg[0] = 0;//先清空msg里面的内容 ssize_t s = read(fd, msg, sizeof(msg)-1); if(s > 0) {//读成功了 msg[s] = 0; printf("client# %s\n", msg); } else if(s == 0) {//读完了 printf("client quit!\n"); break; } else { //读失败了 perror("read error\n"); break; } } close(fd); return 0; }
client.c
#include "common.h" int main() { int fd = open(FILE_NAME, O_WRONLY); if(fd < 0) { perror("open error\n"); return 1; } char msg[128]; while(1) { msg[0] = 0; printf("Please Enter# "); fflush(stdout); ssize_t s = read(0, msg, sizeof(msg)-1); if(s > 0) { msg[s] = 0; write(fd, msg, strlen(msg)); } } close(fd); return 0; }
-
测试代码2
一个进程控制另一个进程完成任务
server.c
#include "common.h" #include <stdlib.h> #include <sys/wait.h> int main() { if(mkfifo(FILE_NAME, 0644) < 0) { perror("mkfifo"); return 1; } int fd = open(FILE_NAME, O_RDONLY); if(fd < 0) { //open失败 perror("open error!\n"); return 2; } char msg[128]; while(1) { msg[0] = 0;//先清空msg里面的内容 ssize_t s = read(fd, msg, sizeof(msg)-1); if(s > 0) {//读成功了 msg[s-1] = 0;//防止空行,相当于把\n改成'\0' printf("client# %s\n", msg); if(fork() == 0) { //创建一个子进程完成一个功能 execlp(msg, msg, NULL); exit(1); } waitpid(-1, NULL, 0); } else if(s == 0) {//读完了 printf("client quit!\n"); break; } else { //读失败了 perror("read error\n"); break; } } close(fd); return 0; }
其余代码与测试代码1相同
-
测试代码3
一个进程让另一个进程做简单的数字计算
server.c
#include "common.h" #include <stdlib.h> #include <sys/wait.h> int main() { if(mkfifo(FILE_NAME, 0644) < 0) { perror("mkfifo"); return 1; } int fd = open(FILE_NAME, O_RDONLY); if(fd < 0) { //open失败 perror("open error!\n"); return 2; } char msg[128]; while(1) { msg[0] = 0;//先清空msg里面的内容 ssize_t s = read(fd, msg, sizeof(msg)-1); if(s > 0) {//读成功了 msg[s-1] = 0;//防止空行,相当于把\n改成'\0' printf("client# %s\n", msg);//3 + 5 char* p = msg; char* lable = "+-*/%";//保存分隔符 int flag = 0; while(*p) { switch(*p) { case '+': flag = 0; break; case '-': flag = 1; break; case '*': flag = 2; break; case '/': flag = 3; break; case '%': flag = 4; break; } p++; } char* data1 = strtok(msg, "+-*/%"); char* data2 = strtok(NULL, "+-*/%"); //转成整数 int x = atoi(data1); int y = atoi(data2); int z = 0; switch(flag) { case 0: z = x + y; break; case 1: z = x - y; break; case 2: z = x * y; break; case 3: z = x / y; break; case 4: z = x % y; break; } printf("%d %c %d = %d\n", x, lable[flag], y, z); } else if(s == 0) {//读完了 printf("client quit!\n"); break; } else { //读失败了 perror("read error\n"); break; } } close(fd); return 0; }
其余代码同测试代码1
-
测试代码4
本地有一个file.txt的文件,client将file.txt的内容放进管道。server将管道中的数据读取出来,在本地创建一个file-bak.txt,把读取的内容写到file-bat.txt
server.c
#include "common.h" #include <stdlib.h> #include <sys/wait.h> int main() { if(mkfifo(FILE_NAME, 0644) < 0) { perror("mkfifo"); return 1; } int fd = open(FILE_NAME, O_RDONLY); if(fd < 0) { //open失败 perror("open error!\n"); return 2; } int out = open("file-bak.txt", O_CREAT|O_WRONLY, 0644); char msg[128]; while(1) { msg[0] = 0;//先清空msg里面的内容 ssize_t s = read(fd, msg, sizeof(msg)-1); if(s > 0) {//读成功了 write(out, msg, s); } else if(s == 0) {//读完了 printf("client quit!\n"); break; } else { //读失败了 perror("read error\n"); break; } } close(fd); close(out); return 0; }
client.c
#include "common.h" int main() { int fd = open(FILE_NAME, O_WRONLY); if(fd < 0) { perror("open error\n"); return 1; } int in = open("file.txt", O_RDONLY); char msg[128]; while(1) { msg[0] = 0; ssize_t s = read(in, msg, sizeof(msg)); if(s == sizeof(msg)) { //没读完呢,msg存不下file.txt里面的全部内容,先把目前读到的写进管道然后继续读 write(fd, msg, s); } else if(s < sizeof(msg)) { //读完了 write(fd, msg, s); printf("read end of file\n"); break; } else { break; } } close(in); close(fd); return 0; }
common.h同测试代码1
4.匿名管道与命名管道的区别
匿名管道由pipe函数创建并打开。
命名管道由mkfifo函数创建,打开用open
FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义