java 类似 linux msgqueue_Linux 高级编程 - 消息队列 MsgQueue

本文深入探讨了消息队列MsgQueue的概念和技术细节,包括其在Windows和Linux平台的应用、消息队列的工作原理、API操作及内核实现机制。通过实例演示了进程间通信的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

2f986b229dfefc723c37e13507655201.png

这是 cdeveloper 的第 32 篇原创

消息队列 Msg Queue

Hello,大家好,今天跟大家分享消息队列相关的技术。如果你在 Windows 上开发过应用程序,想必你应该听过消息队列这个概念。

在 Windows 中每个程序都有一个消息队列,整个程序在一个 loop 中等待从消息队列中取消息并执行,所以称 Windows 上的程序为事件驱动型。

同样在 Linux 开发中也有消息队列这个概念,不过 Linux 中的消息队列是用来进行 IPC 的,本质上跟共享内存一样也是内存维护的一片内存区域,这篇文章就带你学习消息队列的相关操作和内核机制。

系统中的消息队列

可以使用 ipcs -q 查看系统当前使用的消息队列(以下简称 MQ):

8cf8a58cfdb5e9ea14ea8c23dae8bb9c.png

我的系统当前没有任何 MQ,后面我们会用程序创建一个 MQ,然后再用这个命令查看。

消息队列的原理

MQ 传递的是消息,消息即是我们需要在进程间传递的数据。MQ 采用链表来实现消息队列,该链表是由系统内核维护。

系统中可能有很多的 MQ,每个 MQ 用消息队列描述符(消息队列 ID:qid)来区分,qid 是唯一的,用来区分不同的 MQ。

在进行进程间通信时,一个进程将消息加到 MQ 尾端,另一个进程从消息队列中取消息(不一定以先进先出来取消息,也可以按照消息类型字段取消息),这样就实现了进程间的通信。如下 MQ 的模型:

721a70c848d6b66bad78910fbca37249.png

进程 A 向内核维护的消息队列中发消息,进程 B 从消息队列中取消息,从而实现了 A 和 B 的进程间通信。了解了原理,来看看如何使用 MQ。

使用消息队列

MQ 的 API 操作与共享内存几乎是相同的,分为下面 4 个步骤:

创建和访问 MQ

发送消息

接受消息

删除 MQ

下面来学习这些函数如何使用。

1. 创建:msgget

使用 msgget 可以创建一个消息队列,需要指定创建的 key 和标志,key 与返回的 qid 有关系。

4b190294666dbe80936c8fc825f1886d.png

2. 发送:msgsnd

使用 msgsnd 来发送一个消息,必须要有写消息队列的权限。

e5821350e3bd1a6471520633e8d8c851.png

3. 发送:msgrcv

使用 msgrcv 来从 msgqid 标识的 MQ 中读取一个消息放到 msgp 指定的内存中,必须要有读消息队列的权限。

f56fddba800350971a66fe917c121645.png

注意:参数 msgsz 指定由 msgp 参数指向的结构的成员 mtext 的最大大小(以字节为单位),msgtyp 也有 3 种方式:

msgtyp = 0:读取队列中的第一条消息

msgtyp > 0:读取队列中类型为 msgtyp 的第一条消息,除非在 msgflg 中指定了 MSG_EXCEPT,否则将读取类型不等于 msgtype 的队列中的第一条消息。

msgtyp < 0:读取队列中最小类型小于或等于 msgtype 绝对值的第一条消息

4. 控制:msgctl

f8e82dc20030a6ce8c2bc62b4f3f8cd5.png

例子:使用消息队列

我们也写两个程序来用 MQ 来进程间通信,一个进程 A 写入字符串数据到消息队列,另一个进程 B 从队列中取出数据。

这里省略了头文件了,不然图片太长了,头文件可以使用 man 手册来查看,注意学习方法。

write_msg.c

c9c30f653989ead93e7dbc7b43b08952.png

read_msg.c

这是读取进程:

a62a5c5d493ece4027661300a68b3f9f.png

编译:

b7207c68896d1736e33edd63fbee42d3.png

运行:

2c1ec7515cf2f4549d4c099f5185d9eb.png

消息队列创建成功,并且消息也发送了,我们再次用 ipcs -q 看看存不存在这个队列:

bcb630255bb0223643d665049c434931.png

成功输出了我们创建的消息队列,并且大小等于 255 B,里面有一条我们刚才发送的消息,来读取这个消息:

d50e77c301630cb0e91c848616ac1712.png

消息接收成功,并且也删除了消息队列,看看有没有删除成功:

3bf5a611e9b7627680bfdc16d8179b8a.png

删除成功!一样,来看看消息队列在内核中的实现。

MQ 的内核实现

1. struct msg_queue

消息队列在内核中用下面的数据结构表示:

d4ed01db1f503a13f61cdfc051cc8d4c.png

可以看到消息队列实际上就是一个链表。

2. 分配获取消息队列

首先上层应用通过调用 msgget 来进行系统调用,然后陷入内核:

1b6ac8b06864b33d71c913f6dda5de0d.png

消息队列的内核实现机制和共享内存几乎相同:在内核开辟一片内存空间存放消息队列,不同的进程使用这个消息队列(内存空间)来通信,与共享内存使用相同的一组回调函数 ipc_ops。

msgget 的基本的调用过程如下:

d450271d4e0ad61f0bc0bed8ac314287.png

比较重要的是最后一步回调 newque 函数,这个函数在内核中分配了一个消息队列 msg:

041e73146f6d510d0d68b7f150fe3a93.png

3. 发送消息

上层的 msgsnd 系统调用最后会调用到内核的 do_msgsnd 函数,过程如下:

e49fd7266e3d0847b41a6f8a4f0a100c.png

具体的过程这里就不分析了,主要就是在链表中添加一项。

4. 接受消息

上层的 msgrcv 系统调用最后会调用到内核的 do_msgrcv 函数,过程如下:

b35c52c7cfbb51f48bf340f6d7e64738.png

同样接收消息会从链表中删除一项,具体的过程可以根据这个路线仔细分析。

结语

本次我们学习了跟共享内存类似的 IPC 方式:消息队列 Msg Queue,它的使用 API 和内核实现原理跟共享内存是差不多,建议你对照两者进行学习,以此来更好的理解,今天的分享到此为止了,下次再扯吧。

一个坚持原创写作的技术人

给个赞呗

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值