当前位置: 首页 > java >正文

进程间通信IPC(interprocess communicate)

一、进程间通信的三大类

进程间通信(IPC)主要分为以下三大类:

类别主要方式特点
古老的通信方式无名管道、有名管道、信号实现简单,适用于简单场景
IPC 对象通信消息队列、共享内存、信号量集效率较高,功能强大
Socket 通信TCP/IP、UDP支持网络通信,跨主机通信

二、古老的通信方式

1. 管道(Pipe)

管道是一种半双工的通信方式,分为无名管道和有名管道两种。

1.1 无名管道(pipe)
创建方式
#include <unistd.h>
int pipe(int fd[2]);
  • 成功返回 0,失败返回 - 1
  • 创建后生成两个文件描述符:fd[0](读端)和fd[1](写端)
特性
  • 仅用于具有亲缘关系的进程间通信(父子进程、兄弟进程)
  • 半双工通信:数据只能从写端流向读端
  • 管道本质是内核中的一个缓冲区(队列结构),默认大小约 64KB
  • 不支持定位操作(lseek)
  • 管道是特殊文件,可使用文件 IO 函数操作(read、write、close 等)
使用流程
  1. 创建管道(pipe 函数,在 fork 之前)
  2. 读写管道(read/write 函数)
  3. 关闭管道(close 函数)
读写行为
  • 读端存在,向管道写数据:若超过缓冲区大小(64KB),写操作会阻塞
  • 写端存在,从管道读数据:若管道为空,读操作会阻塞
  • 读端关闭后,写操作会导致管道破裂,触发 SIGPIPE 信号
  • 写端关闭后,若管道无数据,读操作会返回 0(类似文件结束)
管道读阻塞示例 (01pipe_read_block.c)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>int main(int argc, char *argv[])
{int fd[2] = {0};  // 管道文件描述符数组int ret = pipe(fd);  // 创建无名管道if (-1 == ret){perror("pipe");return 1;}pid_t pid = fork();  // 创建子进程if (pid > 0)  // 父进程{close(fd[0]);  // 关闭读端char buf[] = "hello,child";  // 待写入数据sleep(3);  // 延迟3秒write(fd[1], buf, strlen(buf) + 1);  // 写入管道}else if (0 == pid)  // 子进程{close(fd[1]);  // 关闭写端char buf[50] = {0};  // 读缓冲区read(fd[0], buf, sizeof(buf));  // 从管道读取(会阻塞等待)printf("child, buf:%s\n", buf);  // 打印读取内容}else {perror("fork");return 1;}return 0;
}

运行结果:
子进程阻塞3秒后,打印:child, buf:hello,child

管道写阻塞示例 (02pipe_write_block.c)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>int main(int argc, char *argv[])
{int fd[2] = {0};int ret = pipe(fd);if (-1 == ret){perror("pipe");return 1;}pid_t pid = fork();if (pid > 0)  // 父进程{close(fd[0]);char buf[1024] = {0};memset(buf, 'a', sizeof(buf));  // 填充'a'int i = 0;for (i = 0; i < 65; i++)  // 超过64KB容量{write(fd[1], buf, sizeof(buf));printf("i is %d\n", i);  // 观察写阻塞点}}else if (0 == pid)  // 子进程{close(fd[1]);sleep(5);  // 延迟读取char buf[50] = {0};read(fd[0], buf, sizeof(buf));printf("child, buf:%s\n", buf);}else {perror("fork");return 1;}return 0;
}

运行结果:
父进程打印 i is 0 到 i is 63 后阻塞(64次×1024=64KB),第64次写入后继续打印 i is 64。子进程5秒后读取并打印64个 'a'。

管道破裂示例 (03pipe_broken.c)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>int main(int argc, char *argv[])
{int fd[2] = {0};int ret = pipe(fd);if (-1 == ret){perror("pipe");return 1;}pid_t pid = fork();if (pid > 0)  // 父进程{sleep(1);  // 确保子进程先关闭close(fd[0]);  // 关闭读端char buf[] = "hello,child";write(fd[1], buf, strlen(buf) + 1);  // 触发管道破裂printf("write end\n");  // 不会执行(已崩溃)}else if (0 == pid)  // 子进程{close(fd[1]);  // 关闭写端close(fd[0]);  // 关闭读端sleep(3);      // 等待父进程写操作printf("child, pid:%d\n", getpid());}else {perror("fork");return 1;}return 0;
}

