实现进程间通信的几种方式
一. 传统的进程间通信 - 管道文件
管道是 UNIX 系统中最古老的进程间通信技术。早期的管道是半双工通信,现有的系统管道是全双工通信。管道是一种特殊的文件,数据在文件中是流动的,读取之后自动消失,如果文件中没有数据则会阻塞。
1. 有名管道
有名管道基于有文件名的管道文件进行通信。
编程模型
-
进程 A
- 创建管道
- 打开管道(写)
- 写数据
- 关闭管道
- 删除管道
-
进程 B
- 打开管道(读)
- 读数据
- 关闭管道
创建有名管道文件
-
命令:
mkfifo
-
函数:
int mkfifo(const char *pathname, mode_t mode);
- 功能:创建有名管道
pathname
:管道文件路径mode
:管道文件权限(如 0664)
代码实现
进程 A
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>int main(int argc, const char* argv[]) {if (mkfifo("fifo", 0644)) {perror("mkfifo");return -1;}int fd = open("fifo", O_WRONLY);if (fd < 0) {perror("open");unlink("fifo");return -1;}char buf[256] = {};for (;;) {printf(">>>>>>>>");fgets(buf, sizeof(buf), stdin);write(fd, buf, strlen(buf));if (0 == strncmp("quit", buf, 4)) {printf("quit\n");break;}}close(fd);usleep(1000);unlink("fifo");return 0;
}
进程 B
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main(int argc, const char* argv[]) {int fd = open("fifo", O_RDONLY);if (fd < 0) {perror("open");return -1;}char buf[1024] = {};int n = read(fd, buf, sizeof(buf));while (n > 0) {write(1, buf, n);n = read(fd, buf, sizeof(buf));}close(fd);return 0;
}
2. 匿名管道
匿名管道只适合通过 fork
创建的父子进程之间通信。
函数
int pipe(int pipefd[2]);
pipefd[0]
用于读pipefd[1]
用于写
编程模型
-
父进程
- 获取一对 fd
- 创建子进程
- 关闭读
- 写数据
- 关闭写
-
子进程
- 共享一对 fd
- 关闭写
- 读数据
- 关闭读
代码实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>int main(int argc, char *argv[]) {int fd[2];if (pipe(fd) == -1) {perror("pipe");return 1;}pid_t pid = fork();if (pid == -1) {perror("fork");return 1;}if (pid > 0) {// 父进程close(fd[0]);for (;;) {char buf[256];printf("请输入要发送的消息:");fgets(buf, sizeof(buf), stdin);buf[strcspn(buf, "\n")] = 0;write(fd[1], buf, strlen(buf) + 1);if (strcmp(buf, "quit") == 0) break;}close(fd[1]);} else {// 子进程close(fd[1]);for (;;) {char buf[256];ssize_t bytesRead = read(fd[0], buf, sizeof(buf));if (bytesRead > 0) {printf("收到消息:%s\n", buf);if (strcmp(buf, "quit") == 0) break;} else {break;}}close(fd[0]);}return 0;
}
二. XSI 进程间通信
XSI 是 X/Open 公司制定的用于进程间通信的系统接口。
共享内存
共享内存允许多个进程共享一块内存,由内核负责协调管理。
函数
-
创建/获取共享内存:
int shmget(key_t key, size_t size, int shmflg);
-
映射共享内存:
void *shmat(int shmid, const void *shmaddr, int shmflg);
-
取消映射:
int shmdt(const void *shmaddr);
-
控制共享内存:
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
编程模型
-
进程 A
- 创建共享内存
- 映射共享内存
- 写数据并通知其他进程
- 收到通知并读数据
- 取消映射
- 删除共享内存
-
进程 B
- 获取共享内存
- 映射共享内存
- 收到通知并读数据
- 写数据并通知其他进程
- 取消映射
代码实现
进程 A
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>char* shm;void sigint(int num) {printf("已收到信号\n");printf("%s\n", shm);
}int main(int argc, const char* argv[]) {printf("进程ID为:%u\n", getpid());// 创建共享内存int shmid = shmget(ftok(".", 110), 4096, IPC_CREAT | 0666);if (shmid == -1) {perror("shmget");return 1;}// 映射共享内存shm = (char*)shmat(shmid, NULL, 0);if (shm == (void*)-1) {perror("shmat");shmctl(shmid, IPC_RMID, NULL);return 1;}signal(SIGRTMIN, sigint);// 写入数据并通知其他进程pid_t pid = 0;printf("请输入与我通信的进程号:");scanf("%d", &pid);for (;;) {printf(">>>>>>");scanf("%s", shm);kill(pid, SIGRTMIN);if (0 == strcmp(shm, "quit")) {printf("通信结束\n");break;}}// 取消映射shmdt(shm);// 删除共享内存shmctl(shmid, IPC_RMID, NULL);return 0;
}
进程 B
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <signal.h>
#include <string.h>char *shm;void sigint(int num) {printf("已收到信号\n");printf("%s\n", shm);
}int main(int argc, const char* argv[]) {// 打印出进程号pid_t pid = getpid();printf("进程ID为:%u\n", pid);// 获取共享内存int shmid = shmget(ftok(".", 110), 4096, IPC_CREAT | 0666);if (shmid == -1) {perror("shmget");return 1;}// 映射共享内存shm = (char*)shmat(shmid, NULL, 0);if (shm == (void*)-1) {perror("shmat");shmctl(shmid, IPC_RMID, NULL);return 1;}// 收到通知并读取数据signal(SIGRTMIN, sigint);// 写入数据并通知其他进程pid_t pids = 0;printf("请输入与我通信的进程号:");scanf("%u", &pids);for (;;) {printf(">>>>>>");scanf("%s", shm);kill(pids, SIGRTMIN);if (0 == strcmp(shm, "quit")) {printf("通信结束\n");break;}}// 取消映射shmdt(shm);return 0;
}