🛑进程间的通信
UNIX平台进程通信方式
- 早期进程间通信方式
- AT&T的贝尔实验室,对Unix早期的进程间通信进行了改进和扩充,形成了“system V IPC”,其通信进程主要局限在单个计算机内
- BSD(加州大学伯克利分校的伯克利软件发布中心),跳过了该限制,形成了基于套接字(socket)的进程间通信机制
Linux继承了上述所有的通信方式
🍃Linux通信方式
早期进程间通信方式:无名管道、有名管道、信号
🌿无名管道
🍀特点
只能用于具有亲缘关系的进程之间的通信 半双工的通信模式,具有固定的读端和写端
- 单工通信:固定的读端和写端,方向不可更改
- 半双工:两者都能发或者收,但是在同一时间内,只能有一个方向
- 全双工:两者都能发收,而且可以同时进行
管道可以看成是一种特殊的文件,对于它的读写可以使用文件IO如read、write函数。
管道是基于文件描述符的通信方式。当一个管道建立时,它会创建两个文件描述符fd[0]和fd[1]。其中fd[0]固定用于读管道,而fd[1]固定用于写管道。
构成了一个半双工的通道。
🍀创建管道
头文件:
#include <unistd.h>
函数原型:
int pipe(int fd[2])
函数参数:
fd:包含两个元素的整型数组
函数返回值:
成功:0
失败:-1
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
int main(int argc, const char *argv[])
{
int fd[2] = {0}; //fd[0] 读 fd[1] 写
//通过pipe创建无名管道
if(-1 == pipe(fd)) //pipe(fd[2]);
{
perror("pipe");
return -1;
}
puts("pipe OK!");
//创建进程
//子进程往父进程发送数据
pid_t pid = 0;
pid = fork();
if(pid < 0)
{
perror("fork");
return -1;
}
if(pid == 0) //子进程
{
//1、关闭读端
close(fd[0]);
char buf[20] = {0};
//输入数据
printf("write %d :", getpid());
fgets(buf, sizeof(buf), stdin);
//将数据写入到管道中
write(fd[1], buf, strlen(buf));
//关闭写段
close(fd[1]);
}
if(pid > 0) //父进程
{
//1、关闭写端
close(fd[1]);
//从管道读数据
char buf[20] = {0};
read(fd[0], buf, sizeof(buf));
printf("read %d :%s", getpid(), buf);
//关闭读端
close(fd[0]);
}
return 0;
}
🍀探究无名管道有多大?
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(int argc, const char *argv[])
{
char buf[1024] = {0};
int fd[2] = {0}; //fd[0] 读 fd[1] 写
//通过pipe创建无名管道
if(-1 == pipe(fd)) //pipe(fd[2]);
{
perror("pipe");
return -1;
}
puts("pipe OK!");
int count = 0;
while(1)
{
write(fd[1], buf, sizeof(buf));
count++;
printf("%dK\n", count);
}
return 0;
}
🍀注意
🌿有名管道
创建有名管道:计算机会在内核空间上创建有名管道并且在用户空间上虚拟出来一个文件,该文件能访问到管道mkfifo
🍀特点
- 无名管道只能用于具有亲缘关系的进程之间,这就限制了无名管道的使用范围
- 有名管道可以使互不相关的两个进程互相通信。有名管道可以通过路径名来指出,并且在文件系统中可见
- 进程通过文件IO来操作有名管道
- 有名管道遵循先进先出规则
- 不支持如lseek() 操作
🍀创建有名管道
头文件:
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
函数原型:
int mkfifo(const char *filename, mode_t mode)
函数参数:
filename:要创建的管道
mode:指定创建的管道的访问权限,一般用8进制数表示
函数返回值:
成功:0
失败:-1
返回值 | 错误 |
---|---|
EACCESS | 参数filename所指定的目录路径无可执行权限 |
EEXIST | 参数filename所指定的文件已存在 |
ENAMETOO LONG | 参数filename的路径名称太长 |
ENOENT | 参数filename包含的目录不存在 |
ENOSPC | 文件的剩余空间不足 |
EROFS | 参数filename指定的文件存在于只读文件系统内 |
#include "./myhead.h"
#include <fcntl.h>
#include <errno.h>
int main(int argc, const char *argv[])
{
int fd = 0;
char buf[20] = {0};
//创建又名管道
//mkfifo(char *pathname, mode);
if(-1 == mkfifo("myfifo", 0666) && errno != EEXIST)
{
perror("mkfifo");
return -1;
}
//往又名管道中写入数据
//打开文件
fd = open("myfifo", O_WRONLY);
if(fd < 0)
{
perror("open");
return -1;
}
//往文件中写
while(1)
{
memset(buf, 0, sizeof(buf));
printf("write:\n");
fgets(buf, sizeof(buf), stdin);
//把数据写入到管道文件中
write(fd, buf, strlen(buf));
if(strncmp(buf, "quit", 4) == 0)
{
break;
}
}
close(fd);
return 0;
}
#include "./myhead.h"
#include <fcntl.h>
#include <errno.h>
int main(int argc, const char *argv[])
{
int fd = 0;
char buf[20] = {0};
//创建又名管道
//mkfifo(char *pathname, mode);
if(-1 == mkfifo("myfifo", 0666) && errno != EEXIST)
{
perror("mkfifo");
return -1;
}
//从又名管道中读数据
//打开文件
fd = open("myfifo", O_RDONLY);
if(fd < 0)
{
perror("open");
return -1;
}
//从文件中读
while(1)
{
memset(buf, 0, sizeof(buf));
read(fd, buf, sizeof(buf));
printf("read:%s", buf);
if(strncmp(buf, "quit", 4) == 0)
{
break;
}
}
close(fd);
return 0;
}