进程之间的通信方式主要有六种,包括管道,信号量,消息队列,信号,共享内存,套接字
管道
管道是 Unix 中最古老的进程间通信的形式,我们把从一个进程连接到另一个进程的数据流称为一个“管道”
例如,我们统计登录用户个数
其中 who 命令和 wc 命令都是两个程序,当它们运行起来后就变成了两个进程,who 进程通过标准输出将数据打到“管道”当中,wc 进程再通过标准输入从“管道”当中读取数据,至此便完成了数据的传输,进而完成数据的进一步加工处理
管道:管道是半双工的,双方需要通信的时候,需要建立两个管道。管道的是指是一个内核缓冲区,进程以先进先出的方式从缓冲区存储数据:管道一端的进程顺序地将进程数据写入缓冲区,另一端的进程则顺序地读取数据,该缓冲区可以看做一个循环队列,读和写的位置都是自动增加的,一个数据只能被读一次,读出以后再缓冲区都不复存在了。当缓冲区读空或写满时,有一定的规则控制相应的读进程或写进程是否进入等待队列,当空的缓冲区有新数据写入或满的缓冲区有数据读出时,就唤醒等待队列中的进程继续读写。管道是最容易实现的。
匿名管道
匿名管道仅限于本地关联进程之间的通信
注意:
这里父子进程看到的同一份文件资源是由操作系统来维护的,所以当父子进程对该文件进行写入操作时,该文件缓冲区当中的数据并不会进行写时拷贝
管道虽然用的是文件的方案,但操作系统一定不会把进程进行通信的数据刷新到磁盘当中,因为这样做有 IO 参与会降低效率,而且也没有必要。也就是说,这种文件是一批不会把数据写到磁盘当中的文件,换句话说,磁盘文件和内存文件不一定是一一对应的,有些文件只会在内存当中存在,而不会在磁盘当中存在
pipe函数
函数描述
创建匿名管道
函数原型
int pipe(int pipefd[2])
函数参数
pipefd 是一个传出参数,用于返回两个指向管道读端和写端的文件描述符
pipefd[0]:管道读端的文件描述符
pipefd[1]:管道写端的文件描述符
函数返回值
成功返回0
失败返回-1,设置error
在创建匿名管道实现父子进程间通信的过程中,组要pipe函数和fork函数搭配使用,具体步骤如下:
1、父进程调用 pipe 函数创建管道
2、父进程创建子进程
3、父进程关闭写端,子进程关闭读端
注意:
管道只能够进行单向通信,因此当父进程创建完子进程后,需要确认父子进程谁读谁写,然后关闭相应的读写端
从管道写端写入的数据会被存到内核缓冲,直到从管道的读端被读取
/创建管道
int pipefd[2];
pipe(pipefd);
//创建子进程
int pid = fork();
if (pid == 0) {//子进程写数据,关闭读端
close(pipefd[0]);
slepp(3);
int write_count = write(pipefd[1], "hello", 5);
printf("write_count = %d\n", write_count);
}
else {//父进程读数据,关闭写端
close(pipefd[1]);
char buff[64];
int read_count = read(pipefd[0], buff, sizeof(buff));
printf("read count = %d\n", read_count);
if (read_count > 0) {
buff[read_count] = '\0';
printf("buff:%s\n", buff);
}
}
while (1);
- 管道中没有数据:write 返回成功写入的字节数,读端进程阻塞在 read 上
- 管道中有数据没满:write 返回成功写入的字节数,read 返回成功读到的字节数
- 管道写满:写端进程阻塞在 write 上,read 返回成功读到的字节数
- 写端全部关闭: read 正常读,返回成功读到的字节数(没有数据返回0,不阻塞)
- 读端全部关闭:写端进程 write 会异常终止进程(被 SIGPIPE 信号杀死)
#include<iostream>
using namespace std;
int pipefd[2];
pipe(pipefd);
int pid = fork();
if (pid == 0) {
close(pipefd[0]);
int write_count;
while (1) {//模拟管道写满
write_count = write(pipefd[1], "hello", 5);
printf("write_count = %d\n", write_count);
}
}
else {
close(pipefd[1]);
char buff[10];
int read_count = read(pipefd[0], buff, sizeof(buff));
printf("parent read count = %d\n", read_count);
while (1);
}
read:
管道中没有数据且写端没有关闭:read阻塞
管道中有数据:read 返回成功读到的字节数
write:
管道没满并且读端没有关闭:write 返回成功写入的字节数
管道已满并且读端没有关闭:write 阻塞
读端全部关闭:写端进程 write 会异常终止进程
命名管道
命名管道和匿名管道一样,都是内存文件,只不过命名管道在磁盘有一个简单的映像,但这个映像的大小永远为0,因为命名管道和匿名管道都不将通信数据刷新到磁盘当中
创建命名管道使用 mkfifo 命令
mkfifo fifo
mkfifo函数
函数描述
程序中创建命名管道
头文件
#include<sys/types.h>
#include<sys/stat.h.
函数原型
int mkfifo(const char *pathname,mode_t mode);
函数参数
pathname:表示要创建的命名管道文件
mode:表示创建命名管道文件的默认权限
函数返回值:
成功,返回0
失败,返回-1
命名管道在父子进程间通信
#include<iostream>
using namespace std;
#include<sys/stat.h>
mkfifo("./fifo", 0644);
int fd = open("./fifo", O_RDWR);
int pid = fork();
if (pid == 0) {
write(fd, "hello fifo", 10);
}
else if (pid > 0) {
char buf[1024];
int ret = read(fd, buf, sizeof(buf));
printf("read count = %d,buf= %s\n", ret buf);
}
命名管道在没有血缘关系的进程间通信:
wfifo.c
int fd = open("./fifo",O_WRONLY);
printf("fd = %d\n",fd);
write(fd,"hello fifo",10);
rfifo.c
int fd = open("./fifo",O_RDONLY);
char buf[1024];
int ret = read(fd,buf,sizeof(buf));
printf("read ccount = %d,buf = %s\n",ret,buf);
命名管道的打开规则:
- 读进程打开 FIFO,没有写进程打开
- 没有 O_NONBLOCK,阻塞直到有写进程打开该 FIFO
- 有 O_NONBLOCK,立刻返回成功
- 写进程打开 FIFO,没有读进程打开
- 没有 O_NONBLOCK,阻塞直到有读进程打开该 FIFO
- 有 O_NONBLOCK,立刻返回失败