Linux —— 进程间通信之消息队列

1. 消息队列基本概念

1.1 什么是消息队列?

  消息队列本质是一个队列(具有先进先出的特性),队列中存放的元素是一个个消息缓存块(message)。
这里写图片描述
消息缓冲块:
  一种自定义数据结构(可以类比链表中的节点),用于存储信息,进程之间通信的方法之一,结构如下:

struct msgbuf{ 
long channel;    //通道号 
char mtext[100]; //消息内容,这里的100是自定义值,可更改 
} 

通道:
  并不是一个实质的东西,有的书上不定义成channel,而是写成type——类别。它的作用是给消息缓存块分类的,具有相同channel值的消息缓存块属于同一类。

1.2 消息队列的作用

  1. 不同进程(process)之间传递消息时,两个进程之间耦合度过高,改动一个进程,引发必须修改另一个进程,为了隔离这两个进程,在两进程间抽离出一层(一个模块),所有两进程之间传递的消息,都必须通过消息队列来传递,单独修改某一个进程,不会影响另一个;

  2. 不同进程(process)之间传递消息时,为了实现标准化,将消息的格式规范化了,并且某一个进程接受的消息太多,一下子无法处理完,这就需要一个先后顺序,必须对收到的消息进行排队,因此诞生了事实上的消息队列;

1.3 消息队列的限制

  1. 每个消息的最大长度是有上限的(MSGMAX),
    使用命令 cat /proc/sys/kernel/msgmax 查看。
  2. 每个消息队列的总的字节数是有上限的(MSGMNB),
    使用命令 cat /proc/sys/kernel/msgmnb 查看。
  3. 系统上消息队列的总数也有⼀一个上限(MSGMNI),
    使用命令 cat /proc/sys/kernel/msgmni 查看。
  4. 消息队列里面的消息一旦取出,就从队列里面消失了,不可多次重复取出同一个消息。

2. 消息队列的各种操作

2.1 查看消息队列

  使用指令 ipcs -q 查看系统中已有的消息队列。
这里写图片描述

  上表中各项的含义(从左到右):

消息队列的关键字(key) 消息队列的标识符(msqid) 系统用户(owner) 消息队列的权限(perms) 消息队列的大小(used-bytes) 消息队列中消息的条数(messages)

2.2 删除消息队列

  使用命令 ipcrm -Q + key 删除消息队列关键字为key的消息队列。

[root@localhost msg]# ipcs -q

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    
0x310203cf 327680     tian       0          0            0           

[root@localhost msg]# ipcrm -Q 0x310203cf
[root@localhost msg]# ipcs -q

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    


2.3 创建消息队列

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

int msgget(key_t key, int msgflg);
/* 
 * 功能:
 *   如果系统中没有消息队列关键字为key的消息队列,那就创建这个消息队列;
 *   如果系统中已经有这个消息队列了,那就访问这个消息队列。
 * 
 * 参数:
 *   key:    
 *     0(IPC_PRIVATE):会建立新的消息队列
 *     大于0的32位整数:视参数msgflg来确定操作。通常要求此值来源于ftok返回的IPC键值
 *   msgflg: 由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
 * 
 * 返回值:
 *   成功返回创建或打开的消息队列的标识符(一个非负整数);失败返回-1 
 */

2.3.1 关于参数key

  key作为队列的关键字,对于每个消息队列来说它是独一无二的。函数将它与已有的消息队列的关键字进行比较,来判断某消息队列是否已经创建,进而确定msgget函数是执行创建还是打开操作。

  通常使用ftok函数来生产key。

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

key_t ftok(const char *pathname, int proj_id);
/*
 * 功能:
 *   生成一个唯一的key
 *  
 * 参数:
 *   pathname:你指定的文件名(路径),要求文件必须存在,一般使用当前目录'.'
 *   proj_id: id是子序号,虽然是int类型,但是只使用低8个bits(1-255),且这8位不能全部为0 
 * 
 * 返回值:
 *   失败返回-1,成功返回key_t值
 */

2.3.2 关于参数msgflg

msgflg可以取以下几个值: 
0: 取消息队列标识符,若不存在则函数会报错 
IPC_CREAT
  如果消息队列对象不存在,则创建之,否则进行打开操作;IPC_CREAT 后面可以加权限,如0666等,和文件的访问权限一样;也可以加IPC_EXCL
IPC_EXCL
  和IPC_CREAT 一起使用(用|连接),如果消息对象不存在则创建之,否则产生一个错误并返回
  
  如果单独使用IPC_CREAT 标志,msgget()函数要么返回一个已经存在的消息队列的标识符,要么返回一个新建立的消息队列的标识符。
  
  如果将IPC_CREATIPC_EXCL标志一起使用,msgget()将返回一个新建的消息队列的标识符,或者返回-1 。如果消息队列对象已存在,IPC_EXCL标志本身并没有太大的意义,但和IPC_CREAT 标志一起使用可以用来保证所得的消息队列是新创建的而不是打开的已有的消息队列
  

2.3.3 msgget使用示例

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

int main ()
{
    key_t key;
    key = ftok(".",'0');//获取key

    int id = msgget(key, IPC_CREAT | IPC_EXCL);//创建消息队列
    if ( id == -1){
        perror( "mssget");
        exit(1);
    }
    return 0;
}

创建结果:

[root@localhost msg]# ./a.out 
[root@localhost msg]# ipcs -q

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    
0x310203cf 557056     root       0          0            0           

加上IPC_EXCL后,如果创建key值相同的消息队列,那么就会报错:

