您的位置:首页 >C++在Linux下如何进行进程间通信
发布于2026-04-26 阅读(0)
扫一扫,手机访问

在Linux环境下开发C++程序,进程间通信(IPC)是个绕不开的话题。当多个进程需要协同工作、交换数据时,Linux系统提供了多种成熟的方案。每种方案都有其独特的“性格”和适用场景,理解它们的差异,是写出高效、稳定程序的关键。接下来,我们就逐一拆解这些常用的IPC方法。
管道算得上是Linux IPC的“元老”了,它提供了一种半双工的通信方式。什么叫半双工?简单说,就是数据只能单向流动。这种机制在父子进程之间尤其好用,因为它直接继承了文件描述符,创建和使用都非常直观。
无名管道,顾名思义,它没有名字,生命周期随进程结束而终结。它最大的特点就是只能在具有亲缘关系的进程(比如父子进程)间使用。其工作原理是:先调用pipe()系统调用创建一个管道,这会返回两个文件描述符,一个用于读,一个用于写。父进程fork()出子进程后,双方各自关闭不需要的一端,就能建立起一条单向的数据通道。
#include
#include
#include
#include
int main() {
int pipefd[2];
pid_t pid;
char buffer[256];
// 创建管道
if (pipe(pipefd) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
pid = fork();
if (pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (pid == 0) { // 子进程
close(pipefd[1]); // 关闭写端
read(pipefd[0], buffer, sizeof(buffer));
std::cout << "Child received: " << buffer << std::endl;
close(pipefd[0]);
} else { // 父进程
close(pipefd[0]); // 关闭读端
const char* message = "Hello from parent";
write(pipefd[1], message, strlen(message) + 1);
close(pipefd[1]);
wait(NULL); // 等待子进程结束
}
return 0;
}
无名管道好用,但限制也明显——只能用于亲缘进程。这时候,命名管道(FIFO)就派上用场了。它在文件系统中有一个路径名,任何知道这个路径的进程都可以打开它进行读写,完全解除了亲缘关系的束缚。你可以把它想象成一个有名字的、先进先出的特殊文件。
#include
#include
#include
#include
#include
int main() {
const char* fifo_name = "/tmp/myfifo";
int fd;
char buffer[256];
// 创建命名管道
if (mkfifo(fifo_name, 0666) == -1) {
perror("mkfifo");
exit(EXIT_FAILURE);
}
fd = open(fifo_name, O_RDWR);
if (fd == -1) {
perror("open");
exit(EXIT_FAILURE);
}
// 写入数据
const char* message = "Hello from FIFO";
write(fd, message, strlen(message) + 1);
// 读取数据
read(fd, buffer, sizeof(buffer));
std::cout << "Received: " << buffer << std::endl;
close(fd);
unlink(fifo_name); // 删除命名管道
return 0;
}
如果说管道是字节流,那么消息队列就是“快递柜”。它允许进程以结构化的消息为单位进行数据交换,每条消息都有类型标识。发送方和接收方可以基于消息类型进行选择性接收,这提供了比管道更灵活的通信模式。消息队列独立于进程存在,即使通信进程结束了,消息仍然可以保留在队列中。
#include
#include
#include
#include
#include
struct msg_buffer {
long mtype;
char mtext[100];
};
int main() {
key_t key = ftok("msgqueue_example.c", 65);
int msgid = msgget(key, 0666 | IPC_CREAT);
if (msgid == -1) {
perror("msgget");
exit(EXIT_FAILURE);
}
msg_buffer buffer;
buffer.mtype = 1;
strcpy(buffer.mtext, "Hello from message queue");
// 发送消息
if (msgsnd(msgid, &buffer, sizeof(buffer.mtext), 0) == -1) {
perror("msgsnd");
exit(EXIT_FAILURE);
}
// 接收消息
if (msgrcv(msgid, &buffer, sizeof(buffer.mtext), 1, 0) == -1) {
perror("msgrcv");
exit(EXIT_FAILURE);
}
std::cout << "Received message: " << buffer.mtext << std::endl;
msgctl(msgid, IPC_RMID, NULL); // 删除消息队列
return 0;
}
追求极致性能?那共享内存绝对是你的首选。它的原理很直接:在内存中划出一块区域,让多个进程都能映射到自己的地址空间。这样一来,进程间的数据交换就变成了直接对内存的读写,完全避免了内核缓冲区的数据拷贝,速度自然是最快的。但天下没有免费的午餐,共享内存不提供任何同步机制,需要配合信号量等工具来防止数据竞争。
#include
#include
#include
#include
#include
int main() {
key_t key = ftok("shm_example.c", 65);
int shmid = shmget(key, 1024, 0666 | IPC_CREAT);
if (shmid == -1) {
perror("shmget");
exit(EXIT_FAILURE);
}
char* str = (char*) shmat(shmid, (void*)0, 0);
if (str == (char*)(-1)) {
perror("shmat");
exit(EXIT_FAILURE);
}
strcpy(str, "Hello from shared memory");
std::cout << "Message written in memory: " << str << std::endl;
shmdt(str); // 分离共享内存
// 删除共享内存
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
严格来说,信号量本身并不是为了传递数据,而是为了解决一个更根本的问题:进程同步。当多个进程需要访问同一块共享资源(比如刚提到的共享内存)时,如何避免冲突?信号量就像一个“资源计数器”或“通行证”。最常见的二元信号量(互斥锁)可以确保同一时刻只有一个进程能进入临界区。P操作(等待)尝试获取信号量,V操作(发信号)则释放信号量。
#include
#include
#include
#include
#include
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
int main() {
key_t key = ftok("semaphore_example.c", 65);
int semid = semget(key, 1, 0666 | IPC_CREAT);
if (semid == -1) {
perror("semget");
exit(EXIT_FAILURE);
}
union semun arg;
arg.val = 1; // 初始化信号量为1
if (semctl(semid, 0, SETVAL, arg) == -1) {
perror("semctl");
exit(EXIT_FAILURE);
}
// P操作(等待信号量)
struct sembuf sb = {0, -1, SEM_UNDO};
if (semop(semid, &sb, 1) == -1) {
perror("semop");
exit(EXIT_FAILURE);
}
std::cout << "Semaphore P operation completed" << std::endl;
// V操作(释放信号量)
sb.sem_op = 1;
if (semop(semid, &sb, 1) == -1) {
perror("semop");
exit(EXIT_FAILURE);
}
std::cout << "Semaphore V operation completed" << std::endl;
semctl(semid, 0, IPC_RMID); // 删除信号量
return 0;
}
最后登场的是功能最强大的套接字。它最初是为网络通信设计的,但同样完美适用于同一台主机上的进程间通信(通过Unix Domain Sockets)。套接字提供全双工通信,支持多种协议(如TCP的可靠流、UDP的数据报),并且是跨平台兼容性最好的IPC方式之一。如果未来你的程序有扩展到网络通信的可能,那么从一开始就使用套接字会是一个具有前瞻性的选择。
这是专门用于本地IPC的套接字,它通过文件系统路径名来标识,效率比网络套接字更高,因为它避免了网络协议栈的开销。
#include
#include
#include
#include
#include
#include
int main() {
struct sockaddr_un addr;
int sockfd, connfd;
char buffer[1024];
sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sockfd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, "/tmp/unix_socket", sizeof(addr.sun_path) - 1);
unlink("/tmp/unix_socket"); // 删除已存在的套接字文件
if (bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
perror("bind");
exit(EXIT_FAILURE);
}
if (listen(sockfd, 5) == -1) {
perror("listen");
exit(EXIT_FAILURE);
}
connfd = accept(sockfd, NULL, NULL);
if (connfd == -1) {
perror("accept");
exit(EXIT_FAILURE);
}
read(connfd, buffer, sizeof(buffer));
std::cout << "Received: " << buffer << std::endl;
close(connfd);
close(sockfd);
return 0;
}
好了,以上就是Linux下C++常用的几种进程间通信方法。简单总结一下:管道简单直接,适合亲缘进程;命名管道打破了亲缘限制;消息队列提供了结构化的消息传递;共享内存速度最快,但需手动同步;信号量是同步的基石;而套接字则是最通用、最强大的方案。实际开发中,没有绝对的“最佳”,只有最“合适”。理解它们的原理和特性,根据你的具体场景——是要求速度、简便性,还是未来的可扩展性——来做出选择,这才是关键所在。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
9