进程通信目的:
(1)数据传输:进程间数据传输;
(2)通知事件:一个进程向另一个或一组进程发送消息,通知某个事件的发生(如子进程终止时需通知父进程);
(3)资源共享:多个进程共享资源,需要内核提供同步互斥机制;
(4)进程控制:某进程需要控制另一个进程的执行(如Debug进程),此时控制进程需要拦截另一个进程的所有陷入、异常、状态等。
进行通信分类及方式:
1. 无名管道
特点:(1)半双工。数据同一时刻只能单向传输;
(2)数据从管道一端写入,另一端读出;
(3)写入管道的数据遵循先进先出;
(4)管道非普通文件,不属于某个文件系统,只存在于内存;
(5)无名管道只能在具有公共祖先的进程(父子进程、兄弟进程等)之间使用。
(1)pipe函数:创建无名管道
#include<unistd.h>
int pipe(int pipefd[2]);
/*
功能:
创建无名管道。
参数:
pipefd:int型数组的首地址,存放了管道文件描述符pipefd[0]、pipefd[1]。
pipefd[0]用于读管道,pipefd[1]用于写管道。
一般的文件I/O函数都可用来操作管道(lseek除外)。
返回值:
成功:0
失败:-1
*/
pipe示例:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
int test() {
int fds[2]; // fds[0]用于读管道,fds[1]用于写管道
int ret = -1;
// 创建一个无名管道
ret = pipe(fds);
if (-1 == ret) {
perror("pipe");
return 1;
}
printf("读管道的文件描述符:%d, 写管道的文件描述符:%d\n", fds[0], fds[1]);
// 关闭文件描述符
close(fds[0]);
close(fds[1]);
return 0;
}
运行结果:
(2)父子进程使用无名管道通信原理:
a)需要在fork之前创建无名管道,然后子进程也有自己的读写管道描述符关联无名管道;
b)父进程给子进程发消息:父进程写管道、子进程读管道;需要关闭父进程的读端文件描述符(fds[0])、子进程的写端文件描述符(fds[1])。 反之类似。
c)管道默认为阻塞,读不到内容则阻塞等待有内容可读;可设置为非阻塞。
(3)管道读写特性:
case 1:
a)若写端打开,管道中无数据,读端进程会阻塞;
b)若写端打开,管道中有数据,读端进程将数据读出,下次若无数据可读则阻塞;
case 2:
若写端关闭,读端进程读取全部内容后,返回0;
case 3:
若读端打开,管道被写满,则写端进程阻塞;
case 4:
若读端关闭,写端进程收到一个信号,然后退出。
查看管道大小:
(4)父子进程使用无名管道通信示例:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#define SIZE 64
int main(int argc, const char* argv[]) {
int ret = -1;
int fds[2];
pid_t pid = -1;
char buf[SIZE];
// 1. 创建无名管道
ret = pipe(fds);
if (-1 == ret) {
perror("pipe");
return 1;
}
// 2. 创建子进程。需要在创建无名管道之后
pid = fork();
if (-1 == pid) {
perror("fork");
return 1;
}
// 子进程 读管道
if (0 == pid) {
close(fds[1]); // 关闭写端
ret = read(fds[0], buf, SIZE); // 读管道
if (ret < 0) {
perror("read");
exit(-1);
}
printf("子进程读到的内容:%s\n", buf);
close(fds[0]); // 关闭读端
exit(0); // 子进程退出
}
// 父进程 写管道
close(fds[0]); // 关闭读端
ret = write(fds[1], "ABCDEFG", 7); // 写管道
if (-1 == ret) {
perror("write");
return 1;
}
printf("父进程写了%d字节.\n", ret);
close(fds[1]); // 关闭写端
return 0;
}
运行结果:
(5)fpathconf函数:查看管道缓冲区
#include<unistd.h>
long fpathconf(int fd, int name);
/*
功能:
通过name查看管道缓冲区的不同属性
参数:
fd:读端或写端文件描述符
name:
_PC_PIPE_BUF:查看管道缓冲区大小
_PC_NAME_MAX:文件名字节数上限
返回值:
成功:属性值
失败:-1
*/
fpathconf示例:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
int main(int argc, const char* argv[]) {
int fds[2];
int ret = -1;
ret = pipe(fds);
if (-1 == ret) {
perror("pipe");
return 1;
}
printf("读端缓冲区大小:%ld,\n写端缓冲区大小:%ld,\n读端文件名字节数上限:%ld,\n写端文件名字节数上限:%ld\n",
fpathconf(fds[0], _PC_PIPE_BUF), fpathconf(fds[1], _PC_PIPE_BUF),
fpathconf(fds[0], _PC_NAME_MAX), fpathconf(fds[1], _PC_NAME_MAX));
return 0;
}
运行结果:
(6)管道读端缓冲区设置为非阻塞的方法:
// 获取读端缓冲区原先的状态标记flags
int flags = fcntl(fd[0], F_GETFL);
// 设置新状态标记flags加入非阻塞状态
flags |= O_NONBLOCK;
// 给读端缓冲区设置新状态标记
fcntl(fd[0], F_SETFL, flags);
读端设置为非阻塞,若无数据,读进程直接返回-1.
读端以非阻塞的方式读管道示例:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#define SIZE 64
int main(int argc, const char* argv[]) {
int ret = -1;
int fds[2];
pid_t pid = -1;
char buf[SIZE];
// 1. 创建无名管道
ret = pipe(fds);
if (-1 == ret) {
perror("pipe");
return 1;
}
// 2. 创建子进程。需要在创建无名管道之后
pid = fork();
if (-1 == pid) {
perror("fork");
return 1;
}
// 子进程 读管道
if (0 == pid) {
close(fds[1]); // 关闭写端
/*设置读端非阻塞*/
ret = fcntl(fds[0], F_GETFL); // 获取读端缓冲区状态
ret |= O_NONBLOCK; //将读端缓冲区加入非阻塞状态
fcntl(fds[0], F_SETFL, ret); // 将新状态设置进入
ret = read(fds[0], buf, SIZE); // 读管道
if (ret < 0) {
perror("read");
exit(-1);
}
printf("子进程读到的内容:%s\n", buf);
close(fds[0]); // 关闭读端
exit(0); // 子进程退出
}
// 父进程 写管道
sleep(1);
close(fds[0]); // 关闭读端
ret = write(fds[1], "ABCDEFG", 7); // 写管道
if (-1 == ret) {
perror("write");
return 1;
}
printf("父进程写了%d字节.\n", ret);
close(fds[1]); // 关闭写端
return 0;
}
运行结果: