# 实验七 进程通信——消息方式 ## 一、实验内容 理解 Linux 消息队列通信机制,掌握 msgget/msgsnd/msrcv/msgctl。 创建消息队列,P1/P2 发消息,P3 收 P1、P4 收 P2,大消息拆分传输。 ## 二、实验设计 ### 2.1 消息队列结构 ``` +------------------+ | 消息队列 (msgid) | +------------------+ | type=1: [数据块1] | | type=1: [数据块2] | | type=2: [短消息] | | type=2: [短消息] | +------------------+ ``` ### 2.2 数据结构 ```cpp struct msgbuf { long mtype; // 消息类型(>0) char mtext[1024]; // 消息正文 }; ``` **要点:** mtype 用于消息过滤接收 ### 2.3 函数设计 | 函数 | 功能 | |------|------| | `msgget()` | 创建/获取消息队列 | | `msgsnd()` | 发送消息 | | `msgrcv()` | 接收消息(按类型) | | `msgctl()` | 控制消息队列(删除等) | ### 2.4 调用关系 ``` main() ├── msgget() 创建消息队列 ├── fork() → P1 (发64x64矩阵,拆4次发送,type=1) ├── fork() → P2 (发短消息,type=2) ├── fork() → P3 (收P1消息,合并为矩阵,type=1) ├── fork() → P4 (收P2消息,type=2) └── wait() 等待子进程,msgctl删除队列 ``` ## 三、编码实现 ### 3.1 消息队列创建 ```cpp key_t key = ftok(IPC_PRIVATE, 1); int msgid = msgget(key, IPC_CREAT | 0666); ``` **要点:** - `IPC_PRIVATE` 创建私有消息队列 - `IPC_CREAT` 不存在则创建 ### 3.2 P1: 大矩阵拆分发送 ```cpp char a[64][64]; // 初始化矩阵 aij = 'a'/'b'/'c'/'d' 根据位置 for (int i = 0; i < 64; i++) for (int j = 0; j < 64; j++) a[i][j] = i < 32 ? (j < 32 ? 'a' : 'b') : (j < 32 ? 'c' : 'd'); struct msgbuf mgb; mgb.mtype = 1; int k = 0; // 拆分为4个32x32块发送 for (int i = 0; i < 32; i++) for (int j = 0; j < 32; j++) mgb.mtext[k++] = a[i][j]; msgsnd(msgid, &mgb, sizeof(mgb), 0); k = 0; for (int i = 0; i < 32; i++) for (int j = 32; j < 64; j++) mgb.mtext[k++] = a[i][j]; msgsnd(msgid, &mgb, sizeof(mgb), 0); k = 0; for (int i = 32; i < 64; i++) for (int j = 0; j < 32; j++) mgb.mtext[k++] = a[i][j]; msgsnd(msgid, &mgb, sizeof(mgb), 0); k = 0; for (int i = 32; i < 64; i++) for (int j = 32; j < 64; j++) mgb.mtext[k++] = a[i][j]; msgsnd(msgid, &mgb, sizeof(mgb), 0); ``` **要点:** - 64x64矩阵拆分为4个32x32块 - 每块约1024字节,正好填满消息正文 - 消息类型都是1,P3会按顺序接收 ### 3.3 P2: 短消息发送 ```cpp struct msgbuf mgb; mgb.mtype = 2; strcpy(mgb.mtext, "ijkl"); msgsnd(msgid, &mgb, sizeof(mgb), 0); strcpy(mgb.mtext, "mnop"); msgsnd(msgid, &mgb, sizeof(mgb), 0); ``` **要点:** 短消息类型为2,与P1的大消息分开 ### 3.4 P3: 大矩阵合并接收 ```cpp char a[64][64]; struct msgbuf mgb; // 按顺序接收4个消息块 msgrcv(msgid, &mgb, sizeof(mgb), 1, 0); int k = 0; for (int i = 0; i < 32; i++) for (int j = 0; j < 32; j++) a[i][j] = mgb.mtext[k++]; msgrcv(msgid, &mgb, sizeof(mgb), 1, 0); k = 0; for (int i = 0; i < 32; i++) for (int j = 32; j < 64; j++) a[i][j] = mgb.mtext[k++]; // ... 接收剩余两块 // 打印完整矩阵(验证拆分传输成功) for (int i = 0; i < 64; i++) { for (int j = 0; j < 64; j++) printf("%c", a[i][j]); printf("\n"); } ``` ### 3.5 P4: 短消息接收 ```cpp struct msgbuf mgb; msgrcv(msgid, &mgb, sizeof(mgb), 2, 0); printf("p4 received %s\n", mgb.mtext); msgrcv(msgid, &mgb, sizeof(mgb), 2, 0); printf("p4 received %s\n", mgb.mtext); ``` **要点:** `msgrcv(msgid, &mgb, size, type, flag)` 按 type 过滤消息 ### 3.6 编译与运行 ```bash g++ exp07_source.cpp -o msgqueue ./msgqueue ``` ## 四、实验结果 ### 4.0 测试命令 ```bash # 编译 g++ exp07_source.cpp -o msgqueue # 运行 ./msgqueue # 查看消息队列(在另一终端) ipcs -q # 清理消息队列(如果进程异常退出) ipcrm -q $(ipcs -q | grep -E '^[0-9]+' | awk '{print $2}') ``` ### 4.1 P3 接收并合并矩阵 ``` abcd...abcd abcd...abcd ... abcd...abcd (64行,每行64个字符) ``` **说明:** 左上32x32为'a',右上32x32为'b',左下32x32为'c',右下32x32为'd' ### 4.2 P4 接收短消息 ``` p4 received ijkl p4 received mnop ``` **说明:** 消息按类型正确接收 ### 4.3 消息队列状态 ``` 创建后: msgid 有4个消息待取 P3接收后: msgid 有2个消息(type=2) P4接收后: msgid 无消息 最终: msgctl(msgid, IPC_RMID, NULL) 删除队列 ``` ## 五、实验结果思考与体会 ### 5.1 消息队列特点 | 特性 | 说明 | |------|------| | 消息类型 | mtype 用于过滤,支持多进程按需接收 | | 容量限制 | 系统限制消息总大小 | | 数据结构 | 链表实现,先进先出 | | 同步阻塞 | 队列空时msgrcv阻塞 | ### 5.2 大消息传输策略 | 方案 | 适用场景 | 注意点 | |------|----------|--------| | 固定拆分 | 已知大小 | 需要接收端知道分块数 | | 长度前缀 | 变长消息 | 第一个消息发长度 | | 序号标记 | 可靠传输 | 每块带序号便于重组 | ### 5.3 思考问题解答 **问题1:传输杨辉三角矩阵** 杨辉三角每行长度不同,可以用长度前缀方法: - 第一个消息发矩阵行数 - 后续消息逐行发送,每行带行号 **问题2:测试系统消息最大长度** ```bash # 查看系统限制 ipcs -l # 编写测试程序逐步增大消息 for size in 1024 4096 8192 16384; do msgsnd(msgid, &mgb, size, 0) done ``` ### 5.4 实验体会 1. **消息队列本质**:内核维护的消息链表,支持按类型过滤 2. **msgtype 妙用**:通过不同的 msgtype 实现消息分类和路由 3. **大消息处理**:拆分为多个小消息发送,接收端按序号合并 4. **进程通信模式**: - P1/P2 发消息:生产数据 - P3/P4 收消息:消费数据 - 通过 msgtype 实现多生产者多消费者 5. **消息队列 vs 管道**: - 管道:无类型,数据流 - 消息队列:有类型,可随机访问 - 消息队列更适合多进程通信