运行结果:
子进程先关闭所有管道端,父进程写操作触发 SIGPIPE 信号,进程异常终止。终端显示:
Broken pipe

读端关闭示例 (04pipe_read_zero.c)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>int main(int argc, char *argv[])
{int fd[2] = {0};int ret = pipe(fd);if (-1 == ret){perror("pipe");return 1;}pid_t pid = fork();if (pid > 0)  // 父进程{close(fd[0]);char buf[] = "hello,child";write(fd[1], buf, strlen(buf) + 1);}else if (0 == pid)  // 子进程{close(fd[1]);while (1){char buf[50] = {0};int ret = read(fd[0], buf, sizeof(buf));if (0 == ret)  // 写端关闭且无数据{printf("IPC end\n");break;}printf("child, buf:%s\n", buf);}}else {perror("fork");return 1;}return 0;
}

运行结果:

子进程先读取到 hello,child,父进程退出后写端关闭,子进程下一次 read() 返回0并打印:
IPC end

注意事项
  • 无名管道的架设应该在 fork 之前进行
  • 父子进程都会拥有 fd [0] 和 fd [1] 两个文件描述符,单一进程中写 fd [1] 可以从 fd [0] 读到
  • 管道数据以队列形式存储,读数据会取走数据,不会保留
  • 管道容量默认约 64KB(65536 字节)
  • 读写端必须同时存在,否则会导致阻塞或异常
  • 读写端固定,不能互换
