创建两个 C 语言文件,实现使用共享内存和信号量集进行两个进程间的双向聊天功能。这两个文件分别为chat1.c和chat2.c,它们可以互相发送和接收消息。
chat1.c
#include <stdio.h> // 标准输入输出库
#include <stdlib.h> // 标准库,包含exit等函数
#include <string.h> // 字符串处理函数库
#include <sys/ipc.h> // 包含IPC(进程间通信)相关函数定义
#include <sys/shm.h> // 共享内存相关函数定义
#include <sys/sem.h> // 信号量相关函数定义
#include <sys/types.h> // 基本系统数据类型
#include <unistd.h> // POSIX操作系统API
#include <signal.h> // 信号处理相关函数#define SHM_SIZE 1024 // 定义共享内存大小为1024字节
#define SEM_KEY 0x1234 // 定义信号量集的键值,两个程序需使用相同值
#define SHM_KEY 0x5678 // 定义共享内存的键值,两个程序需使用相同值// 信号量操作函数,用于执行P/V操作
// semid: 信号量集ID, semnum: 信号量编号, op: 操作类型(1为V操作, -1为P操作)
void sem_op(int semid, int semnum, int op) {struct sembuf sops; // 定义信号量操作结构体sops.sem_num = semnum; // 设置要操作的信号量编号sops.sem_op = op; // 设置操作类型sops.sem_flg = 0; // 操作标志,0表示无特殊标志// 执行信号量操作,若失败则输出错误信息并退出if (semop(semid, &sops, 1) == -1) {perror("semop"); // 输出错误信息exit(EXIT_FAILURE); // 异常退出程序}
}// 定义共享内存中的数据结构
typedef struct {char message[SHM_SIZE]; // 用于存储消息内容的缓冲区int flag; // 消息状态标志,0表示无消息,1表示有消息
} SharedData;int shmid; // 共享内存ID
int semid; // 信号量集ID
SharedData *shm; // 指向共享内存的指针// 清理函数,用于程序退出时释放资源
// signum: 接收到的信号编号
void cleanup(int signum) {// 如果是用户按下Ctrl+C产生的SIGINT信号if (signum == SIGINT) {strcpy(shm->message, "exit"); // 在共享内存中写入退出消息shm->flag = 1; // 标记有新消息sem_op(semid, 1, 1); // 执行V操作,允许进程2读取消息}// 分离共享内存,不再与当前进程地址空间关联if (shmdt(shm) == -1) {perror("shmdt"); // 输出错误信息}// 删除共享内存和信号量集(因为是创建者)shmctl(shmid, IPC_RMID, NULL); // 删除共享内存semctl(semid, 0, IPC_RMID); // 删除信号量集printf("\n聊天结束\n"); // 输出结束信息exit(EXIT_SUCCESS); // 正常退出程序
}int main() {key_t semkey = SEM_KEY; // 信号量集键值key_t shmkey = SHM_KEY; // 共享内存键值union semun sem_union; // 信号量操作的联合体// 创建一个包含2个信号量的信号量集,权限为0666(所有用户可读写)semid = semget(semkey, 2, IPC_CREAT | 0666);if (semid == -1) { // 检查创建是否成功perror("semget"); // 输出错误信息exit(EXIT_FAILURE); // 异常退出}// 初始化信号量0(用于控制进程1发送),初始值为1(允许发送)sem_union.val = 1;if (semctl(semid, 0, SETVAL, sem_union) == -1) {perror("semctl"); // 输出错误信息exit(EXIT_FAILURE); // 异常退出}// 初始化信号量1(用于控制进程2发送),初始值为0(等待发送)sem_union.val = 0;if (semctl(semid, 1, SETVAL, sem_union) == -1) {perror("semctl"); // 输出错误信息exit(EXIT_FAILURE); // 异常退出}// 创建共享内存,大小为SharedData结构体大小,权限0666shmid = shmget(shmkey, sizeof(SharedData), IPC_CREAT | 0666);if (shmid == -1) { // 检查创建是否成功perror("shmget"); // 输出错误信息exit(EXIT_FAILURE); // 异常退出}// 将共享内存连接到当前进程的地址空间shm = (SharedData *)shmat(shmid, NULL, 0);if (shm == (void *)-1) { // 检查连接是否成功perror("shmat"); // 输出错误信息exit(EXIT_FAILURE); // 异常退出}// 初始化共享内存,清空消息缓冲区,设置标志为无消息memset(shm->message, 0, SHM_SIZE);shm->flag = 0;// 设置信号处理函数,当接收到SIGINT信号(Ctrl+C)时调用cleanupsignal(SIGINT, cleanup);printf("聊天程序启动 (进程1),输入exit退出\n"); // 提示启动信息while (1) { // 主循环,持续运行直到退出// 执行P操作,获取发送权限(信号量0)sem_op(semid, 0, -1);// 提示用户输入消息printf("我: ");fflush(stdout); // 刷新标准输出,确保提示信息显示// 从标准输入读取用户输入的消息if (fgets(shm->message, SHM_SIZE, stdin) == NULL) {perror("fgets"); // 输出错误信息break; // 退出循环}// 移除输入消息中的换行符shm->message[strcspn(shm->message, "\n")] = '\0';// 检查用户是否输入"exit",如果是则退出程序if (strcmp(shm->message, "exit") == 0) {shm->flag = 1; // 标记有新消息sem_op(semid, 1, 1); // 执行V操作,允许进程2读取消息cleanup(0); // 调用清理函数退出}shm->flag = 1; // 标记共享内存中有新消息// 执行V操作,允许进程2读取消息(信号量1)sem_op(semid, 1, 1);// 执行P操作,等待接收进程2的消息(信号量0)sem_op(semid, 0, -1);// 检查对方是否发送了退出消息if (strcmp(shm->message, "exit") == 0) {printf("对方已退出\n"); // 提示用户对方已退出cleanup(0); // 调用清理函数退出}// 显示对方发送的消息printf("对方: %s\n", shm->message);memset(shm->message, 0, SHM_SIZE); // 清空消息缓冲区shm->flag = 0; // 标记消息已处理// 执行V操作,允许自己继续发送消息(信号量0)sem_op(semid, 0, 1);}cleanup(0); // 程序结束时调用清理函数return 0; // 主函数返回
}
chat2.c
#include <stdio.h> // 标准输入输出库
#include <stdlib.h> // 标准库,包含exit等函数
#include <string.h> // 字符串处理函数库
#include <sys/ipc.h> // 包含IPC(进程间通信)相关函数定义
#include <sys/shm.h> // 共享内存相关函数定义
#include <sys/sem.h> // 信号量相关函数定义
#include <sys/types.h> // 基本系统数据类型
#include <unistd.h> // POSIX操作系统API
#include <signal.h> // 信号处理相关函数#define SHM_SIZE 1024 // 定义共享内存大小为1024字节
#define SEM_KEY 0x1234 // 定义信号量集的键值,与chat1相同
#define SHM_KEY 0x5678 // 定义共享内存的键值,与chat1相同// 信号量操作函数,用于执行P/V操作
// semid: 信号量集ID, semnum: 信号量编号, op: 操作类型(1为V操作, -1为P操作)
void sem_op(int semid, int semnum, int op) {struct sembuf sops; // 定义信号量操作结构体sops.sem_num = semnum; // 设置要操作的信号量编号sops.sem_op = op; // 设置操作类型sops.sem_flg = 0; // 操作标志,0表示无特殊标志// 执行信号量操作,若失败则输出错误信息并退出if (semop(semid, &sops, 1) == -1) {perror("semop"); // 输出错误信息exit(EXIT_FAILURE); // 异常退出程序}
}// 定义共享内存中的数据结构
typedef struct {char message[SHM_SIZE]; // 用于存储消息内容的缓冲区int flag; // 消息状态标志,0表示无消息,1表示有消息
} SharedData;int shmid; // 共享内存ID
int semid; // 信号量集ID
SharedData *shm; // 指向共享内存的指针// 清理函数,用于程序退出时释放资源
// signum: 接收到的信号编号
void cleanup(int signum) {// 如果是用户按下Ctrl+C产生的SIGINT信号if (signum == SIGINT) {strcpy(shm->message, "exit"); // 在共享内存中写入退出消息shm->flag = 1; // 标记有新消息sem_op(semid, 0, 1); // 执行V操作,允许进程1读取消息}// 分离共享内存,不再与当前进程地址空间关联if (shmdt(shm) == -1) {perror("shmdt"); // 输出错误信息}printf("\n聊天结束\n"); // 输出结束信息exit(EXIT_SUCCESS); // 正常退出程序
}int main() {key_t semkey = SEM_KEY; // 信号量集键值key_t shmkey = SHM_KEY; // 共享内存键值// 获取已存在的信号量集(由chat1创建),权限0666semid = semget(semkey, 2, 0666);if (semid == -1) { // 检查获取是否成功perror("semget"); // 输出错误信息printf("请先启动chat1\n"); // 提示用户先启动chat1exit(EXIT_FAILURE); // 异常退出}// 获取已存在的共享内存(由chat1创建),权限0666shmid = shmget(shmkey, sizeof(SharedData), 0666);if (shmid == -1) { // 检查获取是否成功perror("shmget"); // 输出错误信息printf("请先启动chat1\n"); // 提示用户先启动chat1exit(EXIT_FAILURE); // 异常退出}// 将共享内存连接到当前进程的地址空间shm = (SharedData *)shmat(shmid, NULL, 0);if (shm == (void *)-1) { // 检查连接是否成功perror("shmat"); // 输出错误信息exit(EXIT_FAILURE); // 异常退出}// 设置信号处理函数,当接收到SIGINT信号(Ctrl+C)时调用cleanupsignal(SIGINT, cleanup);printf("聊天程序启动 (进程2),输入exit退出\n"); // 输出启动信息while (1) { // 主循环,持续运行直到退出// 执行P操作,等待接收进程1的消息(信号量1)sem_op(semid, 1, -1);// 检查对方是否发送了退出消息if (strcmp(shm->message, "exit") == 0) {printf("对方已退出\n"); // 提示用户对方已退出cleanup(0); // 调用清理函数退出}// 显示对方发送的消息printf("对方: %s\n", shm->message);memset(shm->message, 0, SHM_SIZE); // 清空消息缓冲区shm->flag = 0; // 标记消息已处理// 执行V操作,允许进程1继续发送消息(信号量1)sem_op(semid, 1, 1);// 执行P操作,获取发送权限(信号量1)sem_op(semid, 1, -1);// 提示用户输入消息printf("我: ");fflush(stdout); // 刷新标准输出,确保提示信息显示// 从标准输入读取用户输入的消息if (fgets(shm->message, SHM_SIZE, stdin) == NULL) {perror("fgets"); // 输出错误信息break; // 退出循环}// 移除输入消息中的换行符shm->message[strcspn(shm->message, "\n")] = '\0';// 检查用户是否输入"exit",如果是则退出程序if (strcmp(shm->message, "exit") == 0) {shm->flag = 1; // 标记有新消息sem_op(semid, 0, 1); // 执行V操作,允许进程1读取消息cleanup(0); // 调用清理函数退出}shm->flag = 1; // 标记共享内存中有新消息// 执行V操作,允许进程1读取消息(信号量0)sem_op(semid, 0, 1);}cleanup(0); // 程序结束时调用清理函数return 0; // 主函数返回
}
程序说明
这个聊天程序使用了共享内存和信号量集实现两个进程间的双向通信:
共享内存:
- 使用一个共享内存区域存储 (
SHM_KEY
) 存储聊天消息 - 共享内存中包含消息内容和一个标志位,用于标记消息状态
- 使用一个共享内存区域存储 (
信号量集:
- 使用两个信号量控制进程间的同步
- 信号量 0:控制进程 1 的发送权限
- 信号量 1:控制进程 2 的发送权限
- 通过 P/V 操作实现进程间的交替发送
工作流程:
- 先启动
chat1
,它会创建共享内存和信号量集 - 再启动
chat2
,它会连接到已创建的共享内存和信号量集 - 进程交替发送消息,通过信号量确保互斥访问共享内存
- 任何输入 "exit" 可以退出聊天程序
- 先启动
编译和运行
编译两个文件:
gcc chat1.c -o chat1
gcc chat2.c -o chat2
运行时先启动 chat1,再在另一个终端启动 chat2:
./chat1
./chat2
这样两个进程就可以互相发送消息进行聊天了。