实现进程间通信的几种方式
一. 传统的进程间通信 - 管道文件
管道是 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;
}