[tian@localhost msg]$ ./a.out 
mssget: File exists

2.4 往消息队列写数据

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

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
/*
 * 功能:
 *   将msgp消息写入到标识符为msqid的消息队列
 *  
 * 参数:
 *   msqid: 消息队列标识符,表示你想要往哪个消息队列里写消息 
 *   msgp:  一个指向消息缓存块结构体指针
 *   msgsz: 要发送消息的大小,不含消息类型占用的4个字节,即char mtext[100]的大小 
 *   msgflg:
 *     设置0:表示阻塞方式,如果消息队列满了那么写进程就会阻塞,直到消息队列有空位,再进行写入操作;
 *     设置IPC_NOWAIT:表示非阻塞方式,如果消息队列满了,直接报错返回。
 *     设置IPC_NOERROR:若发送的消息大于size字节,则把该消息截断,截断部分将被丢弃,且不通知发送进程。 
 *  
 * 返回值:
 *   成功:0
 *   失败:-1,错误原因存在error中
 */

写入操作的步骤:

  1. 创建或打开一个消息队列;
  2. 创建一个消息缓存块,把消息内容写到这个消息缓存块中;
  3. 把这个缓存块内容写入到指定消息队里面;

使用示例:

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

struct msgbuf{
    long channel;
    char mtext[100];
};

int main()
{
    int id = msgget(666, IPC_CREAT); // 打开消息队列,第二个参数为0
    if ( id == -1 ) perror("msgget"),exit(1);

    struct msgbuf mb;
    memset(&mb, 0, sizeof(mb));

    printf("channel:");
    scanf("%ld", &mb.channel);
    printf("text:");
    scanf("%s", mb.mtext);

    int r = msgsnd(id, &mb, strlen(mb.mtext), 0);
    if ( r == -1 ) perror("msgsnd"),exit(1);

    return 0;   
}

写入结果:

[root@localhost msg]# ./a.out 
channel:1
text:hello
[root@localhost msg]# ipcs -q

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    
0x0000029a 425984     root       0          5            1           


2.5 从消息队列读数据

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

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
/*
 * 功能:
 *   从标识符为msqid的消息队列读取消息并存于msgp中,读取后把此消息从消息队列中删除
 *  
 * 参数:
 *   msqid: 消息队列标识符,表示你想要从哪个消息队列读消息 
 *   msgp:  存放消息的结构体,结构体类型要与msgsnd函数发送的类型相同; 
 *   msgsz: 要接收消息的大小,不含消息类型占用的4个字节,即char mtext[100]的大小 
 *   msgtyp:
 *     0: 读取队列中的第一个消息
 *     >0:读取类型等于msgtyp的第一个消息
 *     <0:读取类型等于或者小于msgtyp绝对值的第一个消息
 *   msgflg:
 *     0: 阻塞式接收消息,没有该类型的消息msgrcv函数一直阻塞等待
 *     IPC_NOWAIT:如果没有返回条件的消息调用立即返回,此时错误码为ENOMSG
 *     IPC_EXCEPT:与msgtype配合使用,返回队列中第一个类型不为msgtype的消息
 *     IPC_NOERROR:如果队列中满足条件的消息内容大于所请求的size字节,则把该消息截断,截断部分将被丢弃 
 * 
 * 返回值:
 *   成功:实际读取到的消息数据长度
 *   出错:-1,错误原因存于error中
 */

关于参数msgtyp
  一个消息队列可以被多个进程使用,假设现在有4个进程在使用这个消息队列,两两之间进行通信。此时就有必要标识哪两个进程是一组的,消息队列中哪块消息缓存区是属于你们的。这时候就用到了msgtyp

读消息的基本步骤:

  1. 打开或创建一个消息队列;
  2. 创建一个消息缓存块,把消息队列中要读出的消息放到这个缓存款中;
  3. 从这个消息缓存块查看消息内容;

使用示例:

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

struct msgbuf{
    long type;
    char mtext[100];
};
int main()
{
    int id = msgget(666, IPC_CREAT);
    if ( id == -1 ) perror("msgget"),exit(1);

    struct msgbuf mb;
    memset(&mb, 0, sizeof(mb));

    printf("channel:");
    int channel;
    scanf("%d", &channel);  

    ssize_t r = msgrcv(id, &mb, 100, channel, IPC_NOWAIT);
    if ( r == -1 ) perror("msgrcv"),exit(1);
    printf("%s\n", mb.mtext);
    return 0;
}

读取结果:

[root@localhost msg]# ./w
channel:1
text:hello
[root@localhost msg]# ipcs -q

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    
0x0000029a 491520     root       0          5            1           

[root@localhost msg]# ./r
channel:1
hello
[root@localhost msg]# ipcs -q

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    
0x0000029a 491520     root       0          0            0           

2.6 设置消息队列属性

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

int msgctl(int msqid, int cmd, struct msqid_ds *buf)
/*
 * 功能:
 *   获取或设置消息队列属性
 *  
 * 参数:
 *   msqid:消息队列标识符
 *   cmd:
 *     IPC_STAT:获得msgid的消息队列头数据到buf中
 *     IPC_SET:设置消息队列的属性,要设置的属性需先存储在buf中,可设置的属性包括:msg_perm.uid、msg_perm.gid、msg_perm.mode以及msg_qbytes
 *     buf:消息队列管理结构体,请参见消息队列内核结构说明部分
 *  
 * 返回值:
 *   成功:0
 *   失败:-1,错误原因存于error中
 */

猜你喜欢

转载自blog.csdn.net/tianzez/article/details/80141179