Files
2026-06-25 00:09:09 +08:00

5.9 KiB
Raw Permalink Blame History

实验七 进程通信——消息方式

一、实验内容

理解 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字节正好填满消息正文
  • 消息类型都是1P3会按顺序接收

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 实验体会

  1. 消息队列本质:内核维护的消息链表,支持按类型过滤

  2. msgtype 妙用:通过不同的 msgtype 实现消息分类和路由

  3. 大消息处理:拆分为多个小消息发送,接收端按序号合并

  4. 进程通信模式

    • P1/P2 发消息:生产数据
    • P3/P4 收消息:消费数据
    • 通过 msgtype 实现多生产者多消费者
  5. 消息队列 vs 管道

    • 管道:无类型,数据流
    • 消息队列:有类型,可随机访问
    • 消息队列更适合多进程通信