基本介绍
- 支持不同进程之间以消息(messages)的形式进行数据交换,消息能够拥有自己的标识,且内核使用链表方式进行消息管理。
- 进程之间的通信角色为:发送者和接受者
发送者:
a. 获取消息队列的ID(key或者msgid)
b. 将数据放入一个带有标识的消息结构体,发送到消息队列
接受者:
a. 获取消息队列的ID
b. 指定标识的消息从消息队列中读出,然后进一步后续处理 - 支持不同的进程标记不同的消息类型(1,2,3…),并由内核态维护对应消息类型的链表。
- 内核态的0号消息类型维护了一个链表,用来保存按照时间顺序加入的消息
编程接口
-
生成ipc对象的唯一标识
key
的接口
a. 头文件<sys/ipc.h>
b. 函数声明key_t ftok(const char *path, int id);
c. 参数描述
path
需指定一个已经存在的可访问的文件
id
为用户可自由指定的id -
创建或者打开一个消息队列,并获取system V 消息队列中消息的身份标识
a. 头文件<sys/types.h> <sys/ipc.h> <sys/msg.h>
b. 函数声明int msgget (ket_t key, int msgflg)
c. 参数描述
key
为ipc对象的唯一标识,生成的消息身份标识与该参数相关
msgflg
当该函数没有搜索到系统中与key值对应的消息队列,则msgflg会指定IPC_CREAT,创建一个队列,并返回消息标识
d. 返回值:成功返回消息身份标识,失败返回-1 -
发送消息到消息队列
a. 头文件<sys/types.h> <sys/ipc.h> <sys/msg.h>
b. 函数声明int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
c. 参数描述
msqid
消息标识,类似于文件描述符fd
msgp
消息内容指针
msgsz
消息大小
msgflg
当出现消息队列没有足够的可用空间时,可以通过设置msgflg为IPC_NOWAIT
来让发送函数不产生阻塞,返回失败
d. 返回值 失败返回-1,以及对应失败码;成功则返回0 -
从消息队列中接收消息
a. 头文件<sys/types.h> <sys/ipc.h> <sys/msg.h>
b. 函数声明ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
c. 参数描述
msqid
消息标识,类似于文件描述符fd
msgp
消息内容指针
msgsz
消息大小,同时也是当前接收的消息最大能够接收的大小
msgflg
当出现实际的消息内容大于设定的msgsz,可以通过MSG_NOERROR
来将消息裁剪为msgsz
大小进行获取,否则会返回失败。而消息仍然存在于消息队列。
msgtyp
如果是0,则会读取处于消息队列的第一个消息;大于0,则会读取对应type处于消息队列中的第一个消息;如果小于0,则会读取type绝对值或者小于绝对值的消息队列的第一个消息。
d. 返回值 失败返回-1,成功返回对应消息的大小 -
控制消息队列的各个操作
a. 头文件<sys/types.h> <sys/ipc.h> <sys/msg.h>
b. 函数声明int msgctl(int msqid, int cmd, struct msqid_ds *buf);
c. 参数描述
msgqid
消息标识
cmd
针对消息标识的操作,合法的操作如下:IPC_STAT
获取msgqid的消息对象的信息,将各个属性从内核拷贝到一个临时的数据结构msgqid_ds
类型的buf;调用者需要对消息队列有读权限IPC_SET
自己可以通过临时的msgqid_ds
来设置内核中消息的对应msgqid_ds
的属性IPC_RMID
立即移除消息队列;当前调用者需要拥有 消息队列的所有者权限,或者高于所有者的权限(root)IPC_INFO
返回消息队列的参数限制
其他标识可以通过
man msgctl
来查看
代码实例
消息队列的发送和接收
发送端msg_snd.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define MSG_TYPE1 1
#define MSG_TYPE2 2
struct msgbuf
{
long mtype;
char mtext[100];
};
int main()
{
//当多用户的时候通过指定文件以及设置id来获取唯一的key标识
//key_t key = ftok(".",100);
key_t key = 12345; //个人使用的时候可以直接指定key
//创建msg_qid的对象
int msg_qid = msgget(key, IPC_CREAT | 0666);
struct msgbuf msg;
memset(&msg, 0 , sizeof(msg));
//初始化消息类型以及消息内容
msg.mtype = MSG_TYPE2;
strncpy(msg.mtext, "hello world" , 80);
//发送消息到消息标识的msg_qid IPC 对象中
if( -1 == msgsnd(msg_qid,(void *)&msg,strlen(msg.mtext),0)) {
printf("send msg failed\n");
_exit(-1);
}
return 0;
}
编译运行:
gcc msg_snd.c -o msg_snd
运行前查看系统消息队列
ipcs -q
[root@node1 ~]# ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
0x000004d2 0 root 666 0 0
运行后,可以看到我们发送了消息队列的各个属性信息。关于key值,它为我们设置的12345的16进制数值
[root@node1 ~]# ./msg_snd
[root@node1 ~]# ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
0x000004d2 0 root 666 0 0
0x00003039 65538 root 666 11 1
接收端msg_rcv.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define MSG_TYPE1 1
#define MSG_TYPE2 2
struct msgbuf
{
long mtype;
char mtext[100];
};
int main()
{
key_t key = 12345;
int msg_qid = msgget(key, IPC_CREAT | 0666);
struct msgbuf msg;
memset(&msg, 0 , sizeof(msg));
if (-1 == msgrcv(msg_qid,(void *)&msg,sizeof(msg.mtext),MSG_TYPE2,0)) {
printf("receive msg failed\n");
_exit(-1);
}
printf("%s\n",msg.mtext);
//当完成接收之后从消息队列中删除当前消息
//msgctl(msg_id,IPC_RMID,NULL);
return 0;
}
编译运行,可以看到已经接手到了我们之前发送的内容:
[root@node1 ~]# gcc msg_rcv.c -o msg_rcv
[root@node1 ~]# ./msg_rcv
hello world
查看消息队列情况,消息队列中的数据已经被接收,所以在used-bytes和messages中看不到消息内容了,但是没有删除该消息队列,所以消息标识仍然存在。我们可以在上述代码中加入msgctl
:
[root@node1 ~]# ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
0x000004d2 0 root 666 0 0
0x00003039 65538 root 666 0 0
消息队列中的消息对象的属性控制
控制代码msg_ctl.c
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
其中主要控制的是消息队列一个数据结构,可以通过man msgctl
查看 msqid_ds
结构体
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 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) */
};
以下代码为主要控制参数的代码,通过msgctl的cmd参数进程控制
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int main()
{
key_t key = 12345;
int msg_id = msgget(key,IPC_CREAT|0666);
struct msqid_ds info;
//第一次先从已存在的12345的消息中获取队列状态
if (-1 == msgctl(msg_id,IPC_STAT,&info)) {
printf("control msg failed\n");
_exit(-1);
}
//打印各个状态信息
printf("uid :%d,gid:%d,cuid:%d,cgid:%d\n",\
info.msg_perm.uid,info.msg_perm.gid,info.msg_perm.cuid,info.msg_perm.cgid);
printf("mode:%o3o,cbytes:%lu,qnum:%lu,qbytes:%lu\n",\
info.msg_perm.mode & 0777,info.__msg_cbytes,info.msg_qnum,info.msg_qbytes);
//尝试设置消息队列允许的最大字节内容
info.msg_qbytes = 16000;
//通过cmd为IPS_SET的标记进行设置
if (-1 == msgctl(msg_id, IPC_SET, &info)) {
printf("ipc_set failed\n");
_exit(-1);
}
if (-1 == msgctl(msg_id, IPC_STAT, &info)) {
printf("ipc_stat failed\n");
_exit(-1);
}
printf("mode:%o3o,cbytes:%lu,qnum:%lu,qbytes:%lu\n",\
info.msg_perm.mode & 0777,info.__msg_cbytes,info.msg_qnum,info.msg_qbytes);
return 0;
}
输出如下,可以看到我们控制的队列允许的最大字节内容msg_qbytes已经设置进去:
[root@node1 ~]# ./msg_ctl
uid :0,gid:0,cuid:0,cgid:0
mode:6663o,cbytes:11,qnum:1,qbytes:16000
mode:6663o,cbytes:11,qnum:1,qbytes:16000