Posix消息队列

消息队列可以认为是一个消息链表,某个进程往一个消息队列中写入消息之前,不需要另外某个进程在该队列上等待消息的达到,这一点与管道和FIFO相反。Posix消息队列与System V消息队列的区别如下:
1. 对Posix消息队列的读总是返回最高优先级的最早消息,对System V消息队列的读则可以返回任意指定优先级的消息。
2. 当往一个空队列放置一个消息时,Posix消息队列允许产生一个信号或启动一个线程,System V消息队列则不提供类似的机制。

Posix消息队列操作函数如下:

#include    <mqueue.h>
typedef int mqd_t;
mqd_t mq_open(const char *name, int oflag, ... /* mode_t mode, struct mq_attr *attr */);
返回: 成功时为消息队列描述字,出错时为-1。   
功能: 创建一个新的消息队列或打开一个已存在的消息的队列。     

#include    <mqueue.h>
int mq_close(mqd_t mqdes);
返回: 成功时为0,出错时为-1。
功能: 关闭已打开的消息队列。

#include    <mqueue.h>
int mq_unlink(const char *name)
返回: 成功时为0,出错时为-1
功能: 从系统中删除消息队列。

#include    <mqueue.h>
int mq_getattr(mqd_t mqdes, struct mq_attr *attr);
int mq_setattr(mqd_t mqdes, const struct mq_attr *attr, struct mq_attr *attr);
均返回:成功时为0, 出错时为-1

每个消息队列有四个属性:
struct mq_attr
{
    long mq_flags;           /* message queue flag : 0, O_NONBLOCK */
    long mq_maxmsg;     /* max number of messages allowed on queue*/
    long mq_msgsize;      /* max size of a message (in bytes)*/
    long mq_curmsgs;     /* number of messages currently on queue */
};

每个消息均有一个优先级,它是一个小于MQ_PRIO_MAX的无符号整数
#define MQ_PRIO_MAX 32768

#include    <mqueue.h>
int mq_send(mqd_t mqdes, const char *ptr, size_t len, unsigned int prio);
返回:成功时为0,出错为-1
ssize_t mq_receive(mqd_t mqdes, char *ptr, size_t len, unsigned int *priop);
返回:成功时为消息中的字节数,出错为-1


消息队列的限制:
MQ_OPEN_MAX : 一个进程能够同时拥有的打开着消息队列的最大数目
MQ_PRIO_MAX : 任意消息的最大优先级值加1

#include    <mqueue.h>
int mq_notify(mqd_t mqdes, const struct sigevent *notification);
返回: 成功时为0,出错时为-1
功能: 给指定队列建立或删除异步事件通知

union sigval
{
    int sival_int;          /* Integer value */
    void *sival_ptr;     /* pointer value */
};

struct sigevent
{
    int     sigev_notify;             /* SIGEV_{ NONE, ISGNAL, THREAD} */   //信号,线程
    int     sigev_signo;             /* signal number if SIGEV_SIGNAL */
    union sigval sigev_value;   /* passed to signal handler or thread */
    void    (*sigev_notify_function)(union sigval);
    pthread_attr_t   *sigev_notify_attribute;
}; 

异步信号安全函数
#include    <signal.h>
int sigwait(const sigset_t *set, int *sig);

Posxi实时信号
信号可划分为两大小组:
1. 其值在SIGRTMIN和SIGRTMAX之间(包括两者在内)的实时信号。
2. 所有其他信号:SIGALRM, SIGINT, SIGKILL等等。

void func(int signo, siginfo_t *info, void *context);

typedef struct
{
    int     si_signo;      /* same value as signo argument */
    int     si_code;      /* SI_{USER, QUEUE, TIMER, ASYNCIO, MESGQ}*/
    union sigval si_value;    /* integer or pointer value from sender */
} siginfo_t;

下面采用上面的函数,写程序进程测试。  //  编译添加gcc -lrt 选项

程序1:创建一个消息队列,其名字是作为命令行参数指定,消息队列创建成功后输出队列的属性。程序如下:

#include <stdio.h>
#include <stdlib.h>

#include <unistd.h>
#include <mqueue.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>

#define FILE_MODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)

int main(int argc, char** argv)
{
   int c;
   int flags;
   struct mq_attr attr;
   mqd_t mqd;

   flags = O_RDWR|O_CREAT;
   while(c=getopt(argc,argv,"e") !=-1 )
   {
      switch(c)
      {
         case 'e':
             flags |= O_EXCL;
             break;
      }
   }   
   
   if(optind != argc - 1)
   {
       perror("usage: mqueue [-e] <name>");
       exit(0);
   }
     
   //创建队列
   if((mqd = mq_open(argv[optind],flags,FILE_MODE,NULL)) == -1)
   {
       perror("mq_open error.\n");
       exit(1);
   }
   //读取mqd的属性
   mq_getattr(mqd, &attr);
   
   printf("max #msg=%ld,msg #size=%ld,cur #msg = %ld\n",attr.mq_maxmsg,attr.mq_msgsize,attr.mq_curmsgs);

   mq_close(mqd);
   return 0;
}

因此正确编译的方式如下:

程序编译完成后,如果直接运行程序则提示mq_open失败,提示mq_open permission denied。解决办法是:

mkdir /dev/mqueue
mount -t mqueue none /dev/mqueue

然后再运行即可看到创建的消息队列。程序结果如下所示

Posix消息队列_第1张图片

程序2:练习mq_send和mq_receive函数,调用mqsend程序向消息队列中写入消息,调用mqreceive程序从消息队列中读取消息。程序如下所示:

#include <stdio.h>
#include <stdlib.h>

#include <unistd.h>
#include <mqueue.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
#include <string.h>

int main(int argc, char** argv)
{
   mqd_t   mqd;
   void    *ptr;
   size_t  len;
   unsigned int  proi;
   
   if(argc!=4)
   {
      perror("usage: mqsend <name> <#byte> <priority>");
      exit(1);
   }   
   len = atoi(argv[2]);
   proi = atoi(argv[3]);
   
   mqd = mq_open(argv[1],O_WRONLY);
   ptr = calloc(len,sizeof(char));
   strcpy(ptr,"hello  mqueue\n");
    
   if(mq_send(mqd,ptr,len,proi) == -1)
   { 
       perror("mq_send() error.\n");
       exit(1);
   }
   return 0;
}


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

#include <mqueue.h>
#include <fcntl.h>
#include <sys/stat.h>

int main(int argc, char** argv)
{
   int c,flags;
   
   mqd_t mqd;
   ssize_t n;
   unsigned int prio;
   void *buff;

   struct mq_attr attr;
   
   flags = O_RDONLY;
   while((c = getopt(argc,argv,"n"))!= -1)
   {
      switch(c)
      {
        case 'n':
            flags |= O_NONBLOCK;
            break;
      }
   }
   
   if(optind != argc - 1)
   {
      perror("usage: mqreceive [-n] <name>\n");
      exit(1);
   }
  
   mqd = mq_open(argv[optind],flags);
   mq_getattr(mqd,&attr);
   buff = malloc(attr.mq_msgsize);
    
   if((n = mq_receive(mqd,buff,attr.mq_msgsize,&prio)) == -1)
   {
      perror("mq_recevice error.\n");
      exit(1);
   }
   printf("read %ld bytes , prio = %u\n",(long)n,prio);
   printf("read %s\n",buff);
   return 0;
}

执行结果:

[centos@localhost mqueue]$ ./mqueue2 /myq 100 13
[centos@localhost mqueue]$ ./mqueue2 /myq 120 34
[centos@localhost mqueue]$ ./mqueue2 /myq 200 45
[centos@localhost mqueue]$ ./mqueue3 /myq
read 200 bytes , prio = 45
read hello  mqueue
[centos@localhost mqueue]$ ./mqueue3 /myq
read 120 bytes , prio = 34
read hello  mqueue
[centos@localhost mqueue]$ ./mqueue3 /myq
read 100 bytes , prio = 13
read hello  mqueue

程序3:信号通知函数使用,当有一个消息放置到某个空队列中,该程序产生信号,通知进程消息队列中放入了一个新的消息。程序如下:

int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oldset);
一个进程的信号屏蔽字规定了当前阻塞而不能递送给该进程的信号集。sigprocmask()可以用来检测或改变目前的信号屏蔽字,
其操作依参数how来决定,如果参数oldset不是NULL指针,那么目前的信号屏蔽字会由此指针返回。如果set是一个非空指针,
则参数how指示如何修改当前信号屏蔽字。每个进程都有一个用来描述哪些信号递送到进程时将被阻塞的信号集,
该信号集中的所有信号在递送到进程后都将被阻塞。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

#include <mqueue.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <signal.h>
#include <string.h>

//全局变量,检测信号的产生
volatile sig_atomic_t mqflag;

static void sig_usr1(int signo)
{
    printf("myqueue send sig_usr1\n");
    mqflag=1;
    return ;
}

