一、消息队列是什么
消息队列提供了从一个进程向另外一个进程发送一块数据的方法,每个数据块都有相应的类型,接收者进程接收的数据块可以由不同的类型值。
消息队列和管道的不同之处,是消息队列是基于消息的,而管道是基于字节流的。
消息队列和命名管道一样有缺陷:
- 每个消息的最大长度是由上线的。
- 每个消息队列的总字节数是有上限的。
- 系统上消息队列的总数也是有上限的。
这个上限值我们可以分别在proc/sys/kcrncl/msgmax、proc/sys/kcrncl/msgmnb、proc/sys/kcrncl/msgmni里面找到相应的内容。
消息队列不靠引用计数,是个设计上的缺陷,如果有别人要删除这个消息队列,此时另外一个正在写的直接被删除了。
二、IPC数据结构
每个进程间通信中都有如下数据结构,这个数据结构在/usr/include/linux/ipc.h中
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 */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions */
unsigned short __seq; /* Sequence number */
};
首先IPC 对象的关键字,uid 和gid。然后是IPC 对象的创建者的 uid 和gid。接下来的是IPC 对象的存取权限。最后一个成员也许有点难于理解,不过不要担心,这是系统保存的IPC 对象的使用频率信息,我们完全可以不去理会它。
三、消息队列结构
struct msqid_ds {
struct ipc_perm msg_perm;
struct msg *msg_first; /* first message on queue,unused */
struct msg *msg_last; /* last message in queue,unused */
__kernel_time_t msg_stime; /* last msgsnd time */
__kernel_time_t msg_rtime; /* last msgrcv time */
__kernel_time_t msg_ctime; /* last change time */
unsigned long msg_lcbytes; /* Reuse junk fields for 32 bit */
unsigned long msg_lqbytes; /* ditto */
unsigned short msg_cbytes; /* current number of bytes on queue */
unsigned short msg_qnum; /* number of messages in queue */
unsigned short msg_qbytes; /* max number of bytes on queue */
__kernel_ipc_pid_t msg_lspid; /* pid of last msgsnd */
__kernel_ipc_pid_t msg_lrpid; /* last receive pid */
};
我们可以看到消息队列的结构msgid_ds中第一个结构就是ipc_perm,后面都是它的私有成员。
消息队列是用链表实现的,如下图:
四、有关消息队列函数
(1)msgget函数
我们在Linux下man msgget查看有关msgget函数的信息。
msgget函数有创建和打开的意思。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
- 返回值
返回值是个id相当于文件描述符,失败返回-1。 - 参数
- key:某个消息队列的名称。相当于文件名,产生的方式有三种
方法一:直接写key,例如1234相当于写死
方法二:IPC_PROVATE生成一个私有的消息队列
方法三:用ftok()函数生成。 - msgflg:九个权限标志组成
- key:某个消息队列的名称。相当于文件名,产生的方式有三种
ftok()函数
在key的第三种生成方式中指出了用ftok函数进行生成,下面讲解下ftok函数
int ftok(char* path ,int pro_id)
- 参数
path:第一个参数是一个路径(是系统必须存在的路径)。pro_id:第二个参数是一个id(低八位必须非0).
唯一产生一个id. - 返回值
这个函数的返回值是一个key,这个key分为前八位中间八位和后16位、前8位是id的低八位,中间8位是st的低八位,是次设备号,后16位是inode的低16位。
flg相当于相应的权限,可设置的参数有IPC_CREAT等,权限可以或。
例:
#include <unistd.h>
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <string.h>
int main()
{
//此处采用了ftok函数生成key
int fd = msgget(ftok(".",'a'),IPC_CREAT|0644);
if(fd == -1 )
perror("msgget"),exit(1);
printf("ok\n");
}
运行结果如图:
次设备号 ls -l /dev/sda2
(2)msgctl函数
msgctl是消息队列的控制函数
- 函数原型
int msgctl(int msqid, int cmd, struct msqid_ds *buf); - 返回值
操作成功返回0,失败返回-1。 - 参数
msqid:由msgget函数返回的消息队列标识符
cmd:是将要采取的动作,参数如下
- IPC_RMID:删除消息队列
- IPC_SET:在由权限的情况下,把消息队列的关联值设置为msqid_ds的数据结构中给的值。
- IPC_STAT:把msqid_ds结构中的数据设置为消息队列当前值
(3)msgsnd函数
向消息队列中发送数据
msgsnd
- 函数原型
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
- 返回值:
成功返回0,失败返回-1。 - 参数:
- msqid:由msgget函数返回的消息队列标识码。
- msgp:第二个参数指向准备发送的消息地址,必须是一种特殊的格式,是一个结构体,第一个参数是消息的类型,必须是大于0,第二个参数是真正的消息数据。
- msgsz:第三个参数是通道的大小,是不包含channel字段的
- msgflg:最后一个参数一般填写0,一般常用的是IPC_NOWAIT,如果加了,消息队列写满了会返回错误,errno=EAGAIN
备注:如下是第二个参数的结构体。
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[1]; /* message data */
};
(4)msgrcv函数
向消息队列中读取数据
msgrcv
- 函数原型
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
- 参数
- msqid:由msgget函数返回的消息队列标识码。
- msgtype:从指定的通道收取数据。如果大于0则指定的通道收取数据,如果等于0,则返回消息队列中的第一条,如果小于0,会读取小于等于这个数的绝对值,并且先读最小的。
- msgflg:flag一般写0,还可以写IPC_NOWAIT
五、删除创建的消息队列的方法
(1)手动删除
- 命令:
ipcrm - 注意:
如果根据KEY来删除,则在命令后面要跟上大Q,如果根据id来删除命令后面是用小q
[root@localhost day10]# ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
0xffffffff 0 root 644 0 0
0x61020113 32769 root 644 0 0
[root@localhost day10]# ls -id
131347 .
[root@localhost day10]# ls -l /dev/sda2
brw-rw----. 1 root disk 8, 2 12月 29 23:31 /dev/sda2
[root@localhost day10]# ipcrm -Q 0xffffffff
[root@localhost day10]# ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
0x61020113 32769 root 644 0 0
(2)函数删除
根据函数来删除的方法是选用msgctl()函数
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
如果是要删除,第二个参数填IPC_RMID,第三个参数填0,因为无意义
例子:
#include <unistd.h>
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int id = msgget(ftok(".",'a'),0);
if(id == -1 )
perror("msgget"),exit(1);
msgctl(id,IPC_RMID,0);
}
运行结果如下:
[root@localhost day10]# ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
0x61020113 32769 root 644 0 0
[root@localhost day10]# ./a.out
[root@localhost day10]# ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages