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函数和信号处理,我们可以实现当消息到达队列时立即得到通知的功能,而无需通过轮询或阻塞等待。这在需要高效、实时响应的场景中非常有用。