[聚合文章] system V消息队列

消息系统 2018-01-09 20 阅读

1.消息队列

1)消息队列提供了一个从进程向另外一个进程发送一块是数据的方法

2)每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型

不足之处:

每个消息的最大长度是有限制的。MSGMAX

每个消息队列的总的字节数也是有上限。MSGMNB

系统上消息队列的总数也有一个上限。MSGMNI

可以这样查看这三个限制:

xcy@xcy-virtual-machine:~$ cat /proc/sys/kernel/msgmax
8192
xcy@xcy-virtual-machine:~$ cat /proc/sys/kernel/msgmnb
16384
xcy@xcy-virtual-machine:~$ cat /proc/sys/kernel/msgmni
32000
xcy@xcy-virtual-machine:~$

2.IPC对象数据结构

内核为每个IPC对象维护了一个数据结构:

           struct ipc_perm {
               key_t          __key;       /* Key supplied to msgget(2) */
               uid_t          uid;         /* Effective UID of owner */
               gid_t          gid;         /* Effective GID of owner */
               uid_t          cuid;        /* Effective UID of creator */
               gid_t          cgid;        /* Effective GID of creator */
               unsigned short mode;        /* Permissions */
               unsigned short __seq;       /* Sequence number */
           };

下面是消息队列的数据结构:

           struct msqid_ds {
               struct ipc_perm msg_perm;     /* Ownership and permissions */
               time_t          msg_stime;    /* Time of last msgsnd(2) */
               time_t          msg_rtime;    /* Time of last msgrcv(2) */
               time_t          msg_ctime;    /* Time of last change */
               unsigned long   __msg_cbytes; /* Current number of bytes in
                                                queue (nonstandard) */
               msgqnum_t       msg_qnum;     /* Current number of messages
                                                in queue */
               msglen_t        msg_qbytes;   /* Maximum number of bytes
                                                allowed in queue */ // 这个就是MSGMNB
               pid_t           msg_lspid;    /* PID of last msgsnd(2) */
               pid_t           msg_lrpid;    /* PID of last msgrcv(2) */
           };

3. 如何查看 IPC 对象

可以看到每个 IPC 对象都有一个 key 还有一个 id

命令 ipcs

如何删除:

ipcrm -Q <key>

ipcrm -q <id>

例如:

xcy@xcy-virtual-machine:~$ ipcrm -Q 0x4d2
xcy@xcy-virtual-machine:~$

4.如何操作消息队列

主要有下面几个函数:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgfl_g); 
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgfl_g);
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgfl_g);

先补充一个自己的头文件comm.h

#ifndef __COMM_H__
#define __COMM_H__


#include<errno.h>
#include<stdlib.h>
#define ERR_EXIT(m) \
    do \
    { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)


#endif // __COMM_H__

下面单独介绍:

4.1 msgget:

int msgget(key_t key, int msg fl_g ); 

功能:用来创建和访问一个消息队列

参数:key:某个消息队列的名字(比如1234)。还可以是IPC_PRIVATE,表示私有的,这样创建出来的消息队列只能用于亲缘进程通信,它的key是0.

msg fl_g :由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的

返回值:成功返回非负整数,是该消息队列的标识码,失败返回-1.

下面表示创建的大致过程:


开始 ->
if(key == IPC_PRIVATE)
{
    if(系统表格满了)    
    {
        // 出错返回
    }
    else
    {
        // 创建成功,返回标识符
    }
}
else // 不是私有的
{
    if(key已经存在)    
    {
        if(IPC_CREAT和IPC_EXCL都设置了)
        {
            // 出错返回
        }
        else
        {
            if(访问权限允许)
            {
                // 成功打开消息队列,返回标识符
            }
            else
            {
                // 出错返回
            }
        }
    }
    else   // key 不存在
    {
        if(IPC_CREAT设置了)
        {
            if(系统表格满了)    
              {
                // 出错返回
            }
            else
            {
                // 创建成功,返回标识符
            }
        }
        else // 不存在就无法打开
        {
            // 出错返回
        }
    }
}

View Code

实例:


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

#include"comm.h"
int main()
{
    //int msgid = msgget(1234,0666 | IPC_CREAT | IPC_EXCL);// 已存在则失败
    int msgid = msgget(1234,0666 | IPC_CREAT);
    //int msgid = msgget(IPC_PRIVATE,0666 | IPC_CREAT);
    if(msgid < 0)
        ERR_EXIT("msgget");
    printf("msgget success\n");
    printf("msgid = %d\n", msgid);

    return 0;
}

View Code

4.2 msgctl

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

作用:消息队列控制函数

返回值:成功返回0,失败返回-1

参数:msgid:由msgget返回的id

cmd:有三个值可选

IPC_STAT:获取消息队列的状态,把msqid_ds结构中的数据设置为消息队列的当前关联值

IPC_SET:在进程有足够权限的条件下,把消息队列的当前关联值设置为msqid_ds数据结构中给出的值

IPC_RMID:删除消息队列。(此时buf可置为null)

buf:输入参数,根据第二个参数来设置,具体用法参考例子

例子:


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

#include"comm.h"

void stattest(int msgid)
{
    struct msqid_ds buf;

    int ret = msgctl(msgid, IPC_STAT, &buf);
    if(ret < 0)
        ERR_EXIT("msgctl");
    printf("mode = %o\n", buf.msg_perm.mode);
    printf("bytes = %ld\n", buf.__msg_cbytes);
    printf("number = %d\n", (int)buf.msg_qnum);
    printf("msgmnb = %d\n", (int)buf.msg_qbytes);
}

void rmtest(int msgid)
{
    int ret = msgctl(msgid, IPC_RMID, NULL);
    if(ret < 0)
        ERR_EXIT("msgctl");

    printf("remove msg success\n");
}
void settest(int msgid)
{
    struct msqid_ds buf;

    int ret = msgctl(msgid, IPC_STAT, &buf);
    if(ret < 0)
        ERR_EXIT("msgctl stat");
    sscanf("600", "%o", (unsigned int*)&buf.msg_perm.mode);
    ret = msgctl(msgid, IPC_SET, &buf);
    if(ret < 0)
        ERR_EXIT("msgctl set");
}


int main()
{
    int msgid = msgget(1234,0);
    if(msgid < 0)
        ERR_EXIT("msgget");
    printf("msgget success\n");
    printf("msgid = %d\n", msgid);
    //stattest(msgid);
    //rmtest(msgid);
    settest(msgid);
    return 0;
}

View Code

运行,这里仅仅展示stat的例子:

xcy@xcy-virtual-machine:~/test/IPC_MSG$ ./msg_stat // 不存在会出错
msgget: No such file or directory
xcy@xcy-virtual-machine:~/test/IPC_MSG$ ./msg_get  // 先创建
msgget success
msgid = 229376
xcy@xcy-virtual-machine:~/test/IPC_MSG$ ./msg_stat
msgget success
msgid = 229376
mode = 666
bytes = 0
number = 0
msgmnb = 16384
xcy@xcy-virtual-machine:~/test/IPC_MSG$

4.3 msgsnd

int msgsnd(int msqid, const void *msgp,size_t msgsz, int msg fl_g );

作用:把一条消息添加到消息队列中去 返回值:成功返回 0 ,失败返回 -1

参数: msgid :由 msgget 返回的 idmsgp :一个指针,指向准备发送的消息

msgsz :是 msgp 指向的消息长度。这个长度不包含保存消息类型那个

long int

的长整型

msg fl_g :控制位,控制当前消息队列满或到达系统上限时将要发生的事情。

0 表示等待; msg fl_g =IPC_NOWAIT 。表示队列满不等待,返回

EAGAIN

下面是消息的封装(参考形式):

struct msgbuf{

longmtype; //  必须为

long

型,接收者函数通过这个长整数确定消息的类型

char mtext[1]; // 这里存放具体的消息内容,必须小于 MSGMAX

};

例子:


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

#include"comm.h"

struct msgbuf{
    long mtype;
    char mtext[1];
};

int main(int argc, char *argv[])
{
    if(argc != 3)
    {
        printf("Usage:<%s> <type> <len>\n", argv[0]);
        return -1;
    }
    int type = atoi(argv[1]); // 表示发送的类型
    int len = atoi(argv[2]); // 表示发送的数据大小

    int msgid = msgget(1234,0);
    if(msgid < 0)
        ERR_EXIT("msgget");
    printf("msgget success\n");
    printf("msgid = %d\n", msgid);

    struct msgbuf *buf = (struct msgbuf*)malloc(sizeof(buf));
    buf->mtype = type;
    int ret = msgsnd(msgid, (void*)buf, len, 0);// 满了就等待
    //int ret = msgsnd(msgid, (void*)buf, len, IPC_NOWAIT);// 满了就会失败
    if(ret < 0)
        ERR_EXIT("msgsnd");
    return 0;
}

View Code

4.4 msgrcv

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msg fl_g ); 作用:从一个消息队列中接收消息

返回值:成功返回实际接收到放到接收缓冲区里去的字符个数,失败返回-1 参数:msgid:由msgget返回的id

msgp:一个指针,指向准备接收的消息 msgsz:是msgp指向的消息长度,不包含消息类型的长度。

msgtype:它可以实现接收优先级的简单形式 msg fl_g :控制着队列中没有相应类型的消息可工接收时将要发生的事情。


下面重点来分析msgtye:

=0:表示返回队列里的第一条消息

>0:返回队列第一条类型等于msgtype的消息。

<0:返回队列第一条类型小于等于msgtype绝对值的消息。

关于小于0,man手册里面的东西:

* If  msgtyp  is  less than 0, then the first message in the queue with  the lowest type less than or equal to the absolute  value  of  msgtyp will be read.

我的理解是: 第一条小于 msgtype绝对值的消息将会被读取。 实际的测试情况有点不符和,实际情况返回的是小于 msgtype绝对值中类型最小的那一个。

比如:发送给消息队列的顺序是 4 5 4 1 78 3 8(这些数字表示类型),获取的是-10。结果却收到的是类型为1的消息。并且假如发送了多个1,收到的会是第一个类型为1的消息。

补充:

msgfl_g=IPC_NOWAIT:队列没有可读消息不等待,返回ENOMSG错误

msg fl_g =MSG_NOERROR:消息大小超过msgsz时被截断

msgtype>0且msg fl_g

=MSG_EXCEPT:接收类型不等于msgtype的第一条消息

例子:


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

#include"comm.h"

struct msgbuf{
    long mtype;
    char mtext[1];
};

#define MSGMAX 8192

int main(int argc, char *argv[])
{
    int flag = 0;
    int type = 0;
    int opt;
    while(1)
    {
        /* 接受命令行参数。形式如下: ./a.out -n -t 3    
            -n 表示不等待
            -t 3 :表示接收类型为3的消息。
        */
        opt = getopt(argc, argv, "nt:"); // 这个n表示接受n参数(但是-n后面不能接参数),t后面的冒号表示t后面可以接参数。具体用法网上再查一下。
        if(opt == '?')
            exit(EXIT_FAILURE);
        if(opt == -1)
            break;
        switch(opt)
        {
        case 'n':
            flag |= IPC_NOWAIT;
            break;
        case 't':
            type = atoi(optarg);
            break;
        default:
            break;
        }
    }
    int msgid = msgget(1234,0);
    if(msgid < 0)
        ERR_EXIT("msgget");
    printf("msgget success\n");
    printf("msgid = %d\n", msgid);

    struct msgbuf *buf = (struct msgbuf*)malloc(sizeof(long) + MSGMAX);
    buf->mtype = type;
    int ret = msgrcv(msgid, buf, MSGMAX, type, flag);
    if(ret < 0)
        ERR_EXIT("msgsnd");
    printf("recv msg, type:%ld len:%d\n", buf->mtype, ret); // 打印接受到的类型和长度

    return 0;
}

View Code

运行,联合send一起运行:

先是发送:

xcy@xcy-virtual-machine:~/test/IPC_MSG$ ./msg_send
Usage:<./msg_send> <type> <len>
xcy@xcy-virtual-machine:~/test/IPC_MSG$ ./msg_send 5 50
msgget success
msgid = 229376
xcy@xcy-virtual-machine:~/test/IPC_MSG$ ./msg_send 4 40
msgget success
msgid = 229376
xcy@xcy-virtual-machine:~/test/IPC_MSG$ ./msg_send 3 30
msgget success
msgid = 229376
xcy@xcy-virtual-machine:~/test/IPC_MSG$ ./msg_send 2 30
msgget success
msgid = 229376
xcy@xcy-virtual-machine:~/test/IPC_MSG$ ./msg_send 2 40
msgget success
msgid = 229376
xcy@xcy-virtual-machine:~/test/IPC_MSG$ ./msg_send 1 10
msgget success
msgid = 229376

发送了6个消息。类型分别是5 -> 4 -> 3 -> 2 -> 2 -> 1

再来看接收:

xcy@xcy-virtual-machine:~/test/IPC_MSG$ ./msg_recv -n -3  // 这里参数错误了
./msg_recv: invalid option -- '3'
xcy@xcy-virtual-machine:~/test/IPC_MSG$ ./msg_recv -t -3 // -3.表示接收队列中绝对值小于3的类型,并且是最小的
msgget success
msgid = 229376
recv msg, type:1 len:10
xcy@xcy-virtual-machine:~/test/IPC_MSG$ ./msg_recv -t -3
msgget success
msgid = 229376
recv msg, type:2 len:30
xcy@xcy-virtual-machine:~/test/IPC_MSG$ ./msg_recv -t -3
msgget success
msgid = 229376
recv msg, type:2 len:40
xcy@xcy-virtual-machine:~/test/IPC_MSG$ ./msg_recv -n -t 6 // 这里表示不等待,所以会出错。
msgget success
msgid = 229376
msgsnd: No message of desired type
xcy@xcy-virtual-machine:~/test/IPC_MSG$

注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版权等事宜,请你联系站长进行处理。