一、消息队列简介
1.什么是消息队列
消息队列就是⼀个消息的链表,存放在内核中并由消息队列标识符表示。提供了⼀ 种由⼀个进程向另⼀个进程发送块数据的⽅法。 消息队列是存在于内核中,只有在内核重启(操作系统重启)或者显式的删除消息 队列时,该消息队列才会被真正的删除。 可以使⽤ipcs -q 查看系统消息队列信息,ipcrm -q msgid 移除⽤msgid标识的消息队列
2.消息队列的特点
①消息队列可以实现消息的随机查询。消息不⼀定要以先进先出的次序读取,编程时可以按消息的类型读取
②消息队列允许⼀个或多个进程向它写⼊或者读取消息。
③与⽆名管道、命名管道⼀样,从消息队列中读出消息,消息队列中对应的数据进程通信之消息队列都会被删除
④每个消息队列都有消息队列标识符,消息队列的标识符在整个系统中是唯⼀的。
⑤消息队列是消息的链表,存放在内存中,由内核维护。只有内核重启或⼈⼯删除消息队列时,该消息队列才会被删除。若不⼈⼯删除消息队列,消息队列会 ⼀直存在于系统中。
⑥消息队列的每⼀条消息都有⾃⼰的消息类型,消息类型⽤整数来表示,⽽且必须⼤于0。
3.消息队列与管道⽐较
管道:不能读取特定数据,只要管道中有数据,就会全部读取,通信效率低,不适合判频繁交换数据
消息队列:可以读取相对应的数据,适合频繁的数据交换。
缺点:
1.与管道⼀样,每个数据块有有个最⼤度的限制,
2.系统中所有队列所包含的全部数据块的总数也有个上限。
二、消息队列结构
操作消息队列时,需要⽤到⼀些数据结构,熟悉这些熟悉结构是操作消息队列的关 键。向消息队列发送消息时,需要⼀定的数据结构,Linux系统定义了⼀个模板数据结构:
#include <sys/msg.h>
struct msgbuf
{
long mtype;
char mtext[1];
};
其中 mtype代表消息的类型 mtext指定消息内容,虽然mtext指定为字符类型,并不代表消息类型只能是 char类型,消息类型可以是任意类型,可以根据⽤户需要去定义,⽐如:
//⾃定义消息类型1
struct Msg {
long type;
char name[20];
int age;
} msg;
//⾃定义消息类型2
typedef struct {
char name[20];
int age;
}Person;
typedef struct {
long type;
Person person;
}Msg;
消息队列中的消息⼤⼩是受到限制的,由的宏MSGMAX和 MSGMNB来限制⼀条消息的最⼤⻓度和⼀个队列的最⼤⻓度。 消息⼤⼩三⼤限制:
cat /proc/sys/kernel/msgmax最⼤消息⻓度限制
cat /proc/sys/kernel/msgmnb消息队列总的字节数
cat /proc/sys/kernel/msgmni消息条⽬数
Linux内核为每⼀个消息队列都维护了⼀个msgid_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 qu
eue (nonstandard) */
msgqnum_t msg_qnum; /* Current number of messages i
n queue */
msglen_t msg_qbytes; /* Maximum number of bytes allow
ed in queue */
pid_t msg_lspid; /* PID of last msgsnd(2) */ //可以获得最后一次得到的进程pid
pid_t msg_lrpid; /* PID of last msgrcv(2) */
};
三、消息队列接口函数
Linux提供了⼀系列消息队列的函数接⼝来实现进程间的通信。
1.功能:创建新的消息队列或获取已有的消息队列。
格式: int msgget(key_t key, int msgflg);
key: 关 键 字 值 ⽤ 来 标 识 消 息 队 列 对 象 ( 通 常 是 由 ftok() 返 回 的 , 也 可 以 指 定 为 IPC_PRIVATE),函数将它与已有的消息队列对象的关键字进⾏⽐较来判断消息 队列对象是否已经创建。 其实IPC_PRIVATE表示的key为0,所以这个key和IPC对象。
msgflg: IPC_CREAT如果内核中没有此队列,则创建它。如果key所命名的消息队列存在 时,IPC_CREAT标志会被忽略,⽽只返回⼀个标识符。 IPC_EXCL当和IPC_CREAT⼀起使⽤时,如果队列已经存在,则失败。 除了以上的两个标志以外,在msgflg 标志中还可以有存取权限控制符。这种控制 符的 意义和⽂件系统中的权限控制符是类似的。
返回值: 成功返回⼀个以key命名的消息队列的标识符(⾮零整数),失败时返 回-1.
2.功能:发送消息,将消息添加到消息队列尾部。
格式: int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
参数: msqid:是消息队列的引⽤标识符;
msgp:是指向消息内容所在的缓冲区;
msgsz:要发送消息的实际数据⻓度, 但不包括消息类型的⻓度(4个字节)
msgflg:该值为 0:如果消息队列空间不够,msgsnd 会阻塞。 如果为IPC_NOWAIT则直接返回。
返回值:0 表示成功,-1 失败并设置 errno。
3.功能:读取消息,从消息队列中取⾛消息
格式: size_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
参数 msqid:ipc 内核对象 id
参数 msgp:⽤来接收消息数据地址
参数 msgsz:消息正⽂部分的⼤⼩(不包含消息类型)
参数 msgtyp:指定获取哪种类型的消息
msgtyp = 0:获取消息队列中的第⼀条消息
msgtyp > 0:获取类型为 msgtyp 的第⼀条消息,除⾮指定了 msgflg 为 MSG_EXCEPT,这表示获取除了 msgtyp 类型以外的第⼀条消息。
msgtyp < 0:获取类型 ≤|msgtyp|≤|msgtyp| 的第⼀条消息。
msgflg:0表示忽略,表示进程将被堵塞,直到进程从队列中得到消息为⽌
IPC_NOWAIT:如果指定类型的消息不存在就⽴即返回,同时设置errno 为ENOMSG
错误MSG_EXCEPT:仅⽤于msgtyp>0的情况。表示获取类型不为msgtyp的消 息
错误MSG_NOERROR:如果消息数据正⽂内容⼤于msgsz,就将消息数据截断为msgsz
返回: 成功返回实际接收到的字节数,否则返回-1,错误码存放在errno中。
4.功能:销毁消息队列
格式:int msgctl(int msqid, int cmd, struct msqid_ds *buf);
msqid: 由msgget函数返回的消息队列标识码
cmd: 是将要采取的动作,(有三个可取值) IPC_STAT: 取此队列的msqid_ds结构,并将其存放在buf指向的结构中。
IPC_SET: 按由buf指向的结构中的值,设置与此队列相关的结构中的下列四个字段: msg_perm.uid、 msg_perm.gid、 msg_perm;mode msg_qbytes。
此命令只能由下列两种进程执⾏:⼀种是其有效⽤户ID等于msg_perm.cuid或msg_perm.uid;另⼀种是 具有超级⽤户特权的进程。只有超级⽤户才能增加msg_qbytes的值。
IPC_RMID : 从系统中删除该消息队列以及仍在该队列上的所有数据。这种删除⽴即⽣效。
四、消息队列使用
1.消息队列创建过程:
1)创建发送消息端
2)创建信息接收端
//消息队列发送端
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/msg.h>
#include<string.h>
struct node {
long type;
char name[20];
}Node;
int main()
{
//1.创建消息队列
key_t key = ftok("/bin/ls",3);
int msqid = msgget(key,IPC_CREAT|0600);
if(msqid == -1)
{
printf("create queue failed\n");
return -1;
}
//2.发送消息
struct node mydata;
mydata.type = 1;
strcpy(mydata.name,"hello");
int send = msgsnd(msqid,&mydata,6,0);
if(send == -1)
{
printf("send failed\n");
return -2;
}
return 0;
}
接收端:
//消息队列接收端
//用来接收消息
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/msg.h>
#include<string.h>
struct node {
long type;
char name[20];
}Node;
int main()
{
//strcpy(Node.name,"hello");
//1.获取消息队列
key_t key = ftok("/bin/ls",3);
int msqid = msgget(key,0600);
if(msqid == -1)
{
printf("create queue failed\n");
return -1;
}
//2.获取消息
struct node mydata;
int recv = msgrcv(msqid,&mydata,6,1,0); //6代表消息正文的大小 1代表发送端定义的类型 0表示忽略,进程将被阻塞,直到进程从队列中获取消息为止
if(recv == -1)
{
printf("recv failed\n");
return -2;
}
printf("%s----\n",mydata.name);
//3.销毁消息队列
msgctl(msqid,IPC_RMID,NULL);
return 0;
}
运行结果:
建立两个.c程序,先执行发送端,在执行接收端,接收端就会接收到发送端发送来的数据。
2.消息队列互相通信:
一端向另一端发送数据,另一端收到后,会向发送数据的那一端提示接收成功。
pro1.c:
//利用消息队列互相发送消息
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include<unistd.h>
#include <string.h>
struct mymsg
{
long msgtype;
char text[50];
};
int main()
{
//1.创建消息队列
key_t key=ftok("/bin/rm",1);
int msgid= msgget(key,IPC_CREAT|0600);
if(msgid==-1)
{
printf("msg create failed\n");
return -1;
}
//2. 消息队列发送和接收
struct mymsg data;
data.msgtype=1; //定义的类型为1
strcpy(data.text,"hello");
//发送
int ret=msgsnd(msgid,&data,50,0);
if(ret==-1)
{
printf("send failed\n");
return -2;
}
memset(&data,0,sizeof(data)); //将结构体前sizeof(int)个字节进行初始化
ret=msgrcv(msgid,&data,50,2,0); //接受类型为2的消息队列发送的消息
printf("send end :%s\n",data.text);
//3.destroy
return 0;
}
pro2.c:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include<unistd.h>
#include <string.h>
struct mymsg
{
long msgtype;
char text[50];
};
int main()
{
//1.获取消息队列
key_t key=ftok("/bin/rm",1);
int msgid= msgget(key,0600);
if(msgid==-1)
{
printf("msg create failed\n");
return -1;
}
//2. 接收消息队列
struct mymsg data;
int ret=msgrcv(msgid,&data,50,1,0); //从类型1接收数据
if(ret==-1)
{
printf("recv failed\n");
return -3;
}
printf("recv end=%s\n",data.text);
sprintf(data.text,"%s %s",data.text,"ok"); //字符串拼接函数
data.msgtype=2; //这是设定的类型
msgsnd(msgid,&data,50,0); //发送消息
//3.删除消息队列
msgctl(msgid,IPC_RMID,NULL);
return 0;
}
运行结果: