IPC之消息队列

可阅读mq_overview查阅更多信息:man 7 mq_overview.

posix的消息队列实现更好,但SystemV的消息队列更广泛应用(旧的API)。

posix的消息队列有两种调用方式:库函数和系统调用。

消息队列是一个消息的链接列表,消息都保存在内核中,进程通过一种和共享内存使用的标识符同种类的标识符标识消息。如果你把一个消息添加到一个队列,队列显示出FIFO的特性,因为新消息被添加到队列的末尾。但是,和FIFO不同,队列中的消息能够以有些随意的顺序进行检索,因为,可以通过一个消息的类型把消息从队列中检索出来。从这方面看,队列在编程上类似于结合数组。

system V的消息队列包含如下函数:msgget,msgsnd,msgrcv,msgctl。

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/msg.h>

int msgget(key_t key, int flags);  //flags中可调用标准八进制记法规定所有用户拥有的权限。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int main(int argc, char *argv[])
{
    int qid;
    key_t key;
    
    key = 123;

    if((qid = msgget(key, IPC_CREAT | 0666)) < 0)
    {   
        perror("msgget:create");
        exit(EXIT_FAILURE);
    }   

    printf("created queue id = %d
", qid);

    if((qid == msgget(key, 0)) < 0)
    {   
        perror("msgget: open");
        exit(EXIT_FAILURE);
    }
    printf("opened queue id =%d
", qid);

    exit(EXIT_SUCCESS);
}

把一个新消息添加到队列的末尾,可以使用msgsnd函数,

int msgsnd(int msgid, const void *prt, size_t nbytes, int flags);

ptr指向msgbuf机构体,

struct msgbuf{

  long mtype;

  char mtext[1];

};

消息不应该以null结尾。msgbuf实际只是一个模板,因为mtext必须由保存的数据来确定长度,它和传递给参数nbytes的值减去任何末尾的null字符相对应。mtype可以是任何大于0的long型整数。调用进程还必须具有对这个队列的写权。最后flags变量不是0就是IPC_NOWAIT。如果单个排队消息的总数或者队列以字节计的长度等于系统特定的限制,msgsnd立即返回并且设置errno变量为EAGAIN。因此,你不能向队列添加任何更多的消息,直到由至少一个消息被读取后为止。

提示:可以对msgbuf结构模板进行扩展以满足你的应用程序的需要。例如,如果你要传送一个由一个整数和一个有10个字节的字符串组成的消息,只需按下面的方式声明msgbuf:

struct msgbuf{

  long mtype;

  int i;

  char c[10];

};

msgbuf可简单地看作是一个long型整数后消息数据,消息数据可以采用你觉得合适的格式。在本例声明的结构的长度是sizeof(msgbuf) - sizeof(long)。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#define BUFSZ 512

struct msg{
    long msg_type;
    char msg_text[BUFSZ];
};

int main(int argc, char *argv[])
{
    int qid;
    int len;
    struct msg pmsg;

    if(argc != 2)
    {   
        puts("USAGE: qsnd <queue id>");
        exit(EXIT_FAILURE);
    }

    qid = atoi(argv[1]);

    puts("Enter message to post:");
    if((fgets((&pmsg)->msg_text, BUFSZ, stdin)) == NULL)
    {
        puts("no message to post");
        exit(EXIT_SUCCESS);
    }

    pmsg.msg_type = getpid();
    len = strlen(pmsg.msg_text);
    if((msgsnd(qid, &pmsg, len, 0)) < 0)
    {
        perror("msgsnd");
        exit(EXIT_FAILURE);
    }

    puts("message posted");

    exit(EXIT_SUCCESS);
}
~$./a.out 
created queue id = 32768
opened queue id =32768
~$ipcs -q

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    
0x0000007b 32768      yuxi       666        0            0           

~$gcc qsnd.c -Wall
~$./a.out 32768
Enter message to post:
yuyuyu
message posted
~$ipcs -q

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    
0x0000007b 32768      yuxi       666        7            1   

 读取队列中的消息用msgrcv,

int msgrcv(int msgid, void *ptr, size_t nbytes, long type, int flags);

如果执行成功,msgrcv删除从队列返回的消息。它的参数和msgsnd接受的参数一样,差别在于msgrcv用消息类型和多达nbytes字节的数据填入ptr结构。另外type和前面讨论的msg结构的成员msg_type相对应。type的值决定了返回哪个消息,如下所述:

>如果type是0,返回队列中的第一条消息(最上面的一条消息)。

>如果type大于0,返回msg_type等于type的第一条消息。

>如果type小于0,返回msg_type为小于等于type绝对值的最小值的第一条消息。

flags的值也控制着msgrcv的行为。如果flags中设置了MSG_NOERROR位,那么如果返回的消息比nbytes字节多,消息就会被截断到nbytes字节(但不会产生提示说消息被截短)。否则,msgrcv返回-1表明出错并且设置errno变量的值为E2BIG。消息仍旧保存在队列中。

注意:参数type的负值也可以用于创建一个LIFO,即先入后出类型的队列,这也就是栈。在type中传递负值能够让你在队列中以逆序检索某种类型的消息。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#define BUFSZ 512

struct msg{
    long msg_type;
    char msg_text[BUFSZ];
};

int main(int argc, char *argv[])
{
    int qid;
    int len;
    struct msg pmsg;

    if(argc != 2)
    {   
        puts("USAGE: qrd <queue id>");
        exit(EXIT_FAILURE);
    }

    qid = atoi(argv[1]);

    len = msgrcv(qid, &pmsg, BUFSZ, 0, 0);
    if(len <= 0)
    {
        perror("msgrcv");
        exit(EXIT_FAILURE);
    }

    (&pmsg)->msg_text[len] = '';
    printf("reading queue id: %05d
", qid);
    printf("message type: %05ld
", pmsg.msg_type);
    printf("message length: %d bytes
", len);
    printf("message text: %s
", pmsg.msg_text);

    exit(EXIT_SUCCESS);
}
~$ipcs -q

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    
0x0000007b 32768      yuxi       666        14           2           

~$./a.out 32768
reading queue id: 32768
message type: 04927
message length: 7 bytes
message text: yuyuyu

~$./a.out 32768
reading queue id: 32768
message type: 04985
message length: 7 bytes
message text: yuyuyu

~$ipcs -q

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    
0x0000007b 32768      yuxi       666        0            0    

 msgctl函数提供了对消息队列一定程度的控制功能,

int msgctl(int msgid, int cmd, struct msqid_ds *buf);

一般而言,msgid是一个已经存在的队列ID。cmd可以为如下值之一:

>IPC_RMID--删除队列msgid。

>IPC_STAT--用队列的msqid_ds结构填充buf,这是一种非破坏性读操作。

>IPC_SET--可改变队列的UID、GID、访问模式和队列的最大字节数。

初始化举例:

int init_msq(const char *path, int proj)
{
    if(path == NULL)
        return -1;
    key_t key; 
    
    key = ftok(path, proj);
    if(key == -1)
    {    return -1;
    }    
    
    return msgget(key, IPC_CREAT | 0600);
}

 注:大部分内容摘自《GNU/LINUX编程指南》

原文地址:https://www.cnblogs.com/embedded-linux/p/5079999.html