常见的进程间通信(IPC)方式包括管道(Pipe)、命名管道(FIFO)、消息队列(Message Queue)、共享内存(Shared Memory)、信号(Signal)和套接字(Socket)。
下面是这些通信方式的定义、共同点和不同点、可以执行代码的详细说明:
1. 管道(Pipe)
定义:管道是最基本的进程间通信机制,允许一个进程将数据写入管道,而另一个进程从管道中读取数据。管道分为匿名管道和有名管道。
共同点:
- 半双工:数据只能在一个方向流动,通常是从写端到读端。
- 进程间:通常用于在父子进程之间进行通信。
不同点:
- 匿名管道:通常在创建进程时使用(如
fork()
),只在创建它们的进程之间有效。 - 有名管道(FIFO):可以在不同进程间通信,即使这些进程不是父子关系,也可以通过文件系统路径进行通信。
代码:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main() {
int fd[2]; // 创建一个整型数组,用于存储管道的读写文件描述符
pid_t pid; // 存储子进程ID
pipe(fd); // 创建管道,fd[0] 是读端,fd[1] 是写端
pid = fork(); // 创建子进程
if (pid == 0) { // 子进程
close(fd[0]); // 子进程关闭管道的读端
char message[] = "Hello from child process!";
write(fd[1], message, strlen(message)); // 子进程向管道写数据
close(fd[1]); // 子进程关闭管道的写端
} else { // 父进程
close(fd[1]); // 父进程关闭管道的写端
char buffer[100];
read(fd[0], buffer, sizeof(buffer)); // 父进程从管道读取数据
printf("Parent process received: %s\n", buffer); // 打印从子进程收到的消息
close(fd[0]); // 父进程关闭管道的读端
}
return 0;
}
2. 命名管道(FIFO)
定义:命名管道是一种特殊类型的文件,允许不相关的进程通过文件系统中的路径进行通信。
共同点:
- 全双工:数据流动可以是双向的,但必须手动控制读写端。
- 进程间:可以在不相关的进程间进行通信。
不同点:
- 持久性:FIFO 文件存在于文件系统中,可以被任何进程打开和使用。
- 路径:通信需要通过一个特定的文件路径进行。
代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <string.h>
#define FIFO_PATH "/tmp/myfifo" // FIFO 文件路径
#define BUFFER_SIZE 100
int main() {
// 创建 FIFO 文件
if (mkfifo(FIFO_PATH, 0666) == -1) {
perror("mkfifo");
exit(EXIT_FAILURE);
}
pid_t pid = fork(); // 创建子进程
if (pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (pid == 0) { // 子进程
char buffer[BUFFER_SIZE];
int fd = open(FIFO_PATH, O_RDONLY); // 以只读模式打开 FIFO 文件
if (fd == -1) {
perror("open");
exit(EXIT_FAILURE);
}
// 从 FIFO 文件中读取数据
ssize_t bytesRead = read(fd, buffer, sizeof(buffer) - 1);
if (bytesRead == -1) {
perror("read");
exit(EXIT_FAILURE);
}
buffer[bytesRead] = '\0'; // 确保字符串以 null 结尾
printf("Child process received: %s\n", buffer); // 打印接收到的数据
close(fd); // 关闭 FIFO 文件
exit(EXIT_SUCCESS);
} else { // 父进程
int fd = open(FIFO_PATH, O_WRONLY); // 以只写模式打开 FIFO 文件
if (fd == -1) {
perror("open");
exit(EXIT_FAILURE);
}
const char *message = "Hello from parent";
ssize_t bytesWritten = write(fd, message, strlen(message));
if (bytesWritten == -1) {
perror("write");
exit(EXIT_FAILURE);
}
close(fd); // 关闭 FIFO 文件
// 等待子进程完成
wait(NULL);
// 删除 FIFO 文件
if (unlink(FIFO_PATH) == -1) {
perror("unlink");
exit(EXIT_FAILURE);
}
}
return 0;
}
3. 消息队列(Message Queue)
定义:消息队列允许进程以消息的形式发送和接收数据,每条消息都有一个类型,并可以在消息队列中排队。
共同点:
- 异步通信:发送消息的进程和接收消息的进程可以在不同的时间进行操作。
- 进程间:可以在不相关的进程之间进行通信。
不同点:
- 消息类型:消息可以按照类型进行分类和接收。
- 内存管理:操作系统负责管理消息队列中的消息,支持消息的排队和优先级排序。
代码:
#include <stdio.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/msg.h>
struct msg_buffer {
long msg_type; // 消息类型
char msg_text[100]; // 消息内容
};
int main() {
key_t key = ftok("progfile", 65); // 生成唯一的键
int msgid = msgget(key, 0666 | IPC_CREAT); // 创建消息队列并返回消息ID
struct msg_buffer message;
if (fork() == 0) { // 子进程
message.msg_type = 1;
sprintf(message.msg_text, "Hello from child process!");
msgsnd(msgid, &message, sizeof(message), 0); // 子进程发送消息
} else { // 父进程
msgrcv(msgid, &message, sizeof(message), 1, 0); // 父进程接收消息
printf("Parent process received: %s\n", message.msg_text); // 打印接收到的消息
msgctl(msgid, IPC_RMID, NULL); // 删除消息队列
}
return 0;
}
4. 共享内存(Shared Memory)
定义:共享内存允许多个进程访问同一块内存区域,通常用于高速交换大量数据。
共同点:
- 高效:由于所有进程共享相同的内存区域,数据交换速度非常快。
- 进程间:可以在不相关的进程之间进行通信。
不同点:
- 同步:需要额外的同步机制(如信号量)来确保数据的一致性和避免竞争条件。
- 直接访问:进程直接操作共享的内存区域,降低了数据交换的开销。
代码:
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
key_t key = ftok("shmfile", 65); // 生成唯一的键
int shmid = shmget(key, 1024, 0666 | IPC_CREAT); // 创建共享内存段并返回ID
if (fork() == 0) { // 子进程
char *str = (char*) shmat(shmid, (void*)0, 0); // 连接到共享内存
sprintf(str, "Hello from child process!"); // 子进程写入数据
shmdt(str); // 断开与共享内存的连接
} else { // 父进程
wait(NULL); // 等待子进程完成
char *str = (char*) shmat(shmid, (void*)0, 0); // 连接到共享内存
printf("Parent process received: %s\n", str); // 打印共享内存中的数据
shmdt(str); // 断开与共享内存的连接
shmctl(shmid, IPC_RMID, NULL); // 删除共享内存段
}
return 0;
}
5. 信号(Signal)
定义:信号是一种用于通知进程某种事件发生的机制,信号通常不用于传递大量数据,只用于传递控制信息。
共同点:
- 异步通知:进程可以在接收到信号时立即执行信号处理程序。
- 进程间:可以在不同进程间传递控制信息。
不同点:
- 数据传输限制:信号只能传递有限的控制信息,不适合传递大量数据。
- 处理程序:需要设置信号处理程序来响应特定信号。
代码:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void signal_handler(int signum) {
printf("Received signal %d\n", signum); // 信号处理函数,打印接收到的信号
}
int main() {
signal(SIGUSR1, signal_handler); // 注册信号处理程序
if (fork() == 0) { // 子进程
sleep(1); // 等待父进程准备好
kill(getppid(), SIGUSR1); // 子进程向父进程发送信号
exit(0);
} else { // 父进程
pause(); // 父进程等待信号,使父进程挂起,直到接收到一个信号。该信号会使进程继续执行并调用相应的信号处理程序。
printf("Signal received from child process\n");
}
return 0;
}
6. 套接字(Socket)
定义:套接字是一种进程间通信机制,尤其用于网络通信。套接字可以用于同一台计算机上的进程间通信,也可以用于不同计算机之间的通信。
共同点:
- 全双工:数据流动可以是双向的。
- 进程间:可以在同一台计算机或不同计算机上的进程间进行通信。
不同点:
- 网络通信:套接字支持网络通信,可以用于不同主机之间的进程通信。
- 协议支持:支持多种通信协议,如 TCP 和 UDP。
代码:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void signal_handler(int signum) {
printf("Received signal %d\n", signum); // 信号处理函数,打印接收到的信号
}
int main() {
signal(SIGUSR1, signal_handler); // 注册信号处理程序
if (fork() == 0) { // 子进程
sleep(1); // 等待父进程准备好
kill(getppid(), SIGUSR1); // 子进程向父进程发送信号
exit(0);
} else { // 父进程
pause(); // 父进程等待信号,使父进程挂起,直到接收到一个信号。该信号会使进程继续执行并调用相应的信号处理程序。
printf("Signal received from child process\n");
}
return 0;
}
总结
-
共同点:
- 以上所有IPC机制都用于进程间数据传输或通信。
- 都需要在进程间共享某种形式的资源或状态。
-
不同点:
- 方向性:如管道通常是半双工的,而消息队列和共享内存可以是全双工的。
- 数据量:信号不适合传递大量数据,而共享内存适合传递大量数据。
- 同步:共享内存需要额外的同步机制,而管道和消息队列内置了数据同步机制。
- 持久性:FIFO 文件持久存在于文件系统中,而管道是临时的,通常在进程结束后消失。
- 网络能力:套接字支持网络通信,而其他IPC机制通常只在同一台计算机上使用。
如果对你有帮助的话,欢迎打赏