1.2 有名管道(FIFO)
创建方式
  • 命令行方式:mkfifo 管道名
  • 程序方式:
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
  • 参数
    • pathname:管道路径
    • mode:8进制权限(如 0666
  • 返回值:成功返回0,失败返回-1
特性
  • 可用于任意进程间通信,不要求进程间有亲缘关系
  • 半双工通信方式
  • 有名管道执行过程必须有读写端同时存在
  • 如果有一端没有打开,则默认在 open 函数部分阻塞
  • 存在于文件系统中,有文件名和 i 节点,但不占用磁盘空间
  • 其他特性与无名管道类似
使用流程
  1. 创建有名管道(mkfifo 函数)
  2. 打开有名管道(open 函数)
  3. 读写管道(read/write 函数)
  4. 关闭管道(close 函数)
  5. 删除管道(unlink/remove 函数)
代码示例:

写端 (01fifo_w.c)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>int main(int argc, char *argv[])
{int ret = mkfifo("myfifo", 0666);  // 创建管道if (-1 == ret && EEXIST != errno)  // 忽略已存在错误{perror("mkfifo error");return 1;}int fd = open("myfifo", O_WRONLY);  // 以写方式打开if (-1 == fd){perror("open error");return 1;}char buf[] = "hello, this is fifo tested...\n";sleep(3);  // 等待读端准备write(fd, buf, strlen(buf) + 1);  // 写入数据close(fd);return 0;
}

运行结果:
等待3秒后写入数据到管道(无直接输出,需配合读端程序)。

读端 (02fifo_r.c)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>int main(int argc, char *argv[])
{int ret = mkfifo("myfifo", 0666);if (-1 == ret && EEXIST != errno){perror("mkfifo error");return 1;}int fd = open("myfifo", O_RDONLY);  // 以读方式打开if (-1 == fd){perror("open error");return 1;}char buf[256] = {0};read(fd, buf, sizeof(buf));  // 从管道读取printf("fifo: %s\n", buf);   // 打印内容close(fd);return 0;
}

运行结果:
当写端程序运行后,读端打印:fifo: hello, this is fifo tested...

文件传输(写端)

#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <stdio.h>int main()
{mkfifo("myfifo", 0666);  // 创建管道int fifo_w = open("myfifo", O_WRONLY);  // 写端int src = open("/home/linux/1.png", O_RDONLY);  // 源文件char buf[4096];while (1){int ret = read(src, buf, sizeof(buf));  // 读取图片if (ret <= 0) break;write(fifo_w, buf, ret);  // 写入管道}close(src);close(fifo_w);return 0;
}

文件传输(读端)

#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <stdio.h>int main()
{mkfifo("myfifo", 0666);  // 创建管道int fifo_r = open("myfifo", O_RDONLY);  // 读端int dst = open("2.png", O_WRONLY | O_CREAT | O_TRUNC, 0666);  // 目标文件char buf[4096];while (1){int ret = read(fifo_r, buf, sizeof(buf));  // 从管道读取if (ret <= 0) break;write(dst, buf, ret);  // 写入目标文件}close(fifo_r);close(dst);return 0;
}

运行结果:
1.png 被完整传输到 2.png,两个文件内容完全一致。

2. 信号(Signal)

信号是一种用于通知进程发生了某种事件的异步通信机制。

2.1 信号特性
  • 信号是唯一的异步通信方式
  • 信号编号:1~64,前 32 个有具体含义
  • 常用信号:
    • SIGINT(2):中断信号(Ctrl+C)
    • SIGQUIT(3):退出信号(Ctrl+\)
    • SIGKILL(9):强制终止信号(不可捕获、忽略)
    • SIGTERM(15):终止信号(默认信号)
    • SIGSTOP(19):暂停信号(不可捕获、忽略)
    • SIGUSR1(10)和 SIGUSR2(12):用户自定义信号
2.2 信号发送
kill 函数
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
  • 功能:向指定进程发送信号
  • 参数:
    • pid:目标进程 ID
    • sig:要发送的信号编号(可用kill -l查看所有信号)
  • 返回值:成功返回 0,失败返回 - 1
2.3 信号处理方式

每个进程都会对信号作出默认响应,但不是唯一响应。一般有三种处理方式:

  1. 默认处理:系统预设的处理方式
  2. 忽略处理:进程自行决定忽略该信号(SIGKILL 和 SIGSTOP 不可忽略)
  3. 自定义处理:进程注册信号处理函数,自行处理信号(SIGKILL 和 SIGSTOP 不可自定义)
信号注册函数
#include <signal.h>
/* * 定义函数指针类型 sighandler_t,* 该类型的函数指针指向一个接受 int 类型参数且返回值为 void 的函数,* 用于表示信号处理函数的类型*/
typedef void (*sighandler_t)(int);
/* * 信号注册函数:用于为指定信号设置处理方式* 参数:*   signum:要处理的信号编号(如 SIGINT、SIGKILL 等,范围通常为 1-64)*   handler:信号处理方式,可以是以下三种之一:*            1. SIG_DFL:使用系统默认的处理方式*            2. SIG_IGN:忽略该信号(SIGKILL 和 SIGSTOP 不可忽略)*            3. 自定义函数指针:指向用户定义的信号处理函数(SIGKILL 和 SIGSTOP 不可自定义)* 返回值:*   成功:返回之前注册的信号处理函数指针(用于恢复之前的处理方式)*   失败:返回 SIG_ERR(需要包含 <errno.h> 才能查看具体错误)*/
sighandler_t signal(int signum, sighandler_t handler);
  • 功能:用于为指定信号设置处理方式
  • 参数:
    • signum:要处理的信号编号
    • handler:信号处理函数指针,或特殊值(SIG_IGN 忽略,SIG_DFL 默认)
  • 返回值:成功返回之前的信号处理函数指针,失败返回 SIG_ERR
代码示例:

信号发送程序(01kill.c)

#include <sys/types.h>
#include <signal.h>
#include <stdio.h>int main(int argc, char *argv[])
{if (argc < 3){printf("usage: ./a.out pid sig_num\n");return 1;}int pid = atoi(argv[1]);int sig_num = atoi(argv[2]);int ret = kill(pid, sig_num);  // 发送信号if (-1 == ret){perror("kill error");return 1;}return 0;
}

运行结果:

成功向指定进程发送信号(无输出),目标进程按信号类型执行相应操作

信号处理(02signal.c)

#include <signal.h>
#include <unistd.h>
#include <stdio.h>int flag = 0;void myhandle(int num)  // 信号处理函数
{flag = 1;  // 修改全局标志
}int main()
{signal(SIGINT, myhandle);  // 注册Ctrl+C处理while (1){if (0 == flag)printf("i'm processing..., pid:%d\n", getpid());elseprintf("i'm resting..., pid:%d\n", getpid());sleep(1);}return 0;
}

运行结果:

正常运行时每秒打印i'm processing..., pid:,按 Ctrl+C 后,后续打印变为i'm resting..., pid:

自定义信号处理(03signal_usr.c)

#include <signal.h>
#include <unistd.h>
#include <stdio.h>void myhandle1(int num)
{static int i = 0;printf("老爸叫你....\n");if (++i == 3) signal(SIGUSR1, SIG_IGN);  // 第3次后忽略信号
}void myhandle2(int num)
{static int i = 0;printf("老妈叫你....\n");if (++i == 5) signal(SIGUSR2, SIG_DFL);  // 第5次后恢复默认
}int main()
{signal(SIGUSR1, myhandle1);  // 注册USR1处理signal(SIGUSR2, myhandle2);  // 注册USR2处理while (1){printf("i'm playing..., pid:%d\n", getpid());sleep(1);}return 0;
}

运行结果:

收到 SIGUSR1 信号时打印"老爸叫你",第3次后不再响应
收到 SIGUSR2 信号时打印"老妈叫你",第5次后恢复默认处理(终止进程)

回收子进程(04signal_child.c)

#include <signal.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>void myhandle(int num)  // SIGCHLD处理函数
{pid_t pid = wait(NULL);  // 非阻塞回收printf("father recycle pid: %d\n", pid);
}int main()
{signal(SIGCHLD, myhandle);  // 注册子进程结束信号pid_t pid = fork();if (pid > 0)  // 父进程{for (int i = 10; i > 0; i--){printf("father.... pid:%d\n", getpid());sleep(1);}}else if (0 == pid)  // 子进程{for (int i = 5; i > 0; i--){printf("child... pid:%d\n", getpid());sleep(1);}}return 0;
}

运行结果:

子进程结束后,父进程自动回收

http://www.xdnf.cn/news/19424.html

相关文章:

  • Introduction to GIS —— Chapter 4(Raster Data Model)
  • 解读IEC 60529-2013
  • MySQL 公用表达式
  • AI军团协同作战:Manus Wide Research深度解析
  • CAN数据链路层、网络层(ISO11898、15765)
  • JVM-指针压缩
  • Day 01(02): 精读HDFS概念
  • PortSwigger靶场之DOM XSS in document.write sink using source location.search通关秘籍
  • 多线程使用场景一(es数据批量导入)
  • 使用node-red+opencv+mqtt实现相机图像云端查看
  • 【openGauss】Oracle与openGauss/GaussDB数据一致性高效核对方案
  • 解决Docker运行hello-world镜像报错问题
  • 烦人的Nano 编辑器,如何退出呢?
  • 【Java后端】SpringBoot配置多个环境(开发、测试、生产)
  • Python|Pyppeteer解决无法启动Chromium浏览器的问题(35)
  • 云网络(参考自腾讯云计算工程师认证)
  • MySQL服务启动命令手册(Linux+Windows+macOS)(下)
  • CAD2024安装包下载与安装详细教程
  • Marco:阿里国际推出的商用翻译大模型,支持15种语言,效果超越谷歌、GPT-4
  • Overleaf中文显示
  • AI 相关内容:Agent、MCP、Prompt 与 RAG 入门指南
  • tkinter布局
  • 鸿蒙应用开发:开机自启并自检网络状态
  • docker,数据卷
  • Flink部署实战:从入门到优化
  • Linux基本工具(yum、vim、gcc、Makefile、git、gdb)
  • 【模型训练篇】VeRL分布式基础 - 框架Ray
  • 解决 uni-app 中大数据列表的静默UI渲染失败问题
  • Q1 Top IF 18.7 | 基于泛基因组揭示植物NLR进化
  • C语言强化训练(2)