【操作系统】Linux下进程间通信实现---消息队列

消息队列基本概念

消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。 每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构。我们可以通过发送消息来避免命名管道的同步和阻塞问题。但是消息队列与命名管道一样,每个数据块都有一个最大长度的限制。

消息队列就是一个消息的链表。可以把消息看作一个记录,具有特定的格式以及特定的优先级。对消息队列有写权限的进程可以向消息队列中按照一定的规则添加新消息;对消息队列有读权限的进程则可以从消息队列中读走消息。消息队列是随内核持续的。

  • 消息队列提供了一个从一个进程向另一个进程发送一块数据的方法。
  • 每个数据块都被认为是有一个类型,接收者进程接受的数据块可以有不同的类型值。
  • 消息队列与管道不同的是,消息队列是基于消息的,而管道是基于字节流的,且消息队列的读取不一定是先入先出。
  • 消息队列也有类似管道一样的不足,就是每个消息的最大长度是有上限的(MSGMAX),每个消息队列的总的字节数是有上限的(MSGMNB),系统上消息队列的总数也有一个上限(MSGMNI)。

IPC对象数据结构

IPC对象是活动在内核级别的一种进程间通信的工具。存在的IPC对象通过它的标识符来引用和访问,这个标识符是一个非负整数,它唯一的标识了一个IPC对象,这个IPC对象可以是消息队列或信号量或共享存储器中的任意一种类型。

在Linux系统中标识符被声明成整数,所以可能存在的最大标识符为65535。这里标识符与文件描述符有所不同,使用open函数打开一个文件时,返回的文件描述符的值为当前进程最小可用的文件描述符数组的下标。IPC对象删除或创建时相应的标识符的值会不断增加到最大的值,归零循环分配使用。

IPC的标识符只解决了内部访问一个IPC对象的问题,如何让多个进程都访问某一个特定的IPC对象还需要一个外部键(key),每一个IPC对象都与一个键相关联。这样就解决了多进程在一个IPC对象上汇合的问题。创建一个IPC对象时需要指定一个键值,类型为key_t,键值到标识符的转换是由系统内核来维护的。当有了一个IPC对象的键值,如何让多个进程知道这个键,可以有多种实现的办法。

  • 可以使用文件来做中间的通道,创建IPC对象进程,使用键IPC_PRIVATE成功建立IPC对象之后,将返回的标识符存储在一个文件中。其他进程通过读取这个标识符来引用IPC对象通信。
  • 定义一个多个进程都认可的键,每个进程使用这个键来引用IPC对象,值得注意的是,创建IPC对象的进程中,创建IPC对象时如果该键值已经与一个IPC对象结合,则应该删除该IPC对象,再创建一个新的IPC对象。
  • 多进程通信中,对于指定键引用一个IPC对象而言,可能不具有拓展性,并且在该键值已经被一个IPC对象结合的情况下。所以必须删除这个存在对象之后再建立一个新的。这有可能影响到其他正在使用这个对象的进程。

ipc_perm结构体:

系统为每一个IPC对象保存一个ipc_perm结构体,该结构说明了IPC对象的权限和所有者,每一个版本的内核各有不用的ipc_perm结构成员。

struct ipc_perm{
key_t _key;     //key supplied to xxxget(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
};

注意:

只有超级用户或者创建IPC对象的进程有权改变ipc_perm结构的值。结构中的mode域类似于文件的stat结构的mode域,但是不可以有执行权限。


消息队列结构

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 inqueue (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) */

};

注意:消息队列会一直存在,除非手动删除(ipcrm),或者就只有内核重启了。

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

这里写图片描述


消息队列函数:


1、msgget函数

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

原型:

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

int msgget(key_t key,int msgflg);

参数:

  • key:某个消息队列的名字,唯一的身份标识。
  • msgflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的。
  • 返回值:成功返回一个非负整数,即该消息队列的标志码;失败返回-1。

【注】:

ipcs -q [msqid]     //通过句柄msqid可以查看消息队列对象都有什么

这里写图片描述


2、msgctl函数

功能:消息队列的控制函数

原型:

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

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

参数:

  • msqid:由msgget函数返回的消息队列标志码。
  • cmd:是将要采取的动作,有三个可取值。
  • buf:消息队列结构
  • 返回值 :成功返回0,失败返回-1。

cmd:将要采取的动作(有三个可取值):

命令说明
IPC_STAT把msqid_ds结构中的数据设置为消息队列的当前关联值
IPC_SET在进程有足够权限的前提下,把消息队列的当前关联值设置为msqid_ds数据结构中给出的值
IPC_RMID删除消息队列

3、msgsnd函数

功能:把一条消息添加到消息队列中

原型:

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

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

参数:

  • magid:由msgget函数返回的消息队列标识码。
  • msgp:是一个指针,指针指向准备发送的消息,发送的消息必须是一个结构体。
  • msgsz:是msgp指向的消息长度,这个长度不含保存消息类型的那个long int 长整型。
  • msgflg:控制着当前消息队列满或达到系统上限时将要发生的事情,一般默认不用管(0)。
  • msgflg = IPC_NOWAIT 表示队列满不等待,返回EAGAIN错误。
  • 返回值:成功返回0;失败返回-1。

说明:

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

  • 首先,它必须小于系统规定的上限值;
  • 其次,它必须以一个long int 长整数开始,接收者函数将利用这个长整数确定消息的类型。

消息结构参考形式如下:

struct msgbuf{
    long mtype; //消息的类型,且mtype > 0
    char mtext[1];
    }

4、msgrcv函数:

功能:是从一个消息队列接收消息

原型:

#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);

参数:

  • msgid:由msgget函数返回的消息队列标识码。
  • msgp:是一个指针,指针指向准备接收的消息。
  • msgsz:是msgp指向的消息长度,这个长度不含保存消息类型的那个long int 长整型。
  • msgtype:它可以实现接收优先级的简单形式。
  • msgflg:控制着队列中没有相应类型的消息可供接受时将要发生的事情。
  • 返回值:成功返回实际放到接受缓冲区里去的字符个数;失败返回-1。

说明:

  • msgtype=0返回队列第⼀条信息。
  • msgtype>0返回队列第⼀条类型等于msgtype的消息。
  • msgtype<0返回队列第⼀条类型⼩于等于msgtype绝对值的消息,并且是满⾜条件的消息类型最⼩的消息。
  • msgflg=IPC_NOWAIT,队列没有可读消息不等待,返回ENOMSG错误。
  • msgflg=MSG_NOERROR,消息⼤⼩超过msgsz时被截断。
  • msgtype>0且msgflg=MSG_EXCEPT,接收类型不等于msgtype的第⼀条消息。

消息队列与命名管道的比较

相同点:

  • 同命名管道一样,消息队列进行通信的进程可以是不相关的进程,同时它们都是通过发送和接收的方式来传递数据的。
  • 不管是命名管道还是消息队列,它们对每个数据都有一个最大长度的限制。

不同点:

  • 在命名管道中,发送数据用write,接收数据用read,而在消息队列中,发送数据用msgsnd,接收数据用msgrcv。

与命名管道相比,消息队列的优势在于:

  • 消息队列也可以独立于发送和接收进程而存在,从而消除了在同步命名管道的打开和关闭时可能产生的困难。
  • 同时通过发送消息还可以避免命名管道的同步和阻塞问题,不需要由进程自己来提供同步方法。
  • 接收程序可以通过消息类型有选择地接收数据,而不是像命名管道中那样,只能默认地接收。

消息队列小结

  • 双向通信
  • 用于随意进程
  • 面向数据块(一个个节点)
  • 自带同步互斥
  • 生命周期随内核
  • 消息队列是队列,但是区分类型的(在同类型中支持先进先出原则)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值