前言
消息队列、共享内存和信号量被统称为system-V IPC,V 是罗马数字5,是Unix 的AT&T 分支的其中一个版本,一般习惯称呼他们为IPC 对象,这些对象的操作接口都比较类似,在系统中他们
都使用一种叫做key 的键值来唯一标识,而且他们都是"持续性"资源------即他们被创建之后,
不会因为进程的退出而消失,而会持续地存在,除非调用特殊的函数或者命令删除他们。
Linux 的IPC 对象(包括消息队列、共享内存和信号量)在内核内部使用链表维护,不同的对象使
用IPC 标识符来标识,如消息队列标识符msqid、共享内存标识符shmid,信号量标识符semid。
1:消息队列的概念
消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构。我们可以通过发送消息来避免命令通道的同步和阻塞问题。
2:消息队列与信号、管道的区别
消息队列与信号的对比:
• 信号承载的信息量少,而消息队列可以承载大量自定义的数据。
消息队列与管道的对比:
• 消息队列跟命名管道有不少的相同之处,它与命名管道一样,消息队列进行通信的进程可
以是不相关的进程,同时它们都是通过发送和接收的方式来传递数据的。在命名管道中,发
送数据用write(),接收数据用read(),则在消息队列中,发送数据用msgsnd(),接收数据用
msgrcv(),消息队列对每个数据都有一个最大长度的限制。
• 消息队列也可以独立于发送和接收进程而存在,在进程终止时,消息队列及其内容并不会
被删除。
• 管道只能承载无格式字节流,消息队列提供有格式的字节流,可以减少了开发人员的工作
量。
• 消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级,接收程序可以通
过消息类型有选择地接收数据,而不是像命名管道中那样,只能默认地接收。
• 消息队列可以实现消息的随机查询,消息不一定要以先进先出的顺序接收,也可以按消息
的类型接收。
消息队列的实现包括创建或打开消息队列、发送消息、接收消息和控制消息队列这4 种操作。
3:消息队列的API函数
Linux 内核提供了一系列函数来使用消息队列:
• 其中创建或打开消息队列使用的函数是msgget(),这里创建的消息队列的数量会受到系统
可支持的消息队列数量的限制;
• 发送消息使用的函数是msgsnd() 函数,它把消息发送到已打开的消息队列末尾;
• 接收消息使用的函数是msgrcv(),它把消息从消息队列中取走,与FIFO 不同的是,这里可
以指定取走某一条消息;
• 最后控制消息队列使用的函数是msgctl(),它可以完成多项功能。
3.1 msgget()
msgget()函数的作用是创建或获取一个消息队列对象,并返回消息队列标识符。
cpp
int msgget(key_t key, int msgflg);
若执行成功返回队列ID,失败返回-1。它的两个输入参数说明如下:
• key:消息队列的关键字值,多个进程可以通过它访问同一个消息队列。例如收发进程都使
用同一个键值即可使用同一个消息队列进行通讯。其中有个特殊值IPC_PRIVATE,它用于
创建当前进程的私有消息队列。
• msgflg:表示创建的消息队列的模式标志参数,主要有IPC_CREAT,IPC_EXCL 和权限mode。
该函数可能返回以下错误代码:
-- EACCES:指定的消息队列已存在,但调用进程没有权限访问它
-- EEXIST:key 指定的消息队列已存在,而msgflg 中同时指定IPC_CREAT 和IPC_EXCL
标志
-- ENOENT:key 指定的消息队列不存在同时msgflg 中没有指定IPC_CREAT 标志
-- ENOMEM:需要建立消息队列,但内存不足
-- ENOSPC:需要建立消息队列,但已达到系统的限制
3.2 msgsend() 和 msgrcv()
这个俩函数的主要作用就是将消息写入到消息队列和消息队列接收消息。
函数原型:
cpp
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
参数说明:
• msqid:消息队列标识符。
• msgp :发送给队列的消息。msgp 可以是任何类型的结构体,但第一个字段必须为long 类
型,即表明此发送消息的类型,msgrcv() 函数则根据此接收消息。msgp 定义的参照格式如下:
cpp
/*msgp 定义的参照格式*/
struct s_msg{
long type; /* 必须大于0, 消息类型*/
char mtext[1]; /* 消息正文,可以是其他任何类型*/
} msgp;
• msgsz:要发送消息的大小,不包含消息类型占用的4 个字节,即mtext 的长度。
• msgflg:如果为0 则表示:当消息队列满时,msgsnd() 函数将会阻塞,直到消息能写进
消息队列;如果为IPC_NOWAIT 则表示:当消息队列已满的时候,msgsnd() 函数不等
待立即返回;如果为IPC_NOERROR:若发送的消息大于size 字节,则把该消息截断,
截断部分将被丢弃,且不通知发送进程。
• 返回值:如果成功则返回0,如果失败则返回-1,并且错误原因存于error 中。错误代码:
-- EAGAIN:参数msgflg 设为IPC_NOWAIT,而消息队列已满。
-- EIDRM:标识符为msqid 的消息队列已被删除。
-- EACCESS:无权限写入消息队列。
-- EFAULT:参数msgp 指向无效的内存地址。
-- EINTR:队列已满而处于等待情况下被信号中断。
-- EINVAL:无效的参数msqid、msgsz 或参数消息类型type 小于0
cpp
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
参数说明:
• msqid :消息队列标识符。
• msgp :存放消息的结构体,结构体类型要与msgsnd() 函数发送的类型相同。
• msgsz :要接收消息的大小,不包含消息类型占用的4 个字节。
• msgtyp : 有多个可选的值:如果为0 则表示接收第一个消息,如果大于0 则表示接收类型等于msgtyp 的第一个消息,而如果小于0 则表示接收类型等于或者小于msgtyp 绝对值的第一个消息。
• msgflg: 用于设置接收的处理方式,取值情况如下:
-- 0: 阻塞式接收消息,没有该类型的消息msgrcv 函数一直阻塞等待
-- IPC_NOWAIT:若在消息队列中并没有相应类型的消息可以接收,则函数立即返回,
此时错误码为ENOMSG
-- IPC_EXCEPT:与msgtype 配合使用返回队列中第一个类型不为msgtype 的消息
-- IPC_NOERROR:如果队列中满足条件的消息内容大于所请求的size 字节,则把该消
息截断,截断部分将被丢弃
• 返回值:msgrcv() 函数如果接收消息成功则返回实际读取到的消息数据长度,否则返回-1,
错误原因存于error 中。错误代码:
-- E2BIG:消息数据长度大于msgsz 而msgflag 没有设置IPC_NOERROR
-- EIDRM:标识符为msqid 的消息队列已被删除
-- EACCESS:无权限读取该消息队列
-- EFAULT:参数msgp 指向无效的内存地址
-- ENOMSG:参数msgflg 设为IPC_NOWAIT,而消息队列中无消息可读
-- EINTR:等待读取队列内的消息情况下被信号中断
3.3 msgctl()
消息队列是可以被用户操作的,比如设置或者获取消息队列的相关属性,那么可以通过msgctl()
函数去处理它。函数原型:
cpp
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
参数说明:
• msqid:消息队列标识符。
• cmd 用于设置使用什么操作命令,它的取值有多个:
-- IPC_STAT 获取该MSG 的信息,获取到的信息会储存在结构体msqid_ds 类型的buf 中。
-- IPC_SET 设置消息队列的属性,要设置的属性需先存储在结构体msqid_ds 类型的buf
中,可设置的属性包括:msg_perm.uid、msg_perm.gid、msg_perm.mode及 msg_qbytes,
储存在结构体msqid_ds 中。
-- IPC_RMID 立即删除该MSG,并且唤醒所有阻塞在该MSG 上的进程,同时忽略第三
个参数。
-- IPC_INFO 获得关于当前系统中MSG 的限制值信息。
-- MSG_INFO 获得关于当前系统中MSG 的相关资源消耗信息。
-- MSG_STAT 同IPC_STAT,但msgid 为该消息队列在内核中记录所有消息队列信息的
数组的下标,因此通过迭代所有的下标可以获得系统中所有消息队列的相关信息。
• buf:相关信息结构体缓冲区。
-- 返回值:
-- 成功:0
-- 出错:-1,错误原因存于error 中,错误代码:
∗ EACCESS:参数cmd 为IPC_STAT,确无权限读取该消息队列。
∗ EFAULT:参数buf 指向无效的内存地址。
∗ EIDRM:标识符为msqid 的消息队列已被删除。
∗ EINVAL:无效的参数cmd 或msqid。
∗ EPERM:参数cmd 为IPC_SET 或IPC_RMID,却无足够的权限执行。