说明:
本文章旨在总结备份、方便以后查询,由于是个人总结,如有不对,欢迎指正;另外,内容大部分来自网络、书籍、和各类手册,如若侵权请告知,马上删帖致歉。
QQ 群 号:513683159 【相互学习】
内容来源:
《Linux系统编程》、《Linux网络编程》、《Unix环境高级编程》
目录:
消息队列(Message Queue)
消息队列是内核地址空间中的内部链表,通过Linux内核在各个进程之间传递内容。
消息顺序地发送到消息队列中,并以几种不同的方式从队列中获取,每个消息队列可以用IPC标识符唯一的进行标识。内核中的消息队列是通过IPC的标识符来区别的,不同的消息队列之间是相对独立的。每个消息队列中的消息,又构成一个独立的链表。
一、结构体
(1)消息缓冲区结构
常用结构msgbuf
位于:linux/msg.h
头文件中。
/* message buffer for msgsnd and msgrcv calls */
struct msgbuf {
__kernel_long_t mtype; // 消息类型,以正数来表示
char mtext[1]; // 详细数据
};
PS:
①mtype
:用户可给某消息设定一个类型,可在消息队列中正确地发送和接收自己的消息。
②mtext
:用户构建自己消息结构时,并不一定为char
或长度为1
可依据实际需求构建。
③除以上变量外还可添加其他变量,但消息总大小不能超过8192字节,这是由Linux/msg.h
中定义的MSGMAX
决定。
#define MSGMAX 8192 /* <= INT_MAX */ /* max size of message (bytes) */
(2)msqid_ds 结构
每个队列都有一个msqid_ds
结构与其相关联,此结构规定了队列的当前状态。
位于:linux/msg.h
头文件中。
/* Obsolete, used only for backwards compatibility and libc5 compiles */
struct msqid_ds {
struct ipc_perm msg_perm; /* 用于存放消息队列的许可权限信息,其中包括访问许可信息,队列创建者有关信息*/
struct msg *msg_first; /* 队列上的第一个消息,未使用 */
struct msg *msg_last; /* 队列中的最后一条消息,未使用 */
__kernel_time_t msg_stime; /* 最后msgsnd时间,发送到队列的最后一个消息的时间戳 */
__kernel_time_t msg_rtime; /* 最后msgrcv时间,从队列获取的最后一个消息的时间戳 */
__kernel_time_t msg_ctime; /* 最后更改时间,对队列进程最后一次变动的时间戳 */
unsigned long msg_lcbytes; /* 重用垃圾字段为32位 */
unsigned long msg_lqbytes; /* 同上 */
unsigned short msg_cbytes; /* 当前队列上的字节数 */
unsigned short msg_qnum; /* 队列中的消息数 */
unsigned short msg_qbytes; /* 队列上的最大字节数 */
__kernel_ipc_pid_t msg_lspid; /* 发送最后一个消息进程的PID */
__kernel_ipc_pid_t msg_lrpid; /* 接收最后一个消息进程的PID */
};
(3)ipc_perm结构
该结构中mode成员按flag中的相应权限位设置
位于:linux/ipc.h
头文件中。
/* 过时,仅用于向后兼容和libc5编译 */
struct ipc_perm
{
__kernel_key_t key; /* 函数msgget()使用的键值,用于区分消息队列 */
__kernel_uid_t uid; /* 用户者的UID */
__kernel_gid_t gid; /* 用户者的GID */
__kernel_uid_t cuid; /* 建立者的UID */
__kernel_gid_t cgid; /* 建立者的GID */
__kernel_mode_t mode; /* 权限 */
unsigned short seq; /* 序列号 */
};
二、内核中的消息队列关系
在消息的发送和接收的时候,内核通过一个比较巧妙的设置来实现消息插入队列的动作和从消息中查找消息的算法。
结构list_head
形成一个链表,而结构msg_msg
之中的m_list
成员是一个struct list_head
类型的变量,通过此变量,消息形成了一个链表,在查找和插入时,对m_list
域进行偏移操作就可以找到对应的消息体位置。内核中的代码在头文件<linux/msg.h>
和<linux/msg.c>
中,主要的实现是插入消息和取出消息的操作。
三、函数简介
(1)ftok()——键值构建函数
1.函数说明:将路径名和项目ID转变为一个系统V的IPC键值
项目 | 说明 |
---|---|
函数原型 | key_t ftok(const char *pathname, int proj_id); |
头文件 | sys/types.h、sys/ipc.h |
参数说明 | pathname:已存在的路径名 |
proj_id:8位值 (通常用a,b等表示) | |
返回值 | 若成功则返回键值 若失败则返回-1 |
注意 |
(2)msgget()——获取消息函数
1.函数说明:创建一个新队列 或 打开一个现有队列)。
项目 | 说明 |
---|---|
函数原型 | int msgget(key_t key, int msgflg); |
头文件 | sys/types.h、sys/ipc.h、sys/msg.h |
参数说明 | key:键值(可用flok函数生成) 拿来与内核其他消息队列关键字相比较,打开或访问操作依赖于msgflg参数内容 |
msgflg:参数说明如下: | |
返回值 | 若成功则返回消息队列ID, 若失败则返回-1 |
注意 |
2.msgflg参数:
①IPC_CREAT
:如果在内核中不存在该队列,则创建它。
②IPC_EXCL
:当与IPC_CREAT
一起使用时,如果队列早已存在则将出错。
如果只使用了IPC_CREAT
,msgget()
函数或者返回新创建消息队列的消息队列标识符,或者会返回现有的具有同一个关键字值的队列的标识符。
如果同时使用了IPC_EXCL
和IPC_CREAT
,那么将可能会有两个结果:
或者创建一个新的队列;
或者如果该队列存在,则调用将出错,并返回一1。
IPC_EXCL
本身是没有什么用处的,但在与IPC_CREAT
组合使用时,它可以用于保证没有一个现存的队列为了访问而被打开。
(3)msgsnd()——发送消息函数
1.函数说明:获取队列标识符后,用于向队列传递消息。
项目 | 说明 |
---|---|
函数原型 | int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg); |
头文件 | sys/types.h、sys/ipc.h、sys/msg.h |
参数说明 | msqid:队列标识符 (msgget()获取的返回值) |
msgp:指向区msgbuf结构体的消息缓冲 | |
msgsz:消息的大小(字节单位) 其中不包括消息类型的长度(4字节) | |
msgflg:标记变量 | |
返回值 | 若失败则返回-1 |
注意 |
2.msgflg值:
①设置0
:忽略
②设置IPC_NOWAIT
:若队列满则不会写入,立即报错返回EAGAIN
③未设置IPC_NOWAIT
:调用进程将被中断(阻塞),以下情况为止
1.有空间可容纳要发送的消息。
2.从系统中删除此队列,返回EIDRM
3.捕捉到一个信号并从信号处理程序返回,返回EINTR
。
(4)msgrcv()——接收消息函数忽略
1.函数说明:获取队列标识符后,用于从队列取用消息
项目 | 说明 |
---|---|
函数原型 | ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg); |
头文件 | sys/types.h、sys/ipc.h、sys/msg.h |
参数说明 | msqid:队列标识符 (msgget()获取的返回值) |
msgp:指向消息缓冲区 存放获取的消息 | |
msgsz:消息缓冲区的大小(字节单位) 其中不包括mtype成员的长度(4字节) | |
msgtyp:指定队列中获取的消息类型 | |
msgflg:标记变量 | |
返回值 | 若失败则返回-1 |
注意 |
2.msgtyp变量:
type == 0
返回队列中的第一个消息。
type > 0
返回队列中消息类型为type的第一个消息。
type < 0
返回队列中消息类型值小干或等于type绝对值的消息,如果这种消息有若干个,则取类型值最小的消息。
type值非0
用于以非先进先出次序读消息。
(5)msgctl()——消息控制函数
1.函数说明:在一个msqid
消息队列上执行cmd
指定的控制操作,类似于ioctl()
项目 | 说明 |
---|---|
函数原型 | int msgctl(int msqid, int cmd, struct msqid_ds *buf); |
头文件 | sys/types.h、sys/ipc.h、sys/msg.h |
参数说明 | msqid:队列标识符 (msgget()获取的返回值) |
cmd:命令 | |
buf:应用层和内核空间进行数据交换的指针 | |
返回值 | 若失败则返回-1 |
注意 |
2.cmd值:(也可用于信号量和共享存储)
①IPC_STAT
: 获取 队列的msqid_ds
结构信息,存放在buf
变量所指定的地址中。(调用方必须具有消息队列的读权限)
通过这种方式,应用层可以获得当前消息队列的设置情况,如:是否有消息到来、消息队列的缓冲区设置等。
②IPC_SET
: 设置 队列的msqid_ds
结构的ipc _perm
成员值,它是从 buf
变量所指定的地址中取得该值的,并更新其msg_ctime
成员。
通过IPC_SET
命令,应用层可以设置消息队列的状态,例如修改消息队列的权限,使其他用户可以访问或者不能访问当前的队列;甚至可以设置消息队列的某些当前值来伪装。
③IPC_RMID
: 删除 队列以及仍在该队列的所有数据。
使用此命令执行后,内核会把此消息队列从系统中删除,唤醒所有等待的读写进程(返回一个错误,errno
设置为EIDRM
)。
四、示例实践:
1.功能简介:
主函数先用函数ftok()使用路径“/ipc/msg/b
”获得一个键值,之后进行相关的操作并打印消息的属性。
调用函数msgget()
获得一个消息后,打印消息的属性;
调用函数msgsnd()
发送一个消息后,打印消息的属性;
调用函数msgrcv()
接收一个消息后,打印消息的属性;
调用函数msgctl()
并发送命令IPC_SET
设置一个消息后,打印消息的属性;
调用函数msgctl()
并发送命令IPC_RMID
销毁消息队列。
2.源代码:mq.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/msg.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <time.h>
/* 打印消息属性的函数 */
void msg_show_attr(int msg_id, struct msqid_ds msg_info)
{
int ret =-1;
sleep(1);
ret = msgctl(msg_id, IPC_STAT, &msg_info); //获取消息
if(ret == -1) //获取消息失败
{
printf("获得消息信息失败\n");
return;
}
printf("\n");
/* 以下打印消息的信息 */
printf("现在队列中的字节数:%ld\n", msg_info.msg_cbytes); //消息队列中的字节数
printf("队列中消息数:%ld\n",msg_info.msg_qnum); //消息队列中的消息数
printf("队列中最大字节数:%ld\n",msg_info.msg_qbytes); //消息队列中的最大字节数
printf("最后发送消息的进程pid:%d\n",msg_info.msg_lspid); //最后发送消息的进程
printf("最后接收消息的进程pid:%d\n",msg_info.msg_lrpid); //最后接收消息的进程
printf("最后发送消息的时间:%s",ctime(&(msg_info.msg_stime))); //最后发送消息的时间
printf("最后接收消息的时间:%s",ctime(&(msg_info.msg_rtime))); //最后接收消息的时间
printf("最后变化时间:%s",ctime(&(msg_info.msg_ctime))); //消息的最后变化时间
printf("消息UID是:%d\n",msg_info.msg_perm.uid); //消息的UID
printf("消息GID是:%d\n",msg_info.msg_perm.gid); //消息的GID
}
int main (int argc,char *argv[])
{
/* Step 1 初始化 */
key_t key; //键值
int ret = -1; //返回状态
int msg_flags; //消息的标记
int msg_id; //消息ID识别号
int msg_sflags; //消息发送的标记
int msg_rflags; //消息接受的标记
char *msgpath = "/ipc/msg/"; //消息key产生所用的路径
/* 消息的缓冲区结构 */
struct msgmbuff{
long mtype; //消息类型
char mtext[10]; //消息数据
};
struct msqid_ds msg_info;
struct msgmbuff msg_mbuf; //自定义构建的消息缓冲区
/*
Step 2 构建IPC键值
将路径名和项目的标识符转变为系统V的IPC键值
*/
key = ftok(msgpath, 'a'); //产生key
if(key != -1) //产生key成功
{
printf("成功建立KEY\n");
}
else //产生key失败
{
printf("建立KEY失败\n");
}
/*
Step 3 创建消息队列
msgget:获取或创建消息队列
msg_flags:标记变量
*/
msg_flags = IPC_CREAT|IPC_EXCL; //消息队列的类型
msg_id = msgget(key, msg_flags|0x0666); //建立消息队列
if( msg_id == -1)
{
printf("消息队列建立失败\n");
return 0;
}else{
printf("成功建立消息队列\n");
}
msg_show_attr(msg_id, msg_info); //显示消息的属性
/*
Step 4 发送消息
msg_id:队列标志符
msg_flags:标记变量
*/
msg_sflags = IPC_NOWAIT; //消息的发送标记:直接读取消息,不等待
msg_mbuf.mtype = 10; //消息的大小为10字节
memcpy(msg_mbuf.mtext,"测试消息", sizeof("测试消息")); //复制字符串
ret = msgsnd(msg_id, &msg_mbuf,sizeof("测试消息"),msg_sflags);//发送消息
if( ret == -1) //发送失败
{
printf("发送消息失败\n");
}
msg_show_attr(msg_id,msg_info); //显示消息属性
/*
Step 5 接受消息
msg_id:队列标志符
msg_flags:标记变量
*/
msg_rflags = IPC_NOWAIT|MSG_NOERROR; //消息的接收标记:
ret = msgrcv(msg_id, &msg_mbuf, 10,10,msg_rflags); //接收消息
if( ret == -1) //接收失败
{
printf("接收消息失败\n");
}
else //接收成功
{
printf("接收消息成功,长度:%d\n",ret);
}
msg_show_attr (msg_id, msg_info); //显示消息属性
/*
Step 6 设置消息属性
*/
msg_info.msg_perm.uid = 8;
msg_info.msg_perm.gid = 8;
msg_info.msg_qbytes = 12345;
ret = msgctl(msg_id,IPC_SET, &msg_info); //设置消息属性
if( ret == -1 )
{
printf("设置消息属性失败\n");
return 0;
}
msg_show_attr(msg_id, msg_info); //显示消息属性
/*
Step 7 删除消息队列
*/
ret = msgctl(msg_id,IPC_RMID,NULL); //删除消息队列
if( ret == -1 )
{
printf("删除消息失败\n");
return 0;
}
return 0;
}
3.运行步骤:(未实践成功)
①创建路径名:sudo mkdir -p /ipc/msg
构建IPC键值的路径必须是已存在的目录。
②编译:gcc mq.c -o mq
③运行:./mq