七:进程间通信
一:分类
1、古老的通信方式
无名管道 有名管道 信号
2、IPC对象通信 system v BSD suse fedora kernel.org unix
消息队列(用的相对少,这里不讨论)
共享内存
信号量集
3、socket通信
网络通信
线程信号,posix sem_init
特列:古老的通信方式中信号是唯一的异步通信
所有的通信方式中共享内存是唯一的最高效
二:管道
管道 – >无名管道、有名管道
无名管道 – >pipe – >只能给有亲缘关系进程通信
有名管道 – >fifo – >可以给任意单机进程通信
管道的特性:
1、管道是 半双工的工作模式
2、所有的管道都是特殊的文件不支持定位操作。lseek->> fd fseek ->>FILE*
3、管道是特殊文件,读写使用文件IO。fgets,fread,fgetcopen,read,write,close;
1,读端存在,一直向管道中去写,超过64k,写会阻塞。
2,写端是存在的,读管道,如果管道为空的话,读会阻塞。
3.管道破裂:如果 管道的读端关闭(即没有进程/线程再读),而 写端再写,此时操作系统会向写入者发送 SIGPIPE 信号,导致程序崩溃(默认行为)。
4.read 0 :写端关闭,读端读,读完内容之后还会向下读,如果管道没有内容,read 0 ;
//2和4的区别
| 状态 | 管道是否有数据 | 写端状态 | `read()`返回值 | 说明 |
| ------ | ------- | ----- | ----------- | ----------- |
| 管道为空 | ❌ 没数据 | ✅ 打开 | 阻塞或 -1(非阻塞) | 只是没数据,等待 |
| 管道内容为空 | ❌ 没数据 | ❌ 已关闭 | 0 | 表示 EOF,不能再读 |
使用框架:创建管道 ==》读写管道 ==》关闭管道
1、无名管道 ===》管道的特例 ===>pipe函数
特性:1.1 亲缘关系进程使用
1.2 有固定的读写端
三:创建/打开管道:pipe
#include <unistd.h>
int pipe(int pipefd[2]);
功能:创建并打开一个无名管道
参数:pipefd[0] ==> 无名管道的固定读端
pipefd[1] ==> 无名管道的固定写端
返回值:成功 0, 失败 -1;
注意事项:1、无名管道的架设应该在fork之前进行。
无名管道的读写:===》文件IO的读写方式。
读: read()
写: write()
关闭管道: close();
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char **argv)
{
int fd[2]={0};
int ret = pipe(fd);
pid_t pid = fork();
if(pid>0)
{
//父进程写管道,不读.所以关闭管道的读段.
close(fd[0]);
char buf[]="hello";
sleep(3);
write(fd[1],buf,strlen(buf));
exit(0);
}
else if(0 == pid)
{
//子进程读管道,不写.所以关闭管道的写段.
close(fd[1]);
char buf[50]={0};
// 读阻塞. 父进程会等待一会,在写管道 .
read(fd[0],buf,sizeof(buf));
printf("pipe %s\n",buf);
exit(0);
}
else
{
perror("fork");
exit(1);
}
//system("pause");
return 0;
}
3.1写堵塞
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char **argv)
{
int fd[2] = {0};
int ret = pipe(fd);
pid_t pid = fork();
if (pid > 0)
{
//父进程写管道,不读.所以关闭管道的读段.
close(fd[0]);
int i = 0;
char buf[1024] = {0};
memset(buf, 'a', sizeof(buf));
//这个地方会写阻塞, 管道大小64K
for (i = 0; i < 65; ++i)
{
write(fd[1], buf, sizeof(buf));
printf("i is %d\n", i + 1);
}
}
else if (0 == pid)
{
//子进程读管道,不写.所以关闭管道的写段.
close(fd[1]);
int i = 5;
while (i--)
{
sleep(1);
}
}
else
{
perror("fork");
exit(1);
}
// system("pause");
return 0;
}
3.2管道破裂
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char **argv)
{
int fd[2] = {0};
int ret = pipe(fd);
pid_t pid = fork();
if (pid > 0)
{
//父进程写管道,不读.所以关闭管道的读段.
close(fd[0]);
char buf[] = "hello";
printf("father id:%d\n", getpid());
sleep(5); //确保子进程 关闭管道的读段
//借助gdb 观测管道破裂
write(fd[1], buf, strlen(buf)); // 管道破裂 ,当前进程结束,异常
printf("------------\n");
}
else if (0 == pid)
{
printf("child %d\n", getpid());
//子进程读管道,不写.所以关闭管道的写段.
close(fd[1]);
close(fd[0]); //读段关闭
int i = 5;
}
else
{
perror("fork");
exit(1);
}
// system("pause");
return 0;
}
3.3cp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>
int main(int argc, char **argv)
{
int fd[2]={0};
int ret = pipe(fd);
pid_t pid = fork();
if(pid>0)
{
close(fd[0]);
int srcfd = open("/home/linux/1.png",O_RDONLY);
if(-1 == srcfd )
{
perror("father open");
exit(1);
}
int num =0;
while(1)
{
char buf[4096]={0};
int ret = read(srcfd,buf,sizeof(buf));
if(ret<=0)
{
time_t tm;
time(&tm);
printf("发送结束,num %d, %lu\n",num,tm);
break;
}
write(fd[1],buf,ret);
num+=ret;
}
close(srcfd);
close(fd[1]);
exit(0);
}
else if(0 == pid)
{
close(fd[1]);
int dstfd = open("2.png",O_WRONLY|O_CREAT|O_TRUNC,0666);
if(-1 == dstfd )
{
perror("child open");
exit(1);
}
int num = 0 ;
while(1)
{
char buf[4096]={0};
int ret = read(fd[0],buf,sizeof(buf));
if(ret<=0)
{
time_t tm;
time(&tm);
printf("读取数据结束,%d, tm:%lu\n",num,tm);
break;
}
write(dstfd,buf,ret);
num+=ret;
}
close(fd[0]);
close(dstfd);
exit(0);
}
else
{
perror("fork");
exit(1);
}
//system("pause");
return 0;
}
四:管道的特性
1.管道的数据存储方式:栈, 先进后出
队列形式存储 读数据会剪切取走数据不会保留,先进先出
2.管道的数据容量:建议值: 512* 8 = 4k,实际值:65536byte= 64k
3.管道的同步效果如何验证?
结论:读写端必须同时存在,才能进行管道的读写。
//读端关闭能不能写? 不可以 ===>SIGPIPE 异常终止
//写端关闭能不能读? 可以,取决于pipe有没有内容,===>read返回值为0 不阻塞
4.固定的读写端能否互换:不可以,是固定读写端(返回值都不一样)
五:创建有名管道mkfifo
有名管道===》fifo ==》有文件名称的管道。文件系统(逻辑可以当文件写)中可见
框架:创建有名管道 ==》打开有名管道 ==》读写管道==》关闭管道 ==》卸载有名管道
#include <sys/types.h>
#include <sys/stat.h>
remove();
int mkfifo(const char *pathname, mode_t mode);
功能:在指定的pathname路径+名称下创建一个权限为 mode的有名管道文件。
参数:pathname要创建的有名管道路径+名称
mode 8进制文件权限。
返回值:成功 0, 失败 -1;
2、打开有名管道 open
注意:该函数使用的时候要注意打开方式,
因为管道是半双工模式,所有打开方式直接决定当前进程的读写方式。
一般只有如下方式:
int fd-read = open("./fifo",O_RDONLY); //==>fd 是固定读端
int fd-write = open("./fifo",O_WRONLY); //==>fd 是固定写端
不能是 O_RDWR 方式打开文件。
不能有 O_CREAT 选项,因为创建管道有指定的mkfifo函数
六:有名管道的一些问题
1、是否需要同步,以及同步的位置。
读端关闭 是否可以写,不能写什么原因。
写端关闭 是否可以读。
结论:有名管道执行过程过必须有读写端同时存在。
如果有一端没有打开,则默认在open函数部分阻塞。
2、有名管道是否能在fork之后的亲缘关系进程中使用。
结论: 可以在有亲缘关系的进程间使用。
注意: 启动的次序可能会导致其中一个稍有阻塞。
3、能否手工操作有名管道实现数据的传送。
读: cat fifoname
写: echo “asdfasdf” > fifoname
strcat
1277

被折叠的 条评论
为什么被折叠?



