目录
一、概念与原理
二、IPC的核心点
三、进程间通信及经典实例详解
1、管道(Pipes)
概念与原理
核心点
实现实例
2、消息队列(Message Queues)
概念与原理
核心点
实现实例
3、共享内存(Shared Memory)
概念与原理
核心点
实现实例
4、信号(Signals)
概念与原理
核心点
实现实例
5、套接字(Sockets)
概念与原理
核心点
实现实例
四、总结
一、概念与原理
进程间通信(IPC)的概念: IPC是指在同一主机的不同进程之间传递数据和信号的机制。它允许多个进程共享数据和资源,实现协同工作。IPC的主要技术有管道(Pipes)、消息队列(Message Queues)、共享内存(Shared Memory)、信号(Signals)、套接字(Sockets)等。
IPC的原理: IPC的基本原理是通过操作系统提供的机制,在不同进程之间传递数据和信号。操作系统提供了专用的API和系统调用来实现这些功能。每种IPC机制都有其优点和适用场景,选择合适的IPC技术取决于具体的需求。
二、IPC的核心点
-
管道(Pipes):
- 概念:管道是一种单向通信机制,数据从一端(写端)流向另一端(读端)。
-
实现方式:使用系统调用
pipe
创建管道。 - 适用场景:适用于父子进程之间的通信。
-
命名管道(Named Pipes/FIFOs):
- 概念:命名管道是管道的扩展,允许无亲缘关系的进程通信。
-
实现方式:使用系统调用
mkfifo
创建命名管道。 - 适用场景:适用于任意两个进程之间的通信。
-
消息队列(Message Queues):
- 概念:消息队列是一个存放消息的数据结构,允许进程以消息的形式传递数据。
- 实现方式:使用System V IPC或POSIX消息队列API。
- 适用场景:适用于需要消息传递和排队处理的场景。
-
共享内存(Shared Memory):
- 概念:共享内存允许多个进程在同一块物理内存区域中读写数据。
- 实现方式:使用System V共享内存或POSIX共享内存API。
- 适用场景:适用于需要高效率大数据量传输的场景。
-
信号(Signals):
- 概念:信号是一种进程间传递通知的机制。
-
实现方式:使用系统调用
kill
发送信号,信号处理函数处理信号。 - 适用场景:适用于异步事件通知。
-
套接字(Sockets):
- 概念:套接字是一种通用的通信机制,支持本地和网络通信。
- 实现方式:使用BSD套接字API。
- 适用场景:适用于网络和本地进程之间的通信。
三、进程间通信及经典实例详解
1、管道(Pipes)
概念与原理
管道是一种单向通信机制,主要用于父子进程之间的数据传输。管道创建后,数据从一端(写端)流向另一端(读端)。管道是通过操作系统内核缓冲区实现的,具有缓冲区限制,超出限制后写操作会阻塞直到有空间。
核心点
-
创建管道:使用
pipe
系统调用创建。 -
读写管道:分别使用文件描述符
pipefd[0]
和pipefd[1]
。 - 关闭未使用端:避免资源浪费和潜在的死锁。
实现实例
#include <iostream>
#include <>
#include <sys/>
#include <sys/>
#include <cstring> // for strlen
int main() {
int pipefd[2]; // 存储管道的文件描述符
pid_t cpid;
char buf;
if (pipe(pipefd) == -1) { // 创建管道
perror("pipe");
exit(EXIT_FAILURE);
}
cpid = fork(); // 创建子进程
if (cpid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (cpid == 0) { // 子进程
close(pipefd[1]); // 关闭未使用的写端
std::cout << "子进程从管道读取数据:\n";
while (read(pipefd[0], &buf, 1) > 0) { // 从管道读数据
write(STDOUT_FILENO, &buf, 1); // 将数据写到标准输出
}
write(STDOUT_FILENO, "\n", 1);
close(pipefd[0]); // 关闭读端
_exit(EXIT_SUCCESS);
} else { // 父进程
close(pipefd[0]); // 关闭未使用的读端
const char* msg = "来自父进程的消息!\n";
write(pipefd[1], msg, strlen(msg)); // 将消息写入管道
close(pipefd[1]); // 关闭写端,使子进程看到EOF
wait(NULL); // 等待子进程结束
exit(EXIT_SUCCESS);
}
}
解析:
-
pipe(pipefd)
:创建一个管道,pipefd[0]
用于读,pipefd[1]
用于写。 -
fork()
:创建一个子进程。 - 子进程:关闭写端,读取数据并输出到标准输出。
- 父进程:关闭读端,写入数据到管道,然后等待子进程结束。
运行结果:
子进程从管道读取数据:
来自父进程的消息!
2、消息队列(Message Queues)
概念与原理
消息队列是一种存储消息的数据结构,允许进程以消息的形式传递数据。不同进程可以向消息队列发送和接收消息,具有多个发送者和接收者。消息队列可以实现消息的优先级排序。
核心点
-
创建消息队列:使用
msgget
系统调用创建。 -
发送消息:使用
msgsnd
系统调用。 -
接收消息:使用
msgrcv
系统调用。 -
删除消息队列:使用
msgctl
系统调用。
实现实例
#include <iostream>
#include <sys/>
#include <sys/>
#include <cstring>
#include <>
#include <sys/>
// 定义消息结构体
struct message {
long msg_type;
char msg_text[100];
};
int main() {
key_t key = ftok("progfile", 65); // 生成唯一的键值
int msgid = msgget(key, 0666 | IPC_CREAT); // 创建消息队列
message msg;
if (fork() == 0) { // 子进程
msg.msg_type = 1; // 设置消息类型
strcpy(msg.msg_text, "来自子进程的消息");
msgsnd(msgid, &msg, sizeof(msg), 0); // 发送消息到消息队列
std::cout << "子进程发送消息: " << msg.msg_text << std::endl;
} else { // 父进程
wait(NULL); // 等待子进程结束
msgrcv(msgid, &msg, sizeof(msg), 1, 0); // 从消息队列接收消息
std::cout << "父进程接收到消息: " << msg.msg_text << std::endl;
msgctl(msgid, IPC_RMID, NULL); // 删除消息队列
}
return 0;
}
解析:
-
ftok("progfile", 65)
:生成一个唯一键。 -
msgget(key, 0666 | IPC_CREAT)
:创建消息队列。 - 子进程:设置消息类型,发送消息到消息队列。
- 父进程:等待子进程结束,从消息队列接收消息,然后删除消息队列。
运行结果:
子进程发送消息: 来自子进程的消息
父进程接收到消息: 来自子进程的消息
3、共享内存(Shared Memory)
概念与原理
共享内存是一种高效的进程间通信机制,允许多个进程直接访问同一块物理内存。共享内存是所有IPC机制中最快的,因为数据不需要在内核和用户空间之间进行复制。
核心点
-
创建共享内存:使用
shmget
系统调用创建。 -
附加共享内存:使用
shmat
系统调用将共享内存附加到进程地址空间。 -
分离共享内存:使用
shmdt
系统调用分离共享内存。 -
删除共享内存:使用
shmctl
系统调用删除共享内存。
实现实例
#include <iostream>
#include <sys/>
#include <sys/>
#include <sys/>
#include <cstring>
#include <>
#define SHM_SIZE 1024 // 共享内存大小
int main() {
key_t key = ftok("shmfile", 65); // 生成唯一键
int shmid = shmget(key, SHM_SIZE, 0666|IPC_CREAT); // 创建共享内存段
char *str = (char*) shmat(shmid, nullptr, 0); // 附加到共享内存
pid_t cpid = fork();
if (cpid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (cpid == 0) { // 子进程
sleep(1); // 确保父进程先写入
std::cout << "子进程从共享内存读取数据:\n";
std::cout << str << std::endl;
shmdt(str); // 分离共享内存
_exit(EXIT_SUCCESS);
} else { // 父进程
std::cout << "父进程写入共享内存:\n";
const char* msg = "来自父进程的消息";
strncpy(str, msg, SHM_SIZE);
wait(NULL); // 等待子进程
shmdt(str); // 分离共享内存
shmctl(shmid, IPC_RMID, nullptr); // 删除共享内存
exit(EXIT_SUCCESS);
}
}
解析:
-
ftok("shmfile", 65)
:生成一个唯一键。 -
shmget(key, SHM_SIZE, 0666 | IPC_CREAT)
:创建共享内存段。 -
shmat(shmid, NULL, 0)
:将共享内存段附加到进程地址空间。 - 子进程:等待父进程将数据写入共享内存,从共享内存读取数据并显示。
- 父进程:将数据写入共享内存,等待子进程结束,分离并删除共享内存段。
运行结果:
父进程写入共享内存:
子进程从共享内存读取数据:
来自父进程的消息
4、信号(Signals)
概念与原理
信号是一种用于进程间传递通知的机制。信号是一种异步通信方式,用于通知进程发生了某个事件。每个信号都有一个默认的处理动作,但进程可以定义自己的信号处理函数。
核心点
-
发送信号:使用
kill
系统调用。 -
处理信号:定义信号处理函数,并使用
signal
或sigaction
系统调用安装信号处理函数。 -
信号屏蔽:使用
sigprocmask
屏蔽和解除屏蔽信号。
实现实例
#include <iostream>
#include <csignal>
#include <>
// 信号处理函数
void signalHandler(int signum) {
std::cout << "收到信号: " << signum << std::endl;
exit(signum);
}
int main() {
signal(SIGINT, signalHandler); // 安装信号处理函数
pid_t cpid = fork();
if (cpid == 0) { // 子进程
sleep(2); // 等待2秒
kill(getppid(), SIGINT); // 向父进程发送SIGINT信号
_exit(0);
} else { // 父进程
std::cout << "父进程等待信号...\n";
pause(); // 等待信号
}
return 0;
}
解析:
-
signal(SIGINT, signalHandler)
:设置SIGINT信号的处理函数。 - 子进程:等待2秒后,向父进程发送SIGINT信号。
-
父进程:等待接收信号,当接收到SIGINT信号时,调用信号处理函数
signalHandler
。
运行结果:
父进程等待信号...
收到信号: 2
5、套接字(Sockets)
概念与原理
套接字是一种通用的通信机制,支持本地和网络通信。套接字提供了双向、可靠的字节流传输,常用于网络编程。本地套接字(Unix域套接字)可以用于同一主机上进程间的通信。
核心点
-
创建套接字:使用
socket
系统调用创建套接字。 -
绑定地址:使用
bind
系统调用绑定地址。 -
监听和连接:使用
listen
和accept
系统调用监听和接受连接。 -
发送和接收数据:使用
send
和recv
系统调用发送和接收数据。
实现实例
服务端代码():
#include <iostream>
#include <sys/>
#include <sys/>
#include <>
#define SOCKET_PATH "/tmp/unix_socket"
int main() {
int server_fd, client_fd;
struct sockaddr_un server_addr;
server_fd = socket(AF_UNIX, SOCK_STREAM, 0); // 创建套接字
if (server_fd < 0) {
perror("socket");
exit(EXIT_FAILURE);
}
server_addr.sun_family = AF_UNIX;
strcpy(server_addr.sun_path, SOCKET_PATH);
unlink(SOCKET_PATH);
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { // 绑定套接字
perror("bind");
close(server_fd);
exit(EXIT_FAILURE);
}
if (listen(server_fd, 5) < 0) { // 监听连接
perror("listen");
close(server_fd);
exit(EXIT_FAILURE);
}
std::cout << "等待连接...\n";
client_fd = accept(server_fd, NULL, NULL); // 接受连接
if (client_fd < 0) {
perror("accept");
close(server_fd);
exit(EXIT_FAILURE);
}
char buffer[100];
read(client_fd, buffer, sizeof(buffer)); // 读取数据
std::cout << "收到消息: " << buffer << std::endl;
const char* response = "消息已收到";
write(client_fd, response, strlen(response)); // 发送回复
close(client_fd);
close(server_fd);
unlink(SOCKET_PATH); // 删除套接字文件
return 0;
}
客户端代码():
#include <iostream>
#include <sys/>
#include <sys/>
#include <>
#define SOCKET_PATH "/tmp/unix_socket"
int main() {
int client_fd;
struct sockaddr_un server_addr;
client_fd = socket(AF_UNIX, SOCK_STREAM, 0); // 创建套接字
if (client_fd < 0) {
perror("socket");
exit(EXIT_FAILURE);
}
server_addr.sun_family = AF_UNIX;
strcpy(server_addr.sun_path, SOCKET_PATH);
if (connect(client_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { // 连接服务端
perror("connect");
close(client_fd);
exit(EXIT_FAILURE);
}
const char* msg = "来自客户端的消息";
write(client_fd, msg, strlen(msg)); // 发送消息
char buffer[100];
read(client_fd, buffer, sizeof(buffer)); // 读取回复
std::cout << "收到回复: " << buffer << std::endl;
close(client_fd);
return 0;
}
解析:
-
服务端:
- 创建套接字并绑定到文件路径。
- 监听连接,并接受客户端的连接请求。
- 从连接中读取数据并打印,然后发送回复。
- 关闭连接并删除套接字文件。
-
客户端:
- 创建套接字并连接到服务端。
- 发送消息给服务端并读取回复。
- 关闭连接。
运行结果:
// Server output
等待连接...
收到消息: 来自客户端的消息
// Client output
收到回复: 消息已收到
四、总结
通过上述详细的概念、原理和实例,我们深入理解了C++进程间通信的多种技术。这些技术各有优缺点和适用场景:
- 管道和命名管道:适用于父子进程间的简单数据传输。
- 消息队列:适用于消息传递和排队处理,具有较好的灵活性。
- 共享内存:适用于需要高效传输大数据量的场景,速度最快。
- 信号:适用于异步事件通知和进程控制。
- 套接字:适用于网络通信和本地进程间的通用通信。
在实际应用中,选择合适的IPC机制对于系统性能和稳定性至关重要。希望本文对你在C++进程间通信的学习和应用中有所帮助。