Linux进程间通信(IPC)编程实践(三) 详解System V消息队列


1 消息队列简介

消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法(本机);每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值。消息队列也有管道一样的不足:

(1)每个消息的最长字节数的上限(MSGMAX);

(2)系统中消息队列的总条数也有一个上限(MSGMNI);

(3)每个消息队列所能够保存的总字节数是有上限的(MSGMNB).


消息队列与管道的区别:最主要的区别是管道通信是要求两个进程之间要有亲缘关系,只能承载无格式的字节流,而消息队列当中,通信的两个进程之间可以是完全无关的进程,它是有格式的(可类比TCP\UDP)。至于它与有名管道的区别,首先FIFO是要存储在磁盘上的一种通信方式,而消息队列是在内存中的。


查看系统中的三个限制的上限:

[root@localhost ~]# cat /proc/sys/kernel/msgmax 
8192
[root@localhost ~]# cat /proc/sys/kernel/msgmnb 
16384
[root@localhost ~]# cat /proc/sys/kernel/msgmni 
1942

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 各类IPC对象所共有的数据结构*/  
    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 消息队列所允许的最大字节数 */  
    pid_t           msg_lspid;    /* PID of last msgsnd(2) 最后一个发送数据的进程号*/  
    pid_t           msg_lrpid;    /* PID of last msgrcv(2) 最后一个接受的进程号*/  
};  

3 消息队列在内核中的表示

消息在消息队列中是以链表形式组织的, 每个节点的类型类似如下:

struct msq_Node  
{  
    Type msq_type;  //类型  
    Length msg_len; //长度  
    Data msg_data;  //数据  
  
    struct msg_Node *next;  
};

4 消息队列的基本API

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

4.1 msgget


4.1.1 函数

int msgget(key_t key, int msgflg);  


参数:

  key: 某个消息队列的名字

  msgflg:由九个权限标志构成,如0644,它们的用法和创建文件时使用的mode模式标志是一样的(但是消息队列没有x(执行)权限)

返回值:

  成功返回消息队列编号,即该消息队列的标识码;失败返回-1
 

4.1.2 接下来我们举几个实例来看一下创建消息队列的不同操作及其结果

(1)  在msgflg处指定IPC_CREAT, 如果不存在该消息队列, 则创建之

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

int main(int argc, char *argv[])  
{  
    // 指定IPC_CREAT,如果不存在, 则创建消息队列  
    int msgid = msgget(1234, 0666|IPC_CREAT);  
    if (msgid == -1)
    {
        printf("msgget error!\n");
        return -1;
    }        
    printf("msgget success!\n");
    
    return 0;
}

注意:KEY是16进制显示。我们可以使用命令 ipcrm -q 删除消息队列或者 ipcrm -Q [key](此时key!=0); Ipcs查看消息队列;要指定IPC_CREAT,才可创建,类似于文件的creat。

ipcs 查看消息队列

[root@localhost msg]# ipcs

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    
0x000004d2 0          root       666        0            0           

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      

------ Semaphore Arrays --------
key        semid      owner      perms      nsems     

[root@localhost msg]# 
[root@localhost msg]# 

(2) IPC_CREAT|IPC_EXCL, 如果该消息队列已经存在, 则返回出错

#include <sys/types.h>  
#include <sys/ipc.h>  
#include <sys/msg.h>
#include <stdio.h>
 
int main(int argc, char *argv[])  
{  
    // 指定IPC_EXCL, 如果已经存在,则报告文件已经存在(错误)  
    int msgid = msgget(1234, 0666|IPC_CREAT|IPC_EXCL);  
    if (msgid == -1)
    {
        printf("msgget error!\n");
        return -1;
    }
    printf("msgget success!\n");
    
    return 0;
}

(3) 将key指定为IPC_PRIVATE(值为0)

将key指定为IPC_PRIVATE之后,则每调用一次msgget会创建一个新的消息队列。而且每次创建的消息队列的描述符都是不同的! 因此, 除非将MessageID(key)传送给其他进程(除非有关联的进程),否则其他进程也无法使用该消息队列。但是具有亲缘进程的是可以使用的(fork)。因此, IPC_PRIVATE创建的消息队列,只能用在与当前进程有关系的进程中使用! 

这也意味着进程不能共享这个消息队列,父子兄弟进程是可以的。

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

int main(int argc, char *argv[])  
{  
    // 指定IPC_PRIVATE
    int msgid = msgget(IPC_PRIVATE, 0666|IPC_CREAT|IPC_EXCL);  
    if (msgid == -1)
    {
        printf("msgget error!\n");
        return -1;
    }
    printf("msgget success!\n");
    
    return 0;
}

运行结果:

[root@localhost msg]# ipcs

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    
0x00000000 65536      root       666        0            0           

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      

