文章目录
消息队列
消息队列是保存在内核中的链表,采用链表来实现消息队列, 该链表是由系统内核维护,系统中存在多个消息队列, 每个消息队列用IPC标识符唯一 的进行标识。
消息队列的特点
1、全双工双向通信、既可以发送消息、也可以接受消息
2、消息队列生命周期随内核,如果没有释放消息队列或者没有关闭操作系统,消息队列会一直存在
3、消息队列是保存在内核中的消息链表,通信过程中,存在用户态与内核态之间的数据拷贝开销,通信不及时
4、消息队列不适合比较大数据的传输,大小受限制。因为在内核中每个消息体都有一个最大长度的限制,同时所有队列所包含的全部消息体的总长度也是有上限。在 Linux 内核中,会有两个宏定义 MSGMAX 和 MSGMNB,它们以字节为单位,分别定义了一条消息的最大长度和一个队列的最大长度
5、用户可以自定义消息队列中消息体的数据类型、存在数据格式、非字节流,消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
6、消息队列可以实现消息的随机查询,消息不一定要以先进先出方式读取,也可以按消息的类型读取(相同类型的消息按照先进先出)
消息队列原理图
消息队列数据结构和组织结构
消息队列主要包括三种类型的数据结构:
1、msqid_ds消息队列数据结构:描述整个消息队列的属性,主要包括整个消息队列的权限,拥有者、两个重要的指针分别指向消息队列的第一个消息和最后一个消息。
2、msg消息数据结构:整个消息队列的主体,一个消息队列有若干个msg消息结构,每个消息数据结构的基本成员包括消息类型、消息大小、消息内容指针和下一个消息数据结构位置。
3、msgbuf消息缓冲区结构,用户真正的数据,可以以此结构为模版定义自己的消息结构体
msqid_ds结构体
/usr/include/msg.h中定义
struct msqid_ds{
struct ipc_perm msg_perm; //权限
struct msg *msg_first; //指向消息头
struct msg *msg_last; //指向消息尾
__kernel_tiem_t msg_stime; //发送到队列的最后一个消息的时间戮
__kernel_tiem_t msg_rtime; //从队列中获取的最后一个消息的时间截
__kernel_tiem_t msg_ctime; //对队列进行最后一次变动的时间截
unsigned long msg_lcbytes; //在队列上所驻留的字节总数
unsigned long msg_lqbytes;
unsigned short msg_qnum; //当前处于队列中的消息数目
unsigned short msg_qbytes; //队列中能容纳的字节的最大数目
__kernel_ipc_pid_t msg_lspid; //发送最后一个消息进程的PID
__kernel_ipc_pid_t msg_lrpid; //接收最后一个消息进程的PID
};
msg_perm:它是ipc_perm结构的一个实例, ipc_perm结构是在Linux/ipc.h中定义的。用于存放消息队列的许可权限信息,其中包括访问许可信息,以及队列创建者的有关信息(如uid等)。
struct ipc_perm
{
__key_t __key; /* 用于区分消息队列, 函数msgget()使用的键值 */
__uid_t uid; /* 消息队列用户ID */
__gid_t gid; /* 消息队列用户组ID */
__uid_t cuid; /* 消息队列创建者ID */
__gid_t cgid; /* 消息队列创建者组ID */
unsigned short int mode; /* 权限,用户控制读写,例如0666 可以读写. */
unsigned short int __pad1;
unsigned short int __seq; /* 序列号 */
unsigned short int __pad2;
unsigned long int __unused1;
unsigned long int __unused2;
};
msg 结构体
struct msg{
struct list_head m_list; //内核链表节后
long m_type; //消息类型
int m_ts; //消息大小
struct msg_msgseg *next;// 存储msgbuf中剩余的数据
void *security; //the actual message follows immediately 真正消息位置 msgbuf数据位于此处
}
msg_buf结构体
struct msgbuf{
long mtype; //消息类型
char mtext[256]; //消息数据 大小不能超过8192
}
组织关系
作为IPC的消息队列,其消息的传递是通过Linux内核来进行的。在消息的发送和接收的时候,内核通过一个比较巧妙的设置来实现消息插入队列的动作和从消息中查找消息的算法。 结构list_head形成一个链表,而结构msg_msg之中的m_list成员是一个struct list_head类型的变量,通过此变量,消息形成了一个链表,在查找和插入时,对m_list域进行偏移操作就可以找到对应的消息体位置。内核中的代码在头文件<linux/msg.h>和<linux/msg.c>中,主要的实现是插入消息和取出消息的操作。
函数原型
system V提供的IPC机制包括消息队列,信号量和共享内存3种。使用IPC前必须先创建,每种IPC都有特定的生产者、所有者和访问权限。
pcs命令可以查看当前系统正在使用的IPC工具。一个IPC工具至少包含key值、ID值、拥有者、权限和使用的大小等关键信息。如果需要手工删除某个IPC机制,可以使用ipcrm命令。
//头文件
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
ftok 函数
//获取IPC 键值 用于区分不同的消息队列
key_t ftok(const char *pathname, int proj_id);
参数 pathname:为文件路径,或目录,一般是当前目录。
参数 id:为一个整形变量,是子序号,参与构成ftok()函数的返回值。虽然是int类型,但是只使用8bits(1-255)。
在ftok()函数创建key值过程中使用了该文件属性的st_dev 和st_ino。
key值构成:
key值的第31-24(共8位)为ftok()第二个参数的低8位。//第二个参数的用处在这里。
key值的第23-16(共8位)为该文件的st_dev属性的第8位。
key值的第15-0为该文件的st_ino属性的低16位。
msgget函数
// 创建或打开消息队列:成功返回队列ID,失败返回-1
int msgget(key_t key, int flag);
在使用一个消息对列前,需要使用msgget()函数创建该消息队列,其函数声明如下:
参数 key:为由ftok创建的key值,
参数 msgflg:用来确定消息队列的访问权限,是一个权限标志,表示消息队列的访问权限,它与文件的访问权限一样。msgflg可以与IPC_CREAT做或操作(IPC_CREAT|0777,后面加上文件执行权限),表示当key所命名的消息队列不存在时创建一个消息队列,如果key所命名的消息队列存在时,IPC_CREAT标志会被忽略。
msgsnd函数
发送信息到消息队列
// 添加消息:成功返回0,失败返回-1
int msgsnd(int __msqid,__const void *__msgp,size_t __msgsz,int __msgflg)
参数 msqid:数由msgget()生成的消息队列标识符,即将消息添加到那个消息队列中。
参数 __msgp:指向用户定义的缓冲区msgbuf结构:
参数 msgsz:信息的大小。
参数 msgflg:创建标记,如果指定IPC_NOWAIT,失败会立即返回,0:阻塞发送,IPC_NOWAIT:非阻塞发送
成功调用后,此函数将返回0,否则返回-1,同时将对消息队列msqid以下成员执行下列操作:
msg_qnum:以 1 为增量递增。
smg_lspid:设置为调用进程的进程ID。
msg_stime:设置为当前时间。
msgrcv函数
//成功返回实际读取消息的字节数,失败返回-1,并设置erron
ssize_t msgrcv(int msqid, void *msgp, sizet msgsz, long msgtyp, int msgflg);
参数msgid:消息队列ID
参数msgp:指向msgbuf的指针,用来接收消息
参数msgsz:用于指定mtext的大小,如果收到的消息大于msgsz,并且msgflg&MSG_NOERROR为真,则将g该消息截至msgsz字节,截去部分丢失不提示。
参数 msgtyp:用于指定提取什么类型的消息。
=0:接收队列中的第一条消息,任意类型。
>0: 接收第一条指定为msgtyp类型的消息。
<0:接收第一条最低类型(小于或等于msgtyp的绝对值)的消息。
参数msgflg:如果设置IPC_NOWAIT,如果现在没有消息,调用进程立即返回,同时返回-1,并将errno设置为ENOMSG。如果未设置IPC_NOWAIT,则阻塞调用进程,直至出现以下任何一种情况发生:
a)某一所需类型的消息被放置到队列中;
b)msqid从系统中删除,当该情况发生时,将errno设置为I、EIDRM,并返回-1;
c)调用进程收到一个要捕获的信号,在这种情况下,未收到消息,并且调用进程按signal(SIGTRAP)中指定方式恢复执行。
消息接收成功完成后,该消息将自动从消息队列中删除,并返回接收到的消息大小,并将对整个消息队列msqid数据数据结构的成员执行下列操作:
msg_qnum:以 1 为减量递减。
msg_lrpid:设置为调用进程的进程ID。
msg_rtiem:设置为当前时间
msgctl函数
创建消息队列后,可以对该消息队列的基本属性进行控制(修改)
int msgctl (int __msgid,int __cmd,struct msqid_ds *buf);
参数 msqid:为消息队列标识符,该值为使用msgget()函数创建消息队列的返回值。
参数 buf:是一个临时的msqid_ds 结构体类型的变量。用于存储读取的消息队列属性或者需要修改的消息队列属性。
参数cmd:为执行的控制命令,即要执行的操作。包括以下选项:
#define IPC_RMID 0 //remove
#define IPC_SET 1 //set ipc_perm options
#define IPC_STAT 2 //get ipc_perm options
#define IPC_INFO 3 //see ipcs
IPC_STAT:读取消息队列属性,取得此队列的msqid_ds 结构,并将其存放在buf指向的结构中。
IPC_RMID:删除消息队列,从系统中删除该消息队列以及人在该队列上的所有数据,这种删除立即生效。仍在使用消息队列的其他进程在他们下一次试图对此队列进行操作时,将出错返回EIDRM。
IPC_SET:设置消息队列属性。按由buf指向的结构中的值,设置与此队列相关的结构中的下列4个字段:msg_perm.uid,msg_perm.gid,msg_perm.mode和msg_perm.msg_qbytes。
IPC_INFO:读取消息队列基本情况。
IPC_SET和IPC_RMID只能由下列两种进程执行:
一种是其有效用户ID等于msg_perm.cuid 或msg_perm.uid的进程
另一种 是具有超级用户特权的进程。只有超级用户才能增加msg_qbytes的值。
这四条选项(IPC_STAT、IPC_SET、IPC_INFO和IPC_RMID)也可用于信号量和共享内存。
消息队列例子:
本例在建立消息队列后,打印其属性,并在每次发送和接收后均查看其属性,最后对消息队列进行了修改。
显示消息属性的函数msg_show_attr()
msg_show_attrO函数根据用户输入的消息ID,将消息的属性打印出来。函数根据输入参数msg_id获得消息的信息,将消息队列中的字节数、消息数、最大字节数、最后发送消息的进程、最后接收消息的进程、最后发送消息的时间、最后接收消息的时间、最后消息变化的时间及消息的UID和GID等信息进行打印。
client 发送10条消息 消息类型为1到10
server 接收类型为3的消息,并删除消息队列
client.c
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.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( -1 == ret){
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 (void){
int ret = -1;
int msg_flags, msg_id;
key_t key; //定义消息队列唯一,描述符
struct msgmbuf{ /*消息的缓冲区结构*/
long mtype;
char mtext[100];
};
struct msqid_ds msg_info;
struct msgmbuf msg_mbuf;
int msg_sflags, msg_rflags;
char *msgpath = "."; /*key产生所用的路径, 当前目录*/
key = ftok (msgpath, 'a'); /*产生key*/
if (key != -1){ /*产生key成功*/
printf ("成功建立 KEY\n");
}else{ /*产生key失败*/
printf("建立 KEY失败\n");
}
msg_flags = IPC_CREAT|IPC_EXCL; /*消息队列的类型*/
msg_id = msgget (key, msg_flags | 0666); /*建立消息队列*/
if(-1 == msg_id){
printf("消息队列建立失败\n!");
return 0;
}
msg_show_attr(msg_id, msg_info); /*打印msqid_ds信息 显示消息队列的属性*/
msg_sflags = IPC_NOWAIT; /*消息队列发送函数标志设置为非阻塞*/
memcpy(msg_mbuf.mtext,"jjjjjj from hbw", sizeof ("jjjjj from hbw")); /*复制字符串*/
for(int i = 0; i<10; i++){
msg_mbuf.mtype = i + 1; /*消息类型设置*/
ret = msgsnd(msg_id, &msg_mbuf, sizeof ("jjjjjj from hbw"), msg_sflags); /*发送消息*/
if( -1 == ret){
printf("发送消息失败\n");
}
}
msg_show_attr(msg_id, msg_info); /*打印msqid_ds信息 显示消息队列的属性*/
return 0;
}
Server.c
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.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( -1 == ret){
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[]){
int ret = -1;
int msg_flags, msg_id;
struct msgmbuf{ /*消息的缓冲区结构*/
long mtype;
char mtext[100];
};
struct msqid_ds msg_info;
struct msgmbuf msg_mbuf;
int msg_rflags;
msg_flags = IPC_CREAT; /*消息队列的类型*/
msg_id = msgget(0x610e8d71, msg_flags | 0666); /*建立消息队列*/
if(-1 == msg_id){
printf("消息队列建立失败\n!");
perror("建立失败原因:");
return 0;
}
msg_show_attr(msg_id, msg_info); /*打印msqid_ds信息 显示消息队列的属性*/
msg_mbuf.mtype = 3; /*消息类型设置为10*/
// msg_rflags = 0; //设置为阻塞
msg_rflags = IPC_NOWAIT|MSG_NOERROR; /*消息队列接受函数标志设置为非阻塞 并且信息大于不报错,直接截取*/
ret = msgrcv(msg_id, &msg_mbuf, sizeof(msg_mbuf.mtext), msg_mbuf.mtype ,msg_rflags); /*接收消息*/
if(-1== ret){
printf("接收消息失败\n");
perror("失败原因:");
}else{
printf("接收消息成功,长度: %d\n",ret);
}
msg_show_attr(msg_id, msg_info); /*打印msqid_ds信息 显示消息队列的属性*/
// 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( -1 == ret){
// printf("设置消息属性失败\n!");
// perror("属性设置失败原因:");
// return 0;
// }
// msg_show_attr(msg_id, msg_info); /*打印msqid_ds信息 显示消息队列的属性*/
ret = msgctl (msg_id, IPC_RMID, NULL); /*删除消息队列*/
if( -1 == ret){
printf("删除消息队列失败\n");
perror("删除消息队列失败原因:");
return 0;
}
return 0;
}
Makefile
all:client server
client: client.o
gcc -o client client.o
server: server.o
gcc -o server server.o
clean:
rm -rf server client *.o