在操作系统中,多进程之间常见的通信方式有以下几种:
管道(Pipe):分为匿名管道和命名管道。匿名管道只能在具有亲缘关系(父子进程或兄弟进程)的进程间使用,而命名管道可以在不相关的进程间使用。
消息队列(Message Queue):消息队列是消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
共享内存(Shared Memory):多个进程可以访问同一块物理内存区域,这是最快的一种进程间通信方式,但需要进程自己进行同步和互斥控制。
信号量(Semaphore):主要用于进程间以及同一进程内不同线程之间的同步和互斥。
套接字(Socket):可用于不同主机上的进程间通信,也可用于同一主机上的进程间通信。
🚀 匿名管道 (Linux/MacOS)
#include <unistd.h>
#include <iostream>
int main() {
int fd;
pipe(fd); // 创建管道
if (fork() == 0) { // 子进程(读端)
close(fd); // 关闭写端
char buf[100];
read(fd, buf, sizeof(buf));
std::cout << "Child read: " << buf << std::endl;
} else { // 父进程(写端)
close(fd); // 关闭读端
const char* msg = "Hello via pipe!";
write(fd, msg, strlen(msg)+1);
wait(nullptr); // 等待子进程结束
}
return 0;
}
🔍 命名管道跨进程通信
写入端 (writer.cpp)
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
int main() {
mkfifo("/tmp/myfifo", 0666); // 创建命名管道
int fd = open("/tmp/myfifo", O_WRONLY);
write(fd, "Data from writer", 16);
close(fd);
}
读取端 (reader.cpp)
#include <iostream>
#include <fcntl.h>
#include <unistd.h>
int main() {
int fd = open("/tmp/myfifo", O_RDONLY);
char buf[100];
read(fd, buf, sizeof(buf));
std::cout << "Read from FIFO: " << buf << std::endl;
close(fd);
}
📨 消息队列示例
#include <sys/ipc.h>
#include <sys/msg.h>
#include <iostream>
struct msg_buffer {
long msg_type;
char msg_text[100];
} message;
int main() {
key_t key = ftok("msgqfile", 65);
int msgid = msgget(key, 0666 | IPC_CREAT);
if (fork() == 0) { // 子进程发送
message.msg_type = 1;
strcpy(message.msg_text, "Queue Message");
msgsnd(msgid, &message, sizeof(message), 0);
} else { // 父进程接收
msgrcv(msgid, &message, sizeof(message), 1, 0);
std::cout << "Received: " << message.msg_text << std::endl;
msgctl(msgid, IPC_RMID, NULL); // 清理队列
}
return 0;
}
💾 共享内存同步版
#include <sys/mman.h>
#include <sys/wait.h>
#include <unistd.h>
#include <iostream>
int main() {
const int SIZE = 4096;
int* shared = (int*)mmap(NULL, SIZE, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (fork() == 0) { // 子进程写入
*shared = 2025;
std::cout << "Child wrote: " << *shared << std::endl;
} else { // 父进程读取
wait(nullptr);
std::cout << "Parent read: " << *shared << std::endl;
munmap(shared, SIZE);
}
return 0;
}
⚖️ 信号量同步示例
#include <sys/sem.h>
#include <unistd.h>
#include <iostream>
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
int main() {
key_t key = ftok("semfile", 65);
int semid = semget(key, 1, 0666 | IPC_CREAT);
union semun arg;
arg.val = 1; // 初始值
semctl(semid, 0, SETVAL, arg);
struct sembuf op = {0, -1, 0}; // P操作
if (fork() == 0) { // 子进程
semop(semid, &op, 1);
std::cout << "Child entered critical section\n";
sleep(2);
op.sem_op = 1; // V操作
semop(semid, &op, 1);
} else { // 父进程
semop(semid, &op, 1);
std::cout << "Parent entered critical section\n";
sleep(2);
op.sem_op = 1;
semop(semid, &op, 1);
semctl(semid, 0, IPC_RMID); // 删除信号量
}
return 0;
}
🌐 TCP 套接字通信
服务端 (server.cpp)
#include <sys/socket.h>
#include <netinet/in.h>
#include <iostream>
int main() {
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in addr{AF_INET, htons(8080), INADDR_ANY};
bind(server_fd, (sockaddr*)&addr, sizeof(addr));
listen(server_fd, 5);
int client_fd = accept(server_fd, nullptr, nullptr);
char buf[100];
recv(client_fd, buf, sizeof(buf), 0);
std::cout << "Server received: " << buf << std::endl;
send(client_fd, "ACK", 4, 0);
close(server_fd);
}
客户端 (client.cpp)
#include <sys/socket.h>
#include <arpa/inet.h>
int main() {
int sock = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in addr{AF_INET, htons(8080), inet_addr("127.0.0.1")};
connect(sock, (sockaddr*)&addr, sizeof(addr));
send(sock, "Hello TCP!", 11, 0);
char ack;
recv(sock, ack, sizeof(ack), 0);
close(sock);
}
🔧 编译运行说明
匿名管道/共享内存/信号量
g++ -std=c++11 demo.cpp -o demo &&./demo
命名管道(需分开编译)
g++ writer.cpp -o writer && g++ reader.cpp -o reader
./writer &./reader
TCP 套接字(需双终端)
g++ server.cpp -o server && g++ client.cpp -o client
./server # 终端 1
./client # 终端 2
进程间通信方式详解
匿名管道
匿名管道用于有亲缘关系的进程,比如父子进程。需要用 pipe()
函数创建管道,父进程写数据,子进程读数据。记得关闭不用的端,比如父进程关闭读端,子进程关闭写端。要注意错误处理和正确使用 fork()
。
命名管道
命名管道可以在不相关进程间使用,所以需要创建 FIFO 文件。使用 mkfifo()
函数,然后一个进程以写方式打开,另一个以读方式打开。要注意打开模式,比如 O_WRONLY
和 O_RDONLY
,以及可能的阻塞问题。
消息队列
使用 msgget()
创建或获取队列,msgsnd()
发送消息,msgrcv()
接收。结构体需要包含长整型类型字段,消息内容可以是字符串。注意权限设置和错误检查。
共享内存
需要 shmget()
创建共享内存段,shmat()
附加到进程地址空间。写入和读取数据后要分离,最后删除共享内存。同时,可能需要用信号量来同步。
信号量
使用 sem_open()
创建或打开信号量,sem_wait()
和 sem_post()
进行 P/V 操作。示例可以展示进程间的同步,比如父进程和子进程交替执行。
套接字
可以用于不同主机或同一主机。这里用本地套接字,即 AF_UNIX
。服务器端创建 socket
,bind
,listen
,accept
,客户端 connect
后发送数据。注意地址结构体和关闭套接字。