------ Semaphore Arrays --------
key        semid      owner      perms      nsems     

[root@localhost msg]# 

以上KEY为0x00000000的都是使用IPC_PRIVATE创建的。

(4) 仅打开消息队列时, msgflg选项可以直接忽略(填0), 此时是以消息队列创建时的权限进行打开

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

int main(int argc, char *argv[])  
{  
    int msgid = msgget(1234, 0);  
    if (msgid == -1)
    {
        printf("msgget error!\n");
        return -1;
    }
    printf("msgget success!\n");
    printf("msgid = %d", msgid);
    
    return 0;
}

(5) 低权限创建,高权限打开

会发生“Permission denied”的错误

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

int main()  
{  
    // 低权限创建  
    int msgid = msgget(0x255,0444 | IPC_CREAT);  
    if (msgid < 0)
    {
        printf("mesget error!\n");
        return -1;
    }
    else
    {
        printf("Create Mes OK, msgid = %d\n",msgid);  
    }
  
    // 高权限打开  
    msgid = msgget(0x255,0644 | IPC_CREAT);  
    if (msgid < 0)
    {
        printf("mesget error!\n");
        return -1;
    }
    else
    {
        printf("Create Mes OK, msgid = %d\n",msgid);
    }
    
    return 0;
}

4.2 msgctl函数

功能:获取/设置消息队列的信息

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

参数:

   msqid: 由msgget函数返回的消息队列标识码

(1) IPC_RMID, 删除消息队列

注意: 消息队列并没有运用"引用计数"的功能

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

int main()  
{  
    int msgid = msgget(0x255, 0444 | IPC_CREAT);  
    if (msgid == -1)
    {
        printf("msgget error!\n");
        return -1;
    }
    
    if (msgctl(msgid, IPC_RMID, NULL) == -1)
    {
        printf("msgctl IPC_RMID error!\n");
        return -1;
    }
    
    printf("msgctl IPC_RMID success!\n");
    
    return 0;
}

(2) IPC_STAT,消息队列相关信息读入buf

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

int main()  
{  
    int msgid = msgget(0x255, 0666|IPC_CREAT);  
    if (msgid == -1)
    {
        printf("msgget error");
        return -1;
    }
  
    struct msqid_ds buf;  
    if (msgctl(msgid,IPC_STAT,&buf) == -1)
    {
        printf("msgctl error");
        return -1;
    }
  
    printf("buf.msg_perm.mode = %o\n",buf.msg_perm.mode);   // %o以八进制打印
    printf("buf.__key = %x\n", buf.msg_perm.__key);         // %x以十六进制打印
    
    printf("buf.__msg_cbytes = %d\n", buf.__msg_cbytes);  
    printf("buf.msg_qbytes = %d\n", buf.msg_qbytes);  
    printf("buf.msg_lspid = %d\n", buf.msg_lspid);
    
    return 0;
}

(3) IPC_SET,一般需要先获取,然后再设置

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

int main()  
{  
    int msgid = msgget(0x255, 0666|IPC_CREAT);  
    if (msgid == -1)
    {
        printf("msgget error!\n");
        return -1;
    }
  
    // 获取消息队列的属性  
    struct msqid_ds buf;  
    if (msgctl(msgid,IPC_STAT,&buf) == -1)
    {
        printf("msgctl error!\n");
        return -1;
    }
  
    // 设置消息队列的属性  
    buf.msg_perm.mode = 0600;  
    if (msgctl(msgid, IPC_SET, &buf) == -1)
    {
        printf("msgctl error!\n");
        return -1;
    }
  
    // 获取并打印  
    bzero(&buf, sizeof(buf));  
    if (msgctl(msgid, IPC_STAT, &buf) == -1)
    {
        printf("msgctl IPC_STAT error!\n");
        return -1;
    }
    
    printf("mode = %o\n", buf.msg_perm.mode);
    
    return 0;
}

4.2 msgsnd

4.2.1 函数

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

参数

   msgid: 由msgget函数返回的消息队列标识码, 也可以是通过ipcs命令查询出来的一个已经存在的消息队列的ID号

   msgp:是一个指针,指针指向准备发送的消息,

   msgsz:是msgp指向的消息长度, 注意: 这个长度不含保存消息类型的那个long int长整型

   msgflg:控制着当前消息队列满或到达系统上限时将要发生的事情,如果msgflg = IPC_NOWAIT表示队列满不等待,返回EAGAIN错误。

   
另外:消息结构在两方面受到制约: 

    (1)它必须小于系统规定的上限值(MSGMAX); 
    (2)它必须以一个long int长整数开始,接收者函数将利用这个长整数确定消息的类型;

// 消息结构参考形式如下:    
struct msgbuf    
{    
    long mtype;       /* message type, must be > 0 */    
    char mtext[1];    /* message data, 可以设定为更多的字节数 */    
};

4.3.2 实例

/**
示例1:
测试1: 发送消息的最大长度为8192字节, 一旦超过这个值, 则msgsnd出错, 提示 Invalid argument错误;

测试2: 消息队列所能够接收的最大字节数16384字节, 一旦超过这个长度, 如果msgflg为0(阻塞模式), 则进程会一直阻塞下去, 直到有进程来将消息取走; 而如果msgflg为IPC_NOWAIT模式, 则一个字节也不会写入消息队列, 直接出错返回;  
**/ 

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

int main(int argc, char *argv[])    
{    
    // argv[0]=./main ,argv[1]=type, argv[2]=len
    if (argc != 3)
    {
        printf("Usage: ./main <type> <length>\n");
        return -1;
    }
    
    int type = atoi(argv[1]);    
    int len = atoi(argv[2]);    
    
    int msgid = msgget(0x255, 0666|IPC_CREAT);    
    if (msgid == -1)
    {
        printf("msgget error!\n");
        return -1;
    }
    
    struct msgbuf *buf = NULL;    
    buf = (struct msgbuf *)malloc(len + sizeof(msgbuf::mtype));    
    buf->mtype = type;    
    if (msgsnd(msgid, buf, len, IPC_NOWAIT) == -1)  //非阻塞
    {
        printf("msgsnd error!\n");
        return -1;
    }
    
    return 0;
}

4.4 msgrcv

4.4.1 函数

size_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

参数

   msgid: 由msgget函数返回的消息队列标识码

   msgp:是一个指针,指针指向准备接收的消息;

   msgsz:是msgp指向的消息长度,这个长度不含保存消息类型的那个long int长整型

   msgtype:它可以实现接收优先级的简单形式(见下图)

   msgflg:控制着队列中没有相应类型的消息可供接收时将要发生的事(见下图)
   

返回值:

   成功->返回实际放到接收缓冲区里去的字节数(注意: 此处并不包含msgbuf中的mtype的长度[man-page: msgrcv() returns the number of bytes actually copied into the mtext array.]);

   失败->返回-1;

msgtyp参数

msgtyp=0

返回队列第一条信息

msgtyp>0

返回队列第一条类型等于msgtype的消息

msgtyp<0

返回队列第一条类型小于等于(<=)msgtype绝对值的消息,并且是满足条件的消息类型最小的消息(按照类型进行排序的顺序进行接收消息)

msgflg参数

msgflg=IPC_NOWAIT

队列没有可读消息不等待,返回ENOMSG错误。

msgflg=MSG_NOERROR

消息大小超过msgsz(msgrcv 函数的第三个参数)时被截断, 并且不会报错

msgtyp>0且msgflg=MSG_EXCEPT

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

另外,下面的接受程序中,我们会用到一个用于命令行执行程序时的指定不同参数的函数getopt
我们先介绍一下这个函数的基本用法:

./main 1 234 -c -d

程序会根据读取的参数执行相应的操作,在C语言中,这个功能一般是靠getopt()这个函数,结合switch语句来完成的:

#include <unistd.h>
#include <stdlib.h>
#include <iostream>
using namespace std;
 
int getopt(int argc, char * const argv[],    
           const char *optstring);    
    
extern char *optarg;    
extern int optind, opterr, optopt;    
  
  
// 示例: 解析 ./main -n -t 3 中的参数选项    
int main(int argc, char *argv[])    
{    
    while (true)    
    {    
        int opt = getopt(argc, argv, "nt:");    
        if (opt == '?')
        {
            exit(EXIT_FAILURE);
        }
        else if (opt == -1)
        {        
            break;    
        }
        
        switch (opt) 
        {    
            case 'n':
            {
                cout << "-n" << endl;    
                break;
            }
            
            case 't':
            {
                int n = atoi(optarg);    
                cout << "-t " << n << endl;    
                break;
            }
        }    
    }    
}

例如参数"a:b::cde",表示可以有,-a,-b,-c,-d,-e 这几个参数。

":" 表示必须该选项带有额外的参数,全域变量optarg会指向此额外参数,"::"标识该额外的参数可选(有些Uinx可能不支持"::")。

全域变量optind指示下一个要读取的参数在argv中的位置。

如果getopt()找不到符合的参数则会印出错信息,并将全域变量 optopt 设为“?”字符。

如果不希望getopt()印出错信息,则只要将全域变量 opterr 设为0即可。

/** 
示例2: 消息接收(配合示例1中程序使用)  
说明:     -t [number], 指定接收消息的类型, 类型为number的值  
          -n ,指定以IPC_NOWAIT模式接收消息  
**/

#include <sys/types.h>  
#include <sys/ipc.h>  
#include <sys/msg.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <iostream>
using namespace std;

