第47集:进程间通信
章节标题
进程间通信
核心知识点讲解
进程间通信的概念
进程间通信(IPC,Inter-Process Communication)是指不同进程之间交换数据和协调操作的机制。在Linux系统中,进程是相互独立的执行单元,它们有各自的地址空间,不能直接访问彼此的内存。因此,需要通过专门的IPC机制来实现进程间的通信。
常用的IPC机制
Linux系统提供了多种IPC机制,每种机制都有其特点和适用场景:
- 管道(Pipes):用于具有亲缘关系的进程之间的通信
- 命名管道(Named Pipes):用于无亲缘关系的进程之间的通信
- 信号(Signals):用于进程间的事件通知
- 共享内存(Shared Memory):用于高效的数据共享
- 消息队列(Message Queues):用于进程间的消息传递
- 信号量(Semaphores):用于进程间的同步
- 套接字(Sockets):用于网络通信,也可用于本地进程间通信
管道(Pipes)
匿名管道
匿名管道是最基本的IPC机制之一,它是一个单向的字节流,用于具有亲缘关系的进程之间的通信(如父子进程、兄弟进程)。
基本用法
在Shell中,可以使用 | 符号创建匿名管道:
# 使用管道将一个命令的输出作为另一个命令的输入
命令1 | 命令2
# 示例:使用管道统计文件行数
ls -l | wc -l
# 示例:使用管道过滤文件内容
grep "error" log.txt | head -10在C语言中,可以使用 pipe() 函数创建匿名管道:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
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));
printf("子进程收到:%s\n", buffer);
close(pipefd[0]); // 关闭读端
} else {
// 父进程:写入管道
close(pipefd[0]); // 关闭读端
char *message = "Hello from parent!";
write(pipefd[1], message, sizeof(message));
printf("父进程发送:%s\n", message);
close(pipefd[1]); // 关闭写端
// 等待子进程结束
wait(NULL);
}
return 0;
}特点
- 单向通信:数据只能从管道的一端流向另一端
- 只能用于具有亲缘关系的进程之间
- 管道中的数据是字节流,没有消息边界
- 管道是基于文件描述符的,使用完后需要关闭
命名管道
命名管道(FIFO,First-In-First-Out)是一种特殊的文件,用于无亲缘关系的进程之间的通信。
基本用法
在Shell中,可以使用 mkfifo 命令创建命名管道:
# 创建命名管道
mkfifo 管道名称
# 示例:创建命名管道
mkfifo mypipe
# 向命名管道写入数据
echo "Hello" > mypipe
# 从命名管道读取数据
cat mypipe在C语言中,可以使用 mkfifo() 函数创建命名管道:
// 写入端
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
const char *fifo_path = "/tmp/myfifo";
int fd;
char *message = "Hello from writer!";
// 创建命名管道
mkfifo(fifo_path, 0666);
// 打开命名管道进行写入
fd = open(fifo_path, O_WRONLY);
if (fd == -1) {
perror("open");
exit(EXIT_FAILURE);
}
// 写入数据
write(fd, message, sizeof(message));
printf("写入数据:%s\n", message);
// 关闭文件描述符
close(fd);
return 0;
}
// 读取端
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
const char *fifo_path = "/tmp/myfifo";
int fd;
char buffer[256];
// 打开命名管道进行读取
fd = open(fifo_path, O_RDONLY);
if (fd == -1) {
perror("open");
exit(EXIT_FAILURE);
}
// 读取数据
read(fd, buffer, sizeof(buffer));
printf("读取数据:%s\n", buffer);
// 关闭文件描述符
close(fd);
return 0;
}特点
- 可以用于无亲缘关系的进程之间
- 数据是字节流,没有消息边界
- 命名管道是一种特殊的文件,存在于文件系统中
- 使用完后需要删除命名管道文件
信号(Signals)
信号是一种异步的IPC机制,用于进程间的事件通知。当一个进程向另一个进程发送信号时,接收信号的进程会中断当前的执行,转而去处理该信号。
常用信号
| 信号编号 | 信号名称 | 描述 | 默认行为 |
|---|---|---|---|
| 1 | SIGHUP | 终端挂起或控制进程终止 | 终止进程 |
| 2 | SIGINT | 中断信号(Ctrl+C) | 终止进程 |
| 3 | SIGQUIT | 退出信号(Ctrl+\) | 终止进程并生成核心转储 |
| 9 | SIGKILL | 强制终止信号 | 强制终止进程(不可捕获) |
| 15 | SIGTERM | 终止信号 | 终止进程(默认信号) |
| 18 | SIGCONT | 继续信号 | 继续已暂停的进程 |
| 19 | SIGSTOP | 停止信号 | 暂停进程(不可捕获) |
| 20 | SIGTSTP | 终端停止信号(Ctrl+Z) | 暂停进程 |
基本用法
在Shell中,可以使用 kill 命令向进程发送信号:
# 向进程发送默认信号(SIGTERM)
kill 进程ID
# 向进程发送指定信号
kill -信号编号 进程ID
kill -信号名称 进程ID
# 示例:向进程发送SIGINT信号
kill -2 1234
# 示例:向进程发送SIGKILL信号
kill -9 1234在C语言中,可以使用 kill() 函数向进程发送信号:
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
// 信号处理函数
void signal_handler(int signum) {
printf("收到信号:%d\n", signum);
switch (signum) {
case SIGINT:
printf("处理SIGINT信号(Ctrl+C)\n");
break;
case SIGTERM:
printf("处理SIGTERM信号\n");
break;
default:
printf("处理其他信号\n");
break;
}
}
int main() {
pid_t pid;
// 创建子进程
pid = fork();
if (pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (pid == 0) {
// 子进程:设置信号处理函数
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
printf("子进程启动,PID:%d\n", getpid());
printf("等待信号...\n");
// 无限循环,等待信号
while (1) {
sleep(1);
}
} else {
// 父进程:发送信号
printf("父进程启动,子进程PID:%d\n", pid);
// 等待2秒
sleep(2);
// 发送SIGINT信号
printf("发送SIGINT信号...\n");
kill(pid, SIGINT);
// 等待2秒
sleep(2);
// 发送SIGTERM信号
printf("发送SIGTERM信号...\n");
kill(pid, SIGTERM);
// 等待子进程结束
wait(NULL);
printf("父进程结束\n");
}
return 0;
}特点
- 用于进程间的事件通知
- 信号是异步的,接收进程不知道信号何时到达
- 有些信号是不可捕获的(如SIGKILL、SIGSTOP)
- 信号处理函数应该简短,避免执行耗时操作
共享内存(Shared Memory)
共享内存是最快的IPC机制,它允许多个进程直接访问同一块物理内存区域,不需要数据拷贝。
基本用法
在C语言中,可以使用 shmget()、shmat()、shmdt() 和 shmctl() 函数来使用共享内存:
// 写入端
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
key_t key;
int shmid;
char *shmaddr;
const char *message = "Hello from shared memory!";
// 生成键值
key = ftok("shmfile", 65);
// 创建共享内存段
shmid = shmget(key, 1024, 0666 | IPC_CREAT);
if (shmid == -1) {
perror("shmget");
exit(EXIT_FAILURE);
}
// 附加共享内存
shmaddr = (char *)shmat(shmid, NULL, 0);
if (shmaddr == (char *)-1) {
perror("shmat");
exit(EXIT_FAILURE);
}
// 写入数据
strcpy(shmaddr, message);
printf("写入共享内存:%s\n", message);
// 等待读取
printf("等待读取...\n");
while (*shmaddr != '*') {
sleep(1);
}
// 分离共享内存
shmdt(shmaddr);
// 删除共享内存
shmctl(shmid, IPC_RMID, NULL);
printf("写入端结束\n");
return 0;
}
// 读取端
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
key_t key;
int shmid;
char *shmaddr;
// 生成键值(与写入端相同)
key = ftok("shmfile", 65);
// 获取共享内存段
shmid = shmget(key, 1024, 0666);
if (shmid == -1) {
perror("shmget");
exit(EXIT_FAILURE);
}
// 附加共享内存
shmaddr = (char *)shmat(shmid, NULL, 0);
if (shmaddr == (char *)-1) {
perror("shmat");
exit(EXIT_FAILURE);
}
// 读取数据
printf("读取共享内存:%s\n", shmaddr);
// 标记读取完成
*shmaddr = '*';
// 分离共享内存
shmdt(shmaddr);
printf("读取端结束\n");
return 0;
}特点
- 最快的IPC机制,不需要数据拷贝
- 多个进程可以同时访问同一块内存
- 需要使用信号量等机制来同步对共享内存的访问
- 共享内存段需要显式创建和删除
消息队列(Message Queues)
消息队列是一种用于进程间传递消息的IPC机制,它允许进程发送和接收有结构的消息。
基本用法
在C语言中,可以使用 msgget()、msgsnd()、msgrcv() 和 msgctl() 函数来使用消息队列:
// 发送端
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 消息结构
typedef struct {
long mtype; // 消息类型
char mtext[100]; // 消息内容
} message;
int main() {
key_t key;
int msgid;
message msg;
// 生成键值
key = ftok("msgfile", 65);
// 创建消息队列
msgid = msgget(key, 0666 | IPC_CREAT);
if (msgid == -1) {
perror("msgget");
exit(EXIT_FAILURE);
}
// 设置消息类型和内容
msg.mtype = 1;
strcpy(msg.mtext, "Hello from message queue!");
// 发送消息
if (msgsnd(msgid, &msg, sizeof(msg.mtext), 0) == -1) {
perror("msgsnd");
exit(EXIT_FAILURE);
}
printf("发送消息:%s\n", msg.mtext);
// 删除消息队列
msgctl(msgid, IPC_RMID, NULL);
printf("发送端结束\n");
return 0;
}
// 接收端
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>
// 消息结构(与发送端相同)
typedef struct {
long mtype; // 消息类型
char mtext[100]; // 消息内容
} message;
int main() {
key_t key;
int msgid;
message msg;
// 生成键值(与发送端相同)
key = ftok("msgfile", 65);
// 获取消息队列
msgid = msgget(key, 0666);
if (msgid == -1) {
perror("msgget");
exit(EXIT_FAILURE);
}
// 接收消息
if (msgrcv(msgid, &msg, sizeof(msg.mtext), 1, 0) == -1) {
perror("msgrcv");
exit(EXIT_FAILURE);
}
printf("接收消息:%s\n", msg.mtext);
printf("接收端结束\n");
return 0;
}特点
- 用于进程间传递有结构的消息
- 消息队列是消息的链表,具有消息类型
- 消息队列中的消息可以按照类型接收
- 消息队列需要显式创建和删除
信号量(Semaphores)
信号量是一种用于进程间同步的IPC机制,它可以用来协调多个进程对共享资源的访问。
基本用法
在C语言中,可以使用 semget()、semop() 和 semctl() 函数来使用信号量:
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
// 信号量操作函数
void sem_wait(int semid) {
struct sembuf sb;
sb.sem_num = 0;
sb.sem_op = -1; // P操作
sb.sem_flg = 0;
semop(semid, &sb, 1);
}
void sem_post(int semid) {
struct sembuf sb;
sb.sem_num = 0;
sb.sem_op = 1; // V操作
sb.sem_flg = 0;
semop(semid, &sb, 1);
}
int main() {
key_t key;
int semid;
pid_t pid;
// 生成键值
key = ftok("semfile", 65);
// 创建信号量
semid = semget(key, 1, 0666 | IPC_CREAT);
if (semid == -1) {
perror("semget");
exit(EXIT_FAILURE);
}
// 初始化信号量值为1(互斥锁)
semctl(semid, 0, SETVAL, 1);
// 创建子进程
pid = fork();
if (pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (pid == 0) {
// 子进程
printf("子进程:等待进入临界区...\n");
sem_wait(semid); // P操作
// 临界区
printf("子进程:进入临界区\n");
sleep(3); // 模拟耗时操作
printf("子进程:离开临界区\n");
sem_post(semid); // V操作
} else {
// 父进程
printf("父进程:等待进入临界区...\n");
sem_wait(semid); // P操作
// 临界区
printf("父进程:进入临界区\n");
sleep(3); // 模拟耗时操作
printf("父进程:离开临界区\n");
sem_post(semid); // V操作
// 等待子进程结束
wait(NULL);
// 删除信号量
semctl(semid, 0, IPC_RMID);
}
return 0;
}特点
- 用于进程间的同步和互斥
- 信号量是一个计数器,表示可用资源的数量
- P操作(sem_wait):获取资源,信号量减1
- V操作(sem_post):释放资源,信号量加1
- 信号量需要显式创建和删除
套接字(Sockets)
套接字是一种用于网络通信的IPC机制,也可以用于本地进程间通信。
基本用法
在C语言中,可以使用 socket()、bind()、listen()、accept()、connect()、read()、write() 和 close() 函数来使用套接字:
// 服务器端(本地套接字)
#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main() {
int server_fd, client_fd;
struct sockaddr_un addr;
char buffer[1024];
// 创建套接字
server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
// 设置地址
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, "/tmp/local_socket");
// 删除旧的套接字文件
unlink(addr.sun_path);
// 绑定地址
if (bind(server_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
perror("bind");
exit(EXIT_FAILURE);
}
// 监听连接
if (listen(server_fd, 5) == -1) {
perror("listen");
exit(EXIT_FAILURE);
}
printf("服务器启动,等待连接...\n");
// 接受连接
client_fd = accept(server_fd, NULL, NULL);
if (client_fd == -1) {
perror("accept");
exit(EXIT_FAILURE);
}
printf("客户端连接成功\n");
// 读取数据
read(client_fd, buffer, sizeof(buffer));
printf("收到消息:%s\n", buffer);
// 发送数据
char *response = "Hello from server!";
write(client_fd, response, strlen(response) + 1);
printf("发送消息:%s\n", response);
// 关闭连接
close(client_fd);
close(server_fd);
// 删除套接字文件
unlink(addr.sun_path);
printf("服务器结束\n");
return 0;
}
// 客户端(本地套接字)
#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main() {
int sock_fd;
struct sockaddr_un addr;
char buffer[1024];
// 创建套接字
sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock_fd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
// 设置地址
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, "/tmp/local_socket");
// 连接服务器
if (connect(sock_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
perror("connect");
exit(EXIT_FAILURE);
}
printf("连接服务器成功\n");
// 发送数据
char *message = "Hello from client!";
write(sock_fd, message, strlen(message) + 1);
printf("发送消息:%s\n", message);
// 读取数据
read(sock_fd, buffer, sizeof(buffer));
printf("收到消息:%s\n", buffer);
// 关闭连接
close(sock_fd);
printf("客户端结束\n");
return 0;
}特点
- 可用于网络通信和本地进程间通信
- 支持多种通信协议(TCP、UDP等)
- 提供可靠的面向连接的通信(TCP)
- 本地套接字使用文件系统路径作为地址
实用案例分析
案例1:使用管道进行进程间通信
# 示例1:使用管道统计日志文件中的错误数量
grep "ERROR" /var/log/syslog | wc -l
# 示例2:使用管道过滤和排序文件内容
ls -la | grep "\.txt" | sort -k 5 -n
# 示例3:使用管道将命令输出保存到文件并同时显示
echo "Hello World" | tee output.txt
# 示例4:使用管道进行多命令组合
ps aux | grep "nginx" | grep -v grep | awk '{print $2}' | xargs kill案例2:使用命名管道进行无亲缘关系进程间通信
# 1. 创建命名管道
mkfifo /tmp/data_pipe
# 2. 在一个终端中启动读取进程
cat /tmp/data_pipe
# 3. 在另一个终端中启动写入进程
echo "Hello from another terminal" > /tmp/data_pipe
# 4. 清理命名管道
rm /tmp/data_pipe案例3:使用信号进行进程控制
# 1. 启动一个长时间运行的进程
sleep 300 &
# 2. 查看进程ID
pid=$!
# 3. 发送SIGINT信号(模拟Ctrl+C)
kill -2 $pid
# 4. 启动另一个进程
sleep 300 &
pid=$!
# 5. 发送SIGTERM信号(默认终止信号)
kill $pid
# 6. 启动另一个进程
sleep 300 &
pid=$!
# 7. 发送SIGKILL信号(强制终止)
kill -9 $pid案例4:使用共享内存进行高效数据传输
// 共享内存示例:数据传输
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define SHM_SIZE 1024 * 1024 // 1MB共享内存
int main() {
key_t key;
int shmid;
char *shmaddr;
pid_t pid;
clock_t start, end;
double time_taken;
// 生成键值
key = ftok("shm_test", 65);
// 创建共享内存
shmid = shmget(key, SHM_SIZE, 0666 | IPC_CREAT);
if (shmid == -1) {
perror("shmget");
exit(EXIT_FAILURE);
}
// 创建子进程
pid = fork();
if (pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (pid == 0) {
// 子进程:读取共享内存
shmaddr = (char *)shmat(shmid, NULL, 0);
if (shmaddr == (char *)-1) {
perror("shmat");
exit(EXIT_FAILURE);
}
// 等待数据
while (*shmaddr == 0) {
sleep(1);
}
// 读取数据
start = clock();
printf("子进程:读取数据大小:%ld bytes\n", strlen(shmaddr));
end = clock();
time_taken = ((double)(end - start)) / CLOCKS_PER_SEC;
printf("子进程:读取时间:%f seconds\n", time_taken);
// 分离共享内存
shmdt(shmaddr);
} else {
// 父进程:写入共享内存
shmaddr = (char *)shmat(shmid, NULL, 0);
if (shmaddr == (char *)-1) {
perror("shmat");
exit(EXIT_FAILURE);
}
// 写入大量数据
start = clock();
memset(shmaddr, 'A', SHM_SIZE - 1);
shmaddr[SHM_SIZE - 1] = '\0';
end = clock();
time_taken = ((double)(end - start)) / CLOCKS_PER_SEC;
printf("父进程:写入数据大小:%ld bytes\n", strlen(shmaddr));
printf("父进程:写入时间:%f seconds\n", time_taken);
// 通知子进程数据已写入
*shmaddr = 'A';
// 等待子进程结束
wait(NULL);
// 分离共享内存
shmdt(shmaddr);
// 删除共享内存
shmctl(shmid, IPC_RMID, NULL);
}
return 0;
}案例5:使用信号量进行进程同步
// 信号量示例:生产者-消费者问题
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define BUFFER_SIZE 5
#define ITEM_COUNT 10
// 缓冲区
int buffer[BUFFER_SIZE];
int in = 0; // 写入位置
int out = 0; // 读取位置
// 信号量
int empty_sem; // 空槽位数量
int full_sem; // 已使用槽位数量
int mutex_sem; // 互斥锁
// 信号量操作函数
void sem_wait(int semid) {
struct sembuf sb;
sb.sem_num = 0;
sb.sem_op = -1;
sb.sem_flg = 0;
semop(semid, &sb, 1);
}
void sem_post(int semid) {
struct sembuf sb;
sb.sem_num = 0;
sb.sem_op = 1;
sb.sem_flg = 0;
semop(semid, &sb, 1);
}
// 生产者函数
void producer() {
int item;
for (int i = 0; i < ITEM_COUNT; i++) {
// 生产一个item
item = i;
printf("生产者:生产 item %d\n", item);
// 等待空槽位
sem_wait(empty_sem);
// 进入临界区
sem_wait(mutex_sem);
// 将item放入缓冲区
buffer[in] = item;
in = (in + 1) % BUFFER_SIZE;
printf("生产者:将 item %d 放入缓冲区\n", item);
// 离开临界区
sem_post(mutex_sem);
// 通知有新item
sem_post(full_sem);
// 随机休眠
sleep(rand() % 2);
}
}
// 消费者函数
void consumer() {
int item;
for (int i = 0; i < ITEM_COUNT; i++) {
// 等待有item
sem_wait(full_sem);
// 进入临界区
sem_wait(mutex_sem);
// 从缓冲区取出item
item = buffer[out];
out = (out + 1) % BUFFER_SIZE;
printf("消费者:从缓冲区取出 item %d\n", item);
// 离开临界区
sem_post(mutex_sem);
// 通知有空槽位
sem_post(empty_sem);
// 消费item
printf("消费者:消费 item %d\n", item);
// 随机休眠
sleep(rand() % 3);
}
}
int main() {
key_t key;
pid_t pid;
// 生成键值
key = ftok("sem_prod_cons", 65);
// 创建信号量
empty_sem = semget(key, 1, 0666 | IPC_CREAT);
full_sem = semget(key + 1, 1, 0666 | IPC_CREAT);
mutex_sem = semget(key + 2, 1, 0666 | IPC_CREAT);
// 初始化信号量
semctl(empty_sem, 0, SETVAL, BUFFER_SIZE); // 初始空槽位为缓冲区大小
semctl(full_sem, 0, SETVAL, 0); // 初始已使用槽位为0
semctl(mutex_sem, 0, SETVAL, 1); // 互斥锁初始值为1
// 创建子进程
pid = fork();
if (pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (pid == 0) {
// 子进程:消费者
consumer();
} else {
// 父进程:生产者
producer();
// 等待子进程结束
wait(NULL);
// 删除信号量
semctl(empty_sem, 0, IPC_RMID);
semctl(full_sem, 0, IPC_RMID);
semctl(mutex_sem, 0, IPC_RMID);
printf("生产者-消费者演示结束\n");
}
return 0;
}代码示例
示例1:管道通信示例
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
int pipefd[2];
pid_t pid;
char buffer[256];
int bytes_read;
// 创建管道
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]); // 关闭写端
printf("子进程:等待读取数据...\n");
// 读取数据
bytes_read = read(pipefd[0], buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer[bytes_read] = '\0';
printf("子进程:读取到 %d 字节数据:%s\n", bytes_read, buffer);
}
// 向父进程发送响应
printf("子进程:发送响应...\n");
write(pipefd[0], "Hello from child!", 17);
close(pipefd[0]); // 关闭读端
exit(EXIT_SUCCESS);
} else {
// 父进程:写入管道
close(pipefd[0]); // 关闭读端
// 向子进程发送数据
char *message = "Hello from parent! This is a test message.";
printf("父进程:发送数据:%s\n", message);
write(pipefd[1], message, strlen(message));
// 等待子进程响应
sleep(1);
// 重新打开读端
close(pipefd[1]);
int pipefd_read[2];
pipe(pipefd_read);
// 读取子进程响应
bytes_read = read(pipefd_read[0], buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer[bytes_read] = '\0';
printf("父进程:读取到子进程响应:%s\n", buffer);
}
close(pipefd_read[0]);
close(pipefd_read[1]);
// 等待子进程结束
wait(NULL);
printf("父进程:子进程结束\n");
}
return 0;
}示例2:消息队列示例
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 消息结构
typedef struct {
long mtype; // 消息类型
int id; // 消息ID
char text[200]; // 消息内容
} message;
int main() {
key_t key;
int msgid;
pid_t pid;
message msg;
// 生成键值
key = ftok("msg_example", 65);
// 创建消息队列
msgid = msgget(key, 0666 | IPC_CREAT);
if (msgid == -1) {
perror("msgget");
exit(EXIT_FAILURE);
}
// 创建子进程
pid = fork();
if (pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (pid == 0) {
// 子进程:接收消息
printf("子进程:等待接收消息...\n");
// 接收类型为1的消息
if (msgrcv(msgid, &msg, sizeof(msg) - sizeof(long), 1, 0) == -1) {
perror("msgrcv");
exit(EXIT_FAILURE);
}
printf("子进程:接收到消息\n");
printf(" 类型:%ld\n", msg.mtype);
printf(" ID:%d\n", msg.id);
printf(" 内容:%s\n", msg.text);
// 发送响应消息(类型为2)
msg.mtype = 2;
msg.id = 2;
strcpy(msg.text, "Message received successfully!");
if (msgsnd(msgid, &msg, sizeof(msg) - sizeof(long), 0) == -1) {
perror("msgsnd");
exit(EXIT_FAILURE);
}
printf("子进程:发送响应消息\n");
} else {
// 父进程:发送消息
printf("父进程:准备发送消息...\n");
// 准备消息
msg.mtype = 1;
msg.id = 1;
strcpy(msg.text, "Hello from parent process! This is a structured message.");
// 发送消息
if (msgsnd(msgid, &msg, sizeof(msg) - sizeof(long), 0) == -1) {
perror("msgsnd");
exit(EXIT_FAILURE);
}
printf("父进程:消息发送成功\n");
// 等待子进程响应
printf("父进程:等待子进程响应...\n");
// 接收类型为2的消息
if (msgrcv(msgid, &msg, sizeof(msg) - sizeof(long), 2, 0) == -1) {
perror("msgrcv");
exit(EXIT_FAILURE);
}
printf("父进程:接收到响应消息\n");
printf(" 类型:%ld\n", msg.mtype);
printf(" ID:%d\n", msg.id);
printf(" 内容:%s\n", msg.text);
// 等待子进程结束
wait(NULL);
// 删除消息队列
msgctl(msgid, IPC_RMID, NULL);
printf("父进程:消息队列已删除\n");
}
return 0;
}示例3:本地套接字示例
// 服务器端
#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#define SOCKET_PATH "/tmp/local_server.sock"
int main() {
int server_fd, client_fd;
struct sockaddr_un addr;
char buffer[1024];
ssize_t bytes_read;
// 创建套接字
server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
// 设置地址
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1);
// 删除旧的套接字文件
unlink(SOCKET_PATH);
// 绑定地址
if (bind(server_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
perror("bind");
exit(EXIT_FAILURE);
}
// 监听连接
if (listen(server_fd, 5) == -1) {
perror("listen");
exit(EXIT_FAILURE);
}
printf("服务器启动,等待连接...\n");
printf("套接字路径:%s\n", SOCKET_PATH);
// 接受连接
client_fd = accept(server_fd, NULL, NULL);
if (client_fd == -1) {
perror("accept");
exit(EXIT_FAILURE);
}
printf("客户端连接成功\n");
// 循环处理消息
while (1) {
// 读取消息
bytes_read = read(client_fd, buffer, sizeof(buffer) - 1);
if (bytes_read == -1) {
perror("read");
break;
}
if (bytes_read == 0) {
printf("客户端断开连接\n");
break;
}
buffer[bytes_read] = '\0';
printf("收到消息:%s\n", buffer);
// 检查是否为退出消息
if (strcmp(buffer, "exit") == 0) {
printf("收到退出命令\n");
break;
}
// 发送响应
char response[1024];
snprintf(response, sizeof(response), "Server received: %s", buffer);
if (write(client_fd, response, strlen(response)) == -1) {
perror("write");
break;
}
printf("发送响应:%s\n", response);
}
// 关闭连接
close(client_fd);
close(server_fd);
// 删除套接字文件
unlink(SOCKET_PATH);
printf("服务器关闭\n");
return 0;
}
// 客户端
#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#define SOCKET_PATH "/tmp/local_server.sock"
int main() {
int sock_fd;
struct sockaddr_un addr;
char buffer[1024];
ssize_t bytes_read;
// 创建套接字
sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock_fd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
// 设置地址
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1);
// 连接服务器
if (connect(sock_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
perror("connect");
exit(EXIT_FAILURE);
}
printf("连接服务器成功\n");
printf("输入消息发送给服务器(输入'exit'退出):\n");
// 循环发送消息
while (1) {
// 读取用户输入
printf("> ");
if (fgets(buffer, sizeof(buffer), stdin) == NULL) {
break;
}
// 移除换行符
buffer[strcspn(buffer, "\n")] = '\0';
// 发送消息
if (write(sock_fd, buffer, strlen(buffer)) == -1) {
perror("write");
break;
}
// 检查是否为退出消息
if (strcmp(buffer, "exit") == 0) {
break;
}
// 读取响应
bytes_read = read(sock_fd, buffer, sizeof(buffer) - 1);
if (bytes_read == -1) {
perror("read");
break;
}
if (bytes_read == 0) {
printf("服务器断开连接\n");
break;
}
buffer[bytes_read] = '\0';
printf("服务器响应:%s\n", buffer);
}
// 关闭连接
close(sock_fd);
printf("客户端关闭\n");
return 0;
}总结
本集介绍了 Linux 中的进程间通信(IPC)机制,包括:
- 管道(Pipes):用于具有亲缘关系的进程之间的通信,包括匿名管道和命名管道
- 信号(Signals):用于进程间的事件通知,如中断、终止等
- 共享内存(Shared Memory):最快的IPC机制,允许多个进程直接访问同一块内存
- 消息队列(Message Queues):用于进程间传递有结构的消息
- 信号量(Semaphores):用于进程间的同步和互斥,协调对共享资源的访问
- 套接字(Sockets):用于网络通信,也可用于本地进程间通信
同时,本集还介绍了这些 IPC 机制的基本用法、特点和适用场景,以及多个实用案例和代码示例,包括:
- 使用管道进行进程间通信
- 使用命名管道进行无亲缘关系进程间通信
- 使用信号进行进程控制
- 使用共享内存进行高效数据传输
- 使用信号量进行进程同步(生产者-消费者问题)
- 管道通信示例
- 消息队列示例
- 本地套接字示例
通过掌握这些 IPC 机制,用户可以根据具体的应用场景选择合适的通信方式,实现进程间的高效通信和协调。在实际应用中,常常需要结合多种 IPC 机制来满足不同的通信需求,例如使用共享内存进行数据传输,同时使用信号量进行同步。