Linux_POSIX消息队列-异步通知
前言
与传统的管道和FIFO相比,消息队列提供了更多的灵活性,因为它们允许发送和接收进程以不同的速率运行,并且消息可以被存储直到被接收。然而,在某些情况下,我们可能希望当一个消息到达队列时,能够立即得到通知,而不是通过轮询或阻塞等待。这就是异步通知的用处所在。POSIX消息队列支持异步通知,允许我们注册一个信号处理程序,当特定的事件(如消息到达队列)发生时,操作系统会发送一个信号给进程。
异步通知的设置
在c语言中,我们可以使用mq_notify
函数来设置异步通知。这个函数允许我们为消息队列注册一个通知请求。
mq_notify
函数
函数原型如下:
#include <mqueue.h>
int mq_notify(mqd_t mqdes, const struct sigevent *notification);
函数参数介绍
-
mqdes
:是一个打开的消息队列描述符。 -
notification
:是一个指向sigevent
结构的指针,该结构描述了当消息到达时应如何通知进程。
sigevent
结构体结构如下:
struct sigevent {
int sigev_notify; /* Notification method */
int sigev_signo; /* Notification signal */
union sigval sigev_value;
/* Data passed with notification */
void (*sigev_notify_function) (union sigval);
/* Function used for thread
notification (SIGEV_THREAD) */
void *sigev_notify_attributes;
/* Attributes for notification thread
(SIGEV_THREAD) */
pid_t sigev_notify_thread_id;
/* ID of thread to signal
(SIGEV_THREAD_ID); Linux-specific */
};
sigev_notify
取值:
SIGEV_NONE:这个值表示不需要任何通知。当sigev_notify被设置为这个值时,即使事件发生了,也不会有任何通知发送到进程。
SIGEV_SIGNAL:事件发生时,将sigev_signo指定的信号发送给指定的进程;
SIGEV_THREAD:事件发生时,内核会(在此进程内)以sigev_notify_attributes为线程属性创建一个线程,并让其执行sigev_notify_function,并以sigev_value为其参数
sigev_signo:
在sigev_notify=SIGEV_SIGNAL时使用,指定信号类别, 例如SIGUSR1、SIGUSR2 等
sigev_value:
sigev_notify=SIGEV_SIGEV_THREAD时使用,作为sigev_notify_function的参数, 当发送信号时,这个值会传递给信号处理函数。
void *sigev_notify_attributes;
当使用SIGEV_THREAD
通知方式时,这个字段指向一个属性对象,用于设置新创建线程的属性,如线程的继承性、调度策略等。通常,这会是一个通过pthread_attr_init
和相关函数初始化的pthread_attr_t
结构体的指针。
返回值:
当mq_notify成功时会返回0,如果错误,将放回-1,并且将errno设置为表示错误的值。
On success mq_notify() returns 0; on error, -1 is returned, with errno set to indicate the error.
代码示例
以下以一个简单的示例,展示如何使用POSIX消息队列的异步通知的功能:
#include <stdio.h>
#include <mqueue.h>
#include <pthread.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#define MYQUEUE "/test_queue"
#define MESAGE "queue test message"
void *sender_thread(void *arg)
{
mqd_t mqdes = *(mqd_t *)arg;
char message[] = MESAGE;
printf("sender thread message=%s, mqd=%d\n", message, mqdes);
int ret = mq_send(mqdes,message,strlen(message) + 1,0); // 发送消息,优先级为0
if(-1 == ret)
{
if(errno == EAGAIN)
{
printf("The queue was full\n"); // 消息队列已满,继续发送
}
else{
perror("mq_send");
return NULL;
}
}
printf("send thread end\n");
return NULL;
}
void notify_thread (union sigval sval)
{
mqd_t mqdes = *((mqd_t *)sval.sival_ptr);
char buffer[256];
size_t bytes_read;
struct sigevent sev;
printf("motify is already running\n");
while(1)
{
bytes_read = mq_receive(mqdes,buffer,256,NULL);
if(bytes_read == -1)
{
if(errno == EAGAIN)
{
printf("the queue is empty\n");
break;
}
else{
perror("mq_receive");
exit(-1);
}
}
printf("receive message:%s\n",buffer);
}
sev.sigev_notify = SIGEV_THREAD;
sev.sigev_notify_function = notify_thread;
sev.sigev_notify_attributes = NULL;
sev.sigev_value.sival_ptr = &mqdes;
if(mq_notify(mqdes,&sev) == -1 )
{
perror("mq_notify");
exit(-1);
}
printf("motify is end\n");
}
int main(int argc,char *argv[]) {
pthread_t sender;
struct mq_attr attr;
attr.mq_flags = 0; // 阻塞模式
attr.mq_maxmsg = 10; // 最大消息数量
attr.mq_msgsize = 256; // 每个消息最大大小
attr.mq_curmsgs = 0; // 当前0个消息
mqd_t mqdes = mq_open(MYQUEUE,O_CREAT|O_RDWR,0666,&attr); // 创建一个消息队列
if(mqdes == -1)
{
perror("mq_open");
return -1;
}
struct sigevent sev;
sev.sigev_notify = SIGEV_THREAD;
sev.sigev_notify_function = notify_thread;
sev.sigev_notify_attributes = NULL;
sev.sigev_value.sival_ptr = &mqdes;
int ret = mq_notify(mqdes,&sev);
printf("mq_notify return:%d\n",ret);
if(ret == -1)
{
perror("mq_notify");
return -1;
}
if(-1 == pthread_create(&sender,NULL,sender_thread,&mqdes))
{
perror("sender pthread_create");
return -1;
}
pthread_join(sender,NULL);
sleep(10);
mq_close(mqdes);
mq_unlink(MYQUEUE);
return 0;
}
总结
Linux的POSIX消息队列提供了一种强大的进程间通信机制,而异步通知功能则进一步增强了其灵活性。通过使用mq_notify
函数和信号处理,我们可以实现当消息到达队列时立即得到通知的功能,而无需通过轮询或阻塞等待。这在需要高效、实时响应的场景中非常有用。