int main(int argc, char *argv[])    
{    
    /** 解析参数 **/
    
    int type = 0;
    int flag = 0;
    int opt;
    while ((opt = getopt(argc, argv, "nt:")) != -1)    
    {    
        switch (opt)    
        {
        
            case 'n':   // 指定IPC_NOWAIT选项
            {
                flag |= IPC_NOWAIT;
                break;
            }
            case 't':   // 指定接收的类型, 如果为0的话,说明是按照顺序接收
            {
                type = atoi(optarg);
                break;
            }
            
            default:
            {
                exit(EXIT_FAILURE);
            }
        }    
    }    
    
    int msgid = msgget(0x255, 0);    
    if (msgid == -1)
    {
        printf("msgget error!\n");
        return -1;
    }
    
    const int MSGMAX = 8192;    //指定一条消息的最大长度    
    struct msgbuf *buf;    
    buf = (struct msgbuf *)malloc(MSGMAX + sizeof(buf->mtype));    
    
    ssize_t nrcv;    
    if ((nrcv = msgrcv(msgid, buf, MSGMAX, type, flag)) == -1)
    {
        printf("msgrcv error!\n");
        return -1;
    }
    
    cout << "recv " << nrcv << " bytes, type = " << buf->mtype << endl;
    return 0;
}

从消息队列中发送/读取消息:

/** 综合示例: msgsnd/msgrcv, 消息发送/接收实践 **/    
// 1. 消息发送

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

struct myMsgbuf    
{    
    long mtype;          /* message type, must be > 0 */    
    char mtext[1024];    /* message data, 可以设定为更多的字节数 */    
};


int main()    
{    
    int msgid = msgget(0x1234,0666|IPC_CREAT);    
    if (msgid == -1)
    {
        printf("msgget error!\n");
        return -1;
    }
    
    struct myMsgbuf myBuffer;    
    for (int i = 0; i < 10; ++i)    
    {    
        myBuffer.mtype = i+1;    
        sprintf(myBuffer.mtext,"Hello, My number is %d",i+1);    
        if (msgsnd(msgid,&myBuffer,strlen(myBuffer.mtext), IPC_NOWAIT) == -1)
        {
            printf("msgsnd error(%d)!\n", errno());
            return -1;
        }
    }
    
    return 0;
}

编译执行:

[root@localhost thread]# g++ -o send ./send.cpp
[root@localhost thread]# 
[root@localhost thread]# ./send 
[root@localhost thread]# 
[root@localhost thread]# 
[root@localhost thread]# ipcs

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    
0x00001234 32768      root       666        211          10          

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      

------ Semaphore Arrays --------
key        semid      owner      perms      nsems     

[root@localhost thread]#

// 2. 消息接收:从队首不断的取数据

#include <sys/types.h>  
#include <sys/ipc.h>  
#include <sys/msg.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <iostream>
using namespace std;

struct myMsgbuf    
{    
    long mtype;          /* message type, must be > 0 */    
    char mtext[1024];    /* message data, 可以设定为更多的字节数 */    
};

int main(int argc, char *argv[])    
{    
    int msgid = msgget(0x1234, 0);
    if (msgid == -1)
    {
        printf("msgget error");
        return -1;
    }
    
    struct myMsgbuf buf;    
    ssize_t nrcv;    
    while ((nrcv = msgrcv(msgid, &buf, sizeof(buf.mtext), 0, IPC_NOWAIT)) > 0)    
    {    
        cout << "recv " << nrcv << " bytes, type: " << buf.mtype    
        << ", message: " << buf.mtext << endl;    
    }
    
    return 0;
}

编译执行

[root@localhost thread]# g++ -o rec ./rec.cpp 
[root@localhost thread]# 
[root@localhost thread]# 
[root@localhost thread]# ./rec 
recv 21 bytes, type: 1, message: Hello, My number is 1
recv 21 bytes, type: 2, message: Hello, My number is 2
recv 21 bytes, type: 3, message: Hello, My number is 3
recv 21 bytes, type: 4, message: Hello, My number is 4
recv 21 bytes, type: 5, message: Hello, My number is 5
recv 21 bytes, type: 6, message: Hello, My number is 6
recv 21 bytes, type: 7, message: Hello, My number is 7
recv 21 bytes, type: 8, message: Hello, My number is 8
recv 21 bytes, type: 9, message: Hello, My number is 9
recv 22 bytes, type: 10, message: Hello, My number is 10
[root@localhost thread]# 
[root@localhost thread]# 
[root@localhost thread]# ipcs

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    
0x00001234 32768      root       666        0            0           

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      

------ Semaphore Arrays --------
key        semid      owner      perms      nsems     

[root@localhost thread]#

本文转自:

https://blog.csdn.net/NK_test/article/details/49757631

猜你喜欢

转载自blog.csdn.net/u011857683/article/details/82391790
今日推荐