进程通信目的:
(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;
}运行结果:




















