三、进程间通信
1、 无名管道
无名管道:1、半双工 2、只有具有亲缘关系的两个进程才可使用
主要是用于父子进程间进行通信
1、pipe函数创建无名管道
#include <unistd.h>
int pipe(int fd[2]);
//成功返回0 , 失败返回-1
经由参数返回2个文件描述符,fd[0] 为读而打开,fd[1]为写而打开 , fd[1]的输出是fd[0]的输入
通常,进程会先调用pipe,接着调用fork,从而创建从父进程到子进程的IPC通道。
fork之后做什么取决于我们想要的数据流方向。对于从父进程到子进程的管道,父进程关闭读端fd[0],子进程关闭写端fd[1], 那么就是下图所示的数据流向
当管道的一端被关闭后,下列两条规则起作用
(1) 当读(read) 一个写端已被关闭的管道时,在所有数据都被读取后,read返回0,表示文件结束
(2) 如果写(write) 一个读端已被关闭的管道时,则产生信号SIGPIPE。如果忽略该信号,或者捕捉该信号并从其处理程序返回,则write返回-1, errno设置为EPIPE。
在写管道时,常量PIPE_BUF规定了内核管道缓冲区大小(用pathconf或fpathconf函数)可以确定PIPE_BUF的大小。如果对管道调用write,而且要求写的字节数大小小于等于PIPE_BUF,则此操作不会与其他进程对同一个管道的write操作交叉进行,否则会出现数据交叉
父子进程双向通信
父子进程双向通信采用的是 两个pipe 一个是 父进程---->子进程 ,另一个是 父进程<-------子进程
#include<stdio.h>
#include <unistd.h>
#include <string.h>
int main(int argc,char *argv[])
{
int p_fd[2]; // p -> c
int c_fd[2]; // c -> p
int cli_fd;
int ret = pipe(p_fd);
if (ret == -1)
perror("pipe");
ret = pipe(c_fd);
if (ret == -1)
perror("pipe");
cli_fd = fork();
if (cli_fd == -1)
{
perror("fork");
return -1;
}
if (cli_fd == 0) //子进程
{
char buf[128] = "hello parent";
close(p_fd[1]); // 子进程读取关闭写端
close(c_fd[0]); // 子进程发送,关闭读端
write(c_fd[1], buf, strlen(buf));
memset(buf, 0, 128);
read(p_fd[0],buf, sizeof(buf));
printf("child recv buf :%s\n", buf);
}
else //父进程
{
char buf[128] = "hello child";
close(p_fd[0]);
close(c_fd[1]);
write(p_fd[1], buf, strlen(buf));
read(c_fd[0],buf, sizeof(buf));
printf("parent recv buf :%s\n", buf);
}
return 0;
}
2、有名管道FIFO
FIFO有时被称为命名管道。未命名的管道只能在两个有亲缘关系的进程之间使用。 但是通过FIFO无亲缘关系也可使用
1、创建FIFO
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
#include <fcntl.h> /* Definition of AT_* constants */
#include <sys/stat.h>
int mkfifoat(int dirfd, const char *pathname, mode_t mode);
//两个函数的返回值, 成功 返回0 , 失败返回-1
mkfifo和mkfifoat相似。但是mkfifoat可以被用来在fd文件描述符表示的目录相关位置创建一个FIFO。
参数mode 权限 与 文件IO的方式相同
关于mkfifoat的 path 有三种情况
(1) 如果path参数指定的是绝对路径名,则fd参数会被忽略掉,并且mkfifoat函数的行为和mkfifo类似
(2) 如果path参数指定的是相对路径,则fd参数是一个打开目录的有效文件描述符,路径名和目录有关
(3) 如果path参数指定的是相对路径,并且fd参数有一个特殊值AT_FDCWD,则路径名以当前目录开始,mkfifoat 和 mkfifo类似。
当我们使用mkfifo或者mkfifoat创建FIFO时,要用open来打开它
一般情况下,(没有指定O_NONBLOCK) ,只读open要阻塞到某个其他进程为写而打开这个FIFO为止,类似的,只写open要阻塞到某个其他进程为读而打开为止。
2、实例
创建管道
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
int main(int argc,char *argv[])
{
if (mkfifo("FIFO", 777) == -1)
{
perror("FIFO");
return -1;
}
return 0;
}
读管道进程
/*************************************************************************
> File Name: fifo_read.c
> 作者:YJK
> Mail: 745506980@qq.com
> Created Time: 2021年04月28日 星期三 09时11分59秒
************************************************************************/
#include<stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <string.h>
#define FIFO_NAME "FIFO"
#define BUF_SIZE 128
// 读端
int main(int argc,char *argv[])
{
struct stat st;
int fd;
char buf[BUF_SIZE] = {0};
// 首先判断是否为FIFO文件,如果不是则退出
if (lstat(FIFO_NAME, &st) == -1)
{
perror("lstat");
return -1;
}
fd = open(FIFO_NAME, O_RDONLY); // 以只读方式打开
if (fd == -1)
{
perror("open");
return -1;
}
int ret = 1;
while (ret)
{
ret = read(fd, buf,BUF_SIZE);
if (ret < 0)
{
perror("read");
break;
}
printf("recv buf %s\n", buf);
memset(buf, 0, BUF_SIZE);
}
close(fd);
return 0;
}
写管道进程
#include<stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <string.h>
#define FIFO_NAME "FIFO"
#define BUF_SIZE 128
// 写端
int main(int argc,char *argv[])
{
struct stat st;
int fd;
char *buf="hello world test1\n";
// 首先判断是否为FIFO文件,如果不是则退出
if (lstat(FIFO_NAME, &st) == -1)
{
perror("lstat");
return -1;
}
fd = open(FIFO_NAME, O_WRONLY); //只写打开
if (fd == -1)
{
perror("open");
return -1;
}
int ret = 10;
while (ret)
{
write(fd, buf ,strlen(buf));
sleep(1);
ret --;
}
close(fd);
return 0;
}
刚开始两个写进程 以只写方式打开FIFO因为没有读FIFO所以阻塞
两个写进程向FIFO写数据,一个读进程读取数据
3、总结
FIFO与无名管道相比, FIFO可以实现无亲缘关系的进之间进行通信。
当使用阻塞方式打开FIFO时, 只读open要阻塞到某个其他进程为写而打开这个FIFO为止
只写open要阻塞到某个进程为读而打开这个FIFO为止
无名管道与有名管道总结
实际上就是一个内核中的队列
无名管道
1、半双工 (要实现全双工的话,需要两个无名管道)
2、只能用于具有亲缘关系的进程
3、无名管道存在于内核空间, 不属于任何文件系统,只存在于内存中
有名管道
FIFO,在文件系统目录中存在一个管道文件FIFO
管道文件仅仅是文件系统中的标识,并不在磁盘上占据空间。在使用时,在内存上开辟空间,作为两个进程数据交互的通道
FIFO可以用于无亲缘关系的进程之间进行通信
1、相同点
open打开管道文件以后,在内存中开辟了一块空间,管道的内容在内存中存放,有两个指针—-头指针(指向写的位置)和尾指针(指向读的位置)指向它。读写数据都是在给内存的操作,并且都是半双工通讯。
2、区别
有名在任意进程之间使用,无名在有亲缘关系进程之间使用。
拓展:
全双工、半双工、单工通讯的区别:
单工:方向是固定的,只有一个方向可以写,例如广播。
半双工:方向不固定,但在某一刻只能有一个方向进行写,例如对讲机。
全双工:两个方向都可以同时写,例如打电话。