5.消息队列
1.相关数据类型
(1)mqd_t
该数据类型定义在mqueue.h中,是用来记录消息队列描述符的。
typedef int mqd_t;
实质上是int类型的别名。
(2)struct mq_attr
struct mq_attr {
long mq_flags;
//mq_flags 标记,对于mq_open,忽略它,因为标记是通过前者的调用传递的
long mq_maxmsg;
//mq_maxmgs 队列可以容纳的消息的最大数量
long mq_msgsize;
//mq_msgsize 单条消息的最大允许大小,以字节为单位
long mq_curmsgs;
//mq_curmsgs 当前队列中的消息数量,对于mq_open,忽略它
};
(3)struct timespec
@brief 时间结构体,提供了纳秒级的UNIX时间戳
tv_sec 秒
tv_nsec 纳秒
struct timespec {
time_t tv_sec;
long tv_nsec;
};
2. 相关系统调用**
(1)mq_open()
创建或打开一个已存在的POSIX消息队列,消息队列是通过名称唯一标识的
#include <fcntl.h>
include <sys/stat.h>
#include <mqueue.h>
mqd_t mq_open(const char *name, int oflag, mode_t mode, struct mq_attr *attr)
mqd_t mq_open(const char *name, int oflag);
/*name 消息队列的名称 * 命名规则:必须是以正斜杠/开头,以\0结尾的字符串,中间可以包含若干字符,但不能有正斜杠
oflag 指定消息队列的控制权限,必须也只能包含以下三者之一
- O_RDONLY 打开的消息队列只用于接收消息
- O_WRONLY 打开的消息队列只用于发送消息
- O_RDWR 打开的消息队列可以用于收发消息
可以与以下选项中的0至多个或操作之后作为oflag
O_CLOEXEC 设置close-on-exec标记,这个标记表示执行exec时关闭文件描述符
O_CREAT 当文件描述符不存在时创建它,如果指定了这一标记,需要额外提供mode 和attr参数
O_EXCL 创建一个当前进程独占的消息队列,要同时指定O_CREAT,要求创建的消息 队列不存在,否则将会失败,并提示错误EEXIST
O_NONBLOCK 以非阻塞模式打开消息队列,如果设置了这个选项,在默认情况下收发 消息发生阻塞时,会转而失败,并提示错误EAGAIN
mode 每个消息队列在mqueue文件系统对应一个文件,mode是用来指定消息 队列对应文件的权限的
attr 属性信息,如果为NULL,则队列以默认属性创建
return mqd_t
成功则返回消息队列描述符
失败则返回(mqd_t)-1,同时设置 errno以指明错误原因*/
(2)mq_timedsend()
将msg_ptr指向的消息追加到消息队列描述符mqdes指向的消息队列的尾部。 如果消息队列已满,默认情况下,调用阻塞直至有充足的空间允许新的消息入队,或者达到abs_timeout指定的等待时间节点,或者调用被信号处理函数打断。
需要注意的是, 正如上文提到的,如果在mq_open时指定了O_NONBLOCK标记,则转而失败,并返回错误 EAGAIN。
int mq_timedsend(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned int msg_prio, const struct timespec *abs_timeout);
/*mqdes 消息队列描述符
msg_ptr 指向消息的指针
msg_len msg_ptr指向的消息长度,不能超过队列的mq_msgsize属性指定的队列最大容量,长度为0的消息是被允许的
msg_prio 一个非负整数,指定了消息的优先级,消息队列中的数据是按照优先级降序排列的,如果新旧消息的优先级相同,则新的消息排在后面。
abs_timeout 指向struct timespec类型的对象,指定了阻塞等待的最晚时间。如果消息队列已满,且abs_timeout指定的时间节点已过期,则调用立即返回。
return int 成功返回0,失败返回-1,同时设置errno以指明错误原因。*/
(3)mq_timedreceive()
从消息队列中取走最早入队且权限最高的消息,将其放入msg_ptr指向的缓存中。如果消息队列为空,默认情况下调用阻塞,此时的行为与mq_timedsend同理。
#include <time.h>
#include <mqueue.h>
ssize_t mq_timedreceive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned int *msg_prio, const struct timespec *abs_timeout);
- mqdes 消息队列描述符
-msg_ptr 接收消息的缓存
-msg_len msg_ptr指向的缓存区的大小,必须大于等于mq_msgsize属性指 定的队列单条消息最大字节数 * @param msg_prio 如果不为NULL,则用于接收接收到的消息的优先级
-abs_timeout 阻塞时等待的最晚时间节点,同mq_timedsend * @return ssize_t 成功则返回接收到的消息的字节数,失败返回-1,并设置errno 指明错误原因
(4)mq_unlink()
清除name对应的消息队列,mqueue文件系统中的对应文件被立即清除。消息队列本身的清除必须等待所有指向该消息队列的描述符全部关闭之后才会发生。
#include <mqueue.h>
name 消息队列名称
return int 成功返回0,失败返回-1,并设置errno指明错误原因
int mq_unlink(const char *name);
(5)clock_gettime()
获取以struct timespec形式表示的clockid指定的时钟
#include <time.h>
clock_gettime(clockid_t clockid, struct timespec *tp);
clockid 特定时钟的标识符,常用的是CLOCK_REALTIME,表示当前真实时 间的时钟 *
tp 用于接收时间信息的缓存
return int 成功返回0,失败返回-1,同时设置errno以指明错误原因 */ int
3.非父子进程通信案例
我们创建两个进程:生产者和消费者,前者从控制台接收数据并写入消息队列,后者从消息队列接收数据并打印到控制台。
1.创建producer.c
#include <time.h>
#include <mqueue.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main()
{
// 定义消息队列名称
char *mq_name = "/p_c_mq";
// 设置消息队列的属性
struct mq_attr attr;
attr.mq_flags = 0; // 无特殊标志
attr.mq_maxmsg = 10; // 消息队列最多可容纳10条消息
attr.mq_msgsize = 100; // 每条消息最大100字节
attr.mq_curmsgs = 0; // 当前消息队列中的消息数量为0
// 创建或打开消息队列
mqd_t mqdes = mq_open(mq_name, O_CREAT | O_WRONLY, 0666, &attr);
// 如果打开失败,打印错误信息并退出
if (mqdes == (mqd_t)-1)
{
perror("mq_open");
return -1;
}
// 定义缓冲区用于存储待发送的数据
char writeBuf[100];
struct timespec time_info;
//struct timespec {
//time_t tv_sec; // 秒,time_t 通常是 long 类型
//long tv_nsec; // 纳秒(0 到 999,999,999)
//};
while (1)
{
// 清空缓冲区
memset(writeBuf, 0, 100);
//ssize_t read(int fd, void* buf, size_t count);
// 从标准输入读取数据
ssize_t read_count = read(0, writeBuf, 100);
if (read_count == -1)
{
perror("read");
continue;
}
else if (read_count == 0)
{
// 如果没有读取到数据,退出循环
}
// 获取当前时间并设置发送的最大等待时间为5秒后
clock_gettime(CLOCK_REALTIME, &time_info);
time_info.tv_sec += 5; // 将时间设置为当前时间加上5秒
// 如果读取到EOF,发送EOF信号给消费者并退出
//如果 `read_count` 为 `0`,表示到达文件末尾(EOF)。
//通常发生在用户按下 `Ctrl+D` 时
if (read_count == 0)
{
printf("Received EOF, exit...\n");
char eof = EOF;
// 尝试将EOF消息发送到消息队列
// int mq_timedsend(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned int msg_prio, const struct timespec *abs_timeout);
//mqdes:消息队列描述符
//msg_ptr:指向消息的指针
//msg_len:msg_ptr指向的消息长度,不能超过队列的mq_msgsize属性指定的队列最大容量,长度为0的消息是被允许的
//msg_prio:一个非负整数,指定了消息的优先级,消息队列中的数据是按照优先级降序排列的,如果新旧消息的优先级相同,则新的消息排在后面。
//abs_timeout:指向struct timespec类型的对象,指定了阻塞等待的最晚时间。如果消息队列已满,且abs_timeout指定的时间节点已过期,则调用立即返回
if (mq_timedsend(mqdes, &eof, 1, 0, &time_info) == -1)
{
perror("mq_timedsend");
}
break;
}
// 如果没有读取到EOF,则发送接收到的数据
if (mq_timedsend(mqdes, writeBuf, strlen(writeBuf), 0, &time_info) == -1)
{
perror("mq_timedsend");
}
printf("从命令行接收到数据,已发送至消费者端\n");
}
// 关闭消息队列描述符
close(mqdes);
// 注意:mq_unlink 只应在消费者中调用一次来删除消息队列
return 0;
}
2.创建consumer.c
#include <time.h>
#include <mqueue.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main()
{
char *mq_name = "/p_c_mq";
struct mq_attr attr;
// 设置消息队列的属性
attr.mq_flags = 0;
attr.mq_maxmsg = 10;
attr.mq_msgsize = 100;
attr.mq_curmsgs = 0;
// 创建或打开消息队列
mqd_t mqdes = mq_open(mq_name, O_CREAT | O_RDONLY, 0666, &attr);
if (mqdes == -1)
{
perror("mq_open");
}
char readBuf[100];
struct timespec time_info;
while (1)
{
//`readBuf` 的100个字节设置为 `0`
memset(readBuf, 0, 100);
// 获取当前时间,并使得消费者等待读取生产者的数据直到一天后
clock_gettime(CLOCK_REALTIME, &time_info);
time_info.tv_sec += 86400; // 延迟 86400 秒(1天)
// 尝试接收消息(最多等待一天)
if (mq_timedreceive(mqdes, readBuf, 100, NULL, &time_info) == -1)
{
perror("mq_timedreceive");
}
// 如果收到EOF,退出
if (readBuf[0] == EOF)
{
printf("接收到生产者的终止信号,准备退出...\n");
break;
}
// 打印收到的数据
printf("接收到来自于生产者的数据\n%s", readBuf);
}
// 关闭消息队列描述符
close(mqdes);
// 删除消息队列
mq_unlink(mq_name);
}
producer: producer.c
$(CC) -o $@ $^
cosumer: consumer.c
$(CC) -o $@ $^
p_c_mq: producer consumer
pc_mq_clean:
-rm ./producer ./consumer
5)消息队列
在mqueue文件系统的表示 我们启动producer和consumer,然后查看/dev/mqueue目录,如下。 终止消费和生产者后,上述文件被清除。
6)消息队列通信方向的讨论
我们可以通过设置POSIX消息队列的模式为O_RDWR,使它可以用于收发数据,从技 术上讲,单条消息队列可以用于双向通信,但是这会导致消息混乱,无法确定队列中的数是本进程写入的还是读取的,因此,不会这么做,通常单条消息队列只用于单向通信。 为了实现全双工通信,我们可以使用两条消息队列,分别负责两个方向的通信。类似于管道。
Note
有名管道(Named Pipe)和消息队列(Message Queue)都是进程间通信(IPC)的机制,但它们在实现方式、使用场景和特性上有显著区别。以下是它们的主要区别:
1. 基本概念
有名管道(Named Pipe)
-
定义:有名管道是一种文件系统对象,它允许不相关的进程进行通信。它在文件系统中有一个名字,通常位于某个目录下。
-
实现方式:有名管道基于文件系统,通过在文件系统中创建一个特殊类型的文件来实现。它支持半双工或全双工通信。
-
创建方式:使用
mkfifo系统调用或mkfifo()函数创建。 -
使用方式:通过文件描述符操作(如
open、read、write)进行数据传输。
消息队列(Message Queue) -
定义:消息队列是一种高级的进程间通信机制,允许进程将消息发送到队列中,其他进程可以从队列中读取消息。
-
实现方式:消息队列由操作系统内核管理,不依赖于文件系统。它支持消息的存储和转发,消息可以具有优先级
-
创建方式:使用
msgget系统调用创建(System V 消息队列)或mq_open(POSIX 消息队列)。 -
使用方式:通过消息队列操作(如
msgsnd、msgrcv或mq_send、mq_receive)进行数据传输。
2. 使用场景
有名管道 -
场景:适用于需要通过文件系统进行通信的场景,例如不同用户的进程之间通信,或者需要通过文件路径访问的场景。
- 特点:
- 支持半双工或全双工通信。
- 数据传输基于流,没有消息边界。
- 可以通过文件描述符进行操作,与文件操作类似。
消息队列
- 场景:适用于需要高效、可靠的消息传递的场景,例如生产者-消费者模型,或者需要处理不同类型消息的场景。
- 特点:
- 支持消息的存储和转发,消息具有明确的边界。
- 消息可以带有优先级,高优先级的消息可以先被读取。
- 不依赖于文件系统,性能通常更高。
3. 性能和效率
有名管道 - 性能:由于基于文件系统,性能可能受到文件系统操作的影响。
- 效率:数据传输基于流,没有消息边界,因此可能需要额外的机制来分隔消息。
消息队列
- 性能:由内核直接管理,性能通常比有名管道更高。
- 效率:消息具有明确的边界,支持消息的存储和转发,适合处理不同类型的消息。
4. 消息边界和格式
有名管道 - 消息边界:没有明确的消息边界,数据以字节流的形式传输。
- 格式:需要应用程序自行处理消息的分隔和格式化。
消息队列
- 消息边界:消息具有明确的边界,每条消息都有固定的格式。
- 格式:消息通常包含类型和数据两部分,操作系统会管理消息的边界。
5. 依赖性
有名管道
- 依赖文件系统:需要在文件系统中创建一个特殊文件,因此依赖于文件系统的可用性。
- 路径访问:通信双方需要通过文件路径访问管道。
消息队列
- 不依赖文件系统:完全由内核管理,不依赖于文件系统。
- 标识符访问:通信双方通过消息队列的标识符(如队列名)进行访问。
1004

被折叠的 条评论
为什么被折叠?



