5.9 KiB
5.9 KiB
实验七 进程通信——消息方式
一、实验内容
理解 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 数据结构
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 消息队列创建
key_t key = ftok(IPC_PRIVATE, 1);
int msgid = msgget(key, IPC_CREAT | 0666);
要点:
IPC_PRIVATE创建私有消息队列IPC_CREAT不存在则创建
3.2 P1: 大矩阵拆分发送
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: 短消息发送
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: 大矩阵合并接收
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: 短消息接收
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 编译与运行
g++ exp07_source.cpp -o msgqueue
./msgqueue
四、实验结果
4.0 测试命令
# 编译
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:测试系统消息最大长度
# 查看系统限制
ipcs -l
# 编写测试程序逐步增大消息
for size in 1024 4096 8192 16384; do
msgsnd(msgid, &mgb, size, 0)
done
5.4 实验体会
-
消息队列本质:内核维护的消息链表,支持按类型过滤
-
msgtype 妙用:通过不同的 msgtype 实现消息分类和路由
-
大消息处理:拆分为多个小消息发送,接收端按序号合并
-
进程通信模式:
- P1/P2 发消息:生产数据
- P3/P4 收消息:消费数据
- 通过 msgtype 实现多生产者多消费者
-
消息队列 vs 管道:
- 管道:无类型,数据流
- 消息队列:有类型,可随机访问
- 消息队列更适合多进程通信