int main(int argc, char** argv)
{
  mqd_t    mqd;
  void     *buff;
  ssize_t  n;
  
  sigset_t zeromask,newmask,oldmask;  
  struct mq_attr  attr;
  struct sigevent sigev;
 
  if(argc != 2)
  {
     perror("usage: mqnotify <name>\n");
     exit(0);
  }  
  
  mqd = mq_open(argv[1],O_RDONLY);
  mq_getattr(mqd, &attr);
  buff = malloc(attr.mq_msgsize);
  //strcpy(buff, "hello myqueue.");
  
  sigemptyset(&zeromask);
  sigemptyset(&newmask);
  sigemptyset(&oldmask);
  
  //设置进程屏蔽字,屏蔽SIGUSR1信号
  sigaddset(&newmask,SIGUSR1);

  //信号出发时的函数,信号SIGUSR1产生时激发sig_usr1函数
  signal(SIGUSR1,sig_usr1);
  sigev.sigev_notify = SIGEV_SIGNAL;  //出发类型,0,信号,线程
  sigev.sigev_signo = SIGUSR1;  
  
  if(mq_notify(mqd, &sigev) == -1)
  {
     perror("mq_notify error\n");
     exit(1);
  }
  printf("mqflag = %d\n",mqflag);
  
  for(;;)
  {
      //修改当前进程的信号
      sigprocmask(SIG_BLOCK,&newmask,&oldmask);
      while(mqflag == 0)
          sigsuspend(&zeromask);   //挂起
      mqflag = 0;
      mq_notify(mqd,&sigev);
      
      n = mq_receive(mqd,buff,attr.mq_msgsize, NULL);
      printf("read %ld bytes\n",(long)n);
      sigprocmask(SIG_UNBLOCK,&newmask,NULL);

  }
  return 0;
}

可以使用sigwait函数代替信号处理程序的信号通知,将信号阻塞到某个函数中,仅仅等待该信号的递交。采用sigwait实现上面的程序如下:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>

#include <sys/stat.h>
#include <mqueue.h>
#include <fcntl.h>
#include <signal.h>

int main(int argc, char** argv)
{
    mqd_t  mqd;
    int    signo;
    void   *buff;
    
    ssize_t  n;
    sigset_t newmask;
    
    struct mq_attr   attr;
    struct sigevent  sigev;
   
    if(argc != 2)
    {
       perror("usage: mqnotify <name>\n");
       exit(1);  
    }
   
    mqd = mq_open(argv[1],O_RDONLY);
    mq_getattr(mqd,&attr);
    buff = malloc(attr.mq_msgsize);
  
    
    sigemptyset(&newmask);
    sigaddset(&newmask,SIGUSR1);
    sigprocmask(SIG_BLOCK,&newmask,NULL);
    
    sigev.sigev_notify = SIGEV_SIGNAL;
    sigev.sigev_signo = SIGUSR1;
   
    if(mq_notify(mqd,&sigev) == -1)
    {
       perror("mq_notify error");
       exit(1);
    }
    
    for(;;)
    {
        sigwait(&newmask,&signo);
        if(signo == SIGUSR1)
        {
           mq_notify(mqd,&sigev);
           while((n=mq_receive(mqd,buff,attr.mq_msgsize,NULL)) >= 0)
                 printf("read %ld bytes\n",(long)n);
           if(errno != EAGAIN)
           {
              perror("mq_receive error");
              exit(1);
           }
        }
    }
   
   return 0;
}

启动线程处理消息通知,程序如下:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

#include <mqueue.h>
#include <fcntl.h>
#include <signal.h>

mqd_t mqd;
struct mq_attr attr;
struct sigevent sigev;

static void notify_thread(union sigval);

int main(int argc,char** argv)
{
   if(argc != 2)
   {
       printf("usage: mqnotify <name>");
       exit(1);
   }
  
   mqd = mq_open(argv[1],O_RDONLY|O_NONBLOCK);
   mq_getattr(mqd, &attr);
   
   sigev.sigev_notify = SIGEV_THREAD;  //信号,产生线程
   sigev.sigev_value.sival_ptr = NULL;
   sigev.sigev_notify_function = notify_thread;
   sigev.sigev_notify_attributes = NULL;

   if(mq_notify(mqd,&sigev) == -1)
   {
       perror("mq_notify error.\n");
       exit(1);
   }   
   for(;;)
   {
      pause();
   }
   return 0;
}

static void notify_thread(union sigval arg)
{
   ssize_t  n;
   void     *buff;

   printf("notify thread started.\n");
   
   buff = malloc(attr.mq_msgsize);
   mq_notify(mqd,&sigev);
   while( (n=mq_receive(mqd,buff,attr.mq_msgsize,NULL)) >= 0)
         printf("read %ld butes \n",(long)n);
   if(errno != EAGAIN)
   {
       perror("mq_receive error\n");
       exit(1);
   }
   free(buff);
   pthread_exit(NULL);
}


你可能感兴趣的:(Posix消息队列)