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结构体大小,权限0666
shmid = 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)时调用cleanup
signal(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创建),权限0666
semid = semget(semkey, 2, 0666);
if (semid == -1) { // 检查获取是否成功
perror("semget"); // 输出错误信息
printf("请先启动chat1\n"); // 提示用户先启动chat1
exit(EXIT_FAILURE); // 异常退出
}
// 获取已存在的共享内存(由chat1创建),权限0666
shmid = shmget(shmkey, sizeof(SharedData), 0666);
if (shmid == -1) { // 检查获取是否成功
perror("shmget"); // 输出错误信息
printf("请先启动chat1\n"); // 提示用户先启动chat1
exit(EXIT_FAILURE); // 异常退出
}
// 将共享内存连接到当前进程的地址空间
shm = (SharedData *)shmat(shmid, NULL, 0);
if (shm == (void *)-1) { // 检查连接是否成功
perror("shmat"); // 输出错误信息
exit(EXIT_FAILURE); // 异常退出
}
// 设置信号处理函数,当接收到SIGINT信号(Ctrl+C)时调用cleanup
signal(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
这样两个进程就可以互相发送消息进行聊天了。