消息队列
前言
消息队列:
1、消息队列是进程或线程间通讯的其中一种方式。遵循先进先出的原则,保证了时间的顺序性。拥有该消息队列读权限的进程可以从消息队列读出数据,拥有该消息队列写权限的进程可以向消息队列发送数据。
2、消息作为节点一个一个地存放在消息队列里,可把消息队列比作信箱,消息比作依次顺序存放的信件。地址比作消息类型,内容为消息。
支持双向传输,可以使用消息类型区分不同的消息。
3、任何不同的进程都可以进行通讯,不局限于父子进程的通讯。
4、不足之处是消息的读写涉及数据拷贝,比较花费时间。
消息队列有两套机制:system v 和posix 前者移植性比较好,后者使用比较简单还可配合select机制使用、信号和线程通知。
一、System V 消息队列
1、创建或打开msgget()
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
success:返回标志符 error:-1
key:对象的关键字 或者 IPC_PRIVATE私有队列
msgflg:
IPC_CREAT:如果消息队列对象不存在,则创建之,否则则进行打开操作
IPC_EXCL: 和IPC_CREAT 一起使用(用”|”连接),如果消息对象不存在则创建之,
否则产生一个错误并返回。
2、控制(删除)msgctl()
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
msqid:消息队列的标志符
cmd:
IPC_STAT:把msgid_ds结构中的数据设置为消息队列的当前关联值,即用消息队列的当前
关联值覆盖msgid_ds的值。
IPC_SET:如果进程有足够的权限,就把消息列队的当前关联值设置为msgid_ds结构中给
出的值
IPC_RMID:删除消息队列
buf: 指向msgid_ds结构的指针,删除时设为NULL
3、发送/接收消息 msgsnd()/msgrcv()
消息结构体:
发送和接受的消息要定义一个消息结构体,且结构体必须至少含有消息类型(long的整型):
struct msg
{
long mtype;
char msg[100];
...
};
发送:
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
success: 0 error: -1
msqid:消息队列返回的标志符
msgp:待发送消息的buf
msgsz:除去消息类型(mtype)的最大消息大小
msgflg:
0:阻塞
IPC_NOWAIT:不阻塞。当消息队列满时,会阻塞知道队列中有足够的空间来存放这条消息。
但如果指定这个标识,那么会立即返回EAGAIN
接收:
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
int msgflg);
success:接收到的size(<= msgsz)
msgp:存放消息的buf
msgsz:除去消息类型(mtype)的最大消息大小
msgtyp:
msgtyp = 0:获取消息队列中的第一条消息
msgtyp > 0:获取类型为 msgtyp 的第一条消息,除非指定了 msgflg 为MSG_EXCEPT,
这表示获取除了 msgtyp 类型以外的第一条消息。
msgtyp < 0:按优先级队列最小获取类型 ≤|msgtyp|的第一条消息。
msgflg:
如果为 0 表示没有消息就阻塞。
IPC_NOWAIT:如果指定类型的消息不存在就立即返回,同时设置 errno 为 ENOMSG
MSG_EXCEPT:仅用于 msgtyp > 0 的情况。表示获取类型不为 msgtyp 的消息
MSG_NOERROR:如果消息数据正文内容大于 msgsz,就将消息数据截断为 msgsz
参考:https://www.jianshu.com/p/7e3045cf1ab8
4、demo.c
用消息队列write写消息 read读消息 read如果没消息队列会一直报错
read.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>
#include <unistd.h>
/*
消息队列比作信箱
消息比作信
消息类型比作信的目标地址
消息内容比作信的真实内容
ipcs -q
ipcrm -q msgid
*/
#define MSGKEY 1024
struct msgstru
{
long msgtype;
char msgtext[2048];
};
int main(int argc, char *argv[])
{
struct msgstru msgs;
int msgid,ret_value;
char str[512];
printf( "This is a read_process\r\n");
while(1){
msgid = msgget(MSGKEY,IPC_EXCL );/*检查消息队列是否存在 */
if(msgid < 0){
printf("msq not existed! errno=%d [%s]\n",errno,strerror(errno));
sleep(2);
continue;
}
/*接收消息队列 msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
* msgtyp:从消息队列内读取的消息形态。如果值为零,
* 则表示消息队列中的所有消息都会被读取
* msgflg : 0 表示阻塞等待,IPC_NOWAIT不等待
*/
ret_value = msgrcv(msgid,&msgs,sizeof(struct msgstru)-sizeof(long), 0, 0);
printf("read_process read_msg:ret_value=[%d],type=[%ld],text=[%s]\n",ret_value,msgs.msgtype,
msgs.msgtext);
}
return 0;
}
write.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>
#define MSGKEY 1024
struct msgstru
{
long msgtype; //消息类型type 的长度改不了
char msgtext[2048];//消息内容可修改大小
};
int main(int argc, char *argv[])
{
struct msgstru msgs;
int msg_type;
char str[256];
int ret_value;
int msqid;
printf( "This is a write_process\n");
msqid=msgget(MSGKEY,IPC_EXCL); /*检查消息队列是否存在*/
if(msqid < 0){
printf("msg not exist, so try to create one msg\r\n");
msqid = msgget(MSGKEY,IPC_CREAT|0666);/*创建消息队列ipcs -q 显示消息队列的使用情况*/
if(msqid <0){
printf("failed to create msq | errno=%d [%s]\n",errno,strerror(errno));
return -1;
}
}
while (1){
printf("input message type(end:0):");
scanf("%d",&msg_type);
if (msg_type == 0)
break;
printf("input message to be sent:");
scanf ("%s",str);
msgs.msgtype = msg_type;
strcpy(msgs.msgtext, str);
/* 发送消息队列
* 当msgflg为0时,msgsnd()及msgrcv()
* 在队列呈满或呈空的情形时,采取阻塞等待的处理模式。IPC_NOWAIT
*/
ret_value = msgsnd(msqid,&msgs,sizeof(struct msgstru)-sizeof(long),0);
printf("write_process=%d\r\n", ret_value);
if ( ret_value < 0 ) {
printf("write_process write msg failed,errno=%d[%s]\n",errno,strerror(errno));
//return -1;
}
}
printf("del the msg\r\n");
msgctl(msqid,IPC_RMID,0); //删除消息队列
return 0;
}
5、修改消息队列参数
1.永久修改
root用户下修改/etc/sysctl.conf 文件。
参数msgmax,msgmni,msgmnb都已经成功修改 更改的方法:
在配置文件/etc/sysctl.conf中加上 kernel.msgmax=value kernel.msgmni=value kernel.msgmnb=value 然后运行sysctl -p 即可进行修改
max queues system wide = // msgmni
max size of message (bytes) = //msgmax
default max size of queue (bytes) = //msgmnb
2.临时修改
root用户下sysctl -w kernel.msgmnb= 1048576
二、POSIX 消息队列
1.创建,关闭和删除
#include <mqueue.h>
mqd_t mq_open(const char *name, int oflag, mode_t mode,
struct mq_attr *attr );
成功返回消息队列描述符,失败返回-1
name:表示消息队列的名字,它符合POSIX IPC的名字规则。
oflag:表示打开的方式,和open函数的类似。有必须的选项:
O_RDONLY,O_WRONLY,O_RDWR,
还有可选的选项:O_NONBLOCK,O_CREAT,O_EXCL。
mode:是一个可选参数,在oflag中含有O_CREAT标志且消息队列不存在时,
才需要提供该参数。表示默认访问权限。可以参考open。
attr:也是一个可选参数,在oflag中含有O_CREAT标志且消息队列不存在时才需要。
该参数用于给新队列设定某些属性,如果是空指针,那么就采用默认属性。
mqd_t mq_close(mqd_t mqdes);
用于关闭一个消息队列,和文件的close类型,关闭后,消息队列并不从系统中删除
mqd_t mq_unlink(const char *name);
用于删除一个消息队列。消息队列创建后只有通过调用该函数或者是内核自举才能进行删除。
成功返回0,失败返回-1
2.发送与接收
mqd_t mq_send(mqd_t mqdes, const char *msg_ptr, size_t msg_len,
unsigned msg_prio);
成功返回0,出错返回-1
mqd_t mq_receive(mqd_t mqdes, char *msg_ptr, size_t msg_len,
unsigned *msg_prio);
成功返回接收到消息的字节数,出错返回-1
#ifdef __USE_XOPEN2K
mqd_t mq_timedsend(mqd_t mqdes, const char *msg_ptr, size_t msg_len,
unsigned msg_prio, const struct timespec *abs_timeout);
mqd_t mq_timedreceive(mqd_t mqdes, char *msg_ptr, size_t msg_len,
unsigned *msg_prio, const struct timespec *abs_timeout);
#endif
mqdes:消息队列描述符;
msg_ptr:指向消息体缓冲区的指针;
msg_len:消息体的长度,其中mq_receive的该参数不能小于能写入队列中消息的最大大小,
即一定要大于等于该队列的mq_attr结构中mq_msgsize的大小。如果mq_receive
中的msg_len小于该值,就会返回EMSGSIZE错误。POXIS消息队列发送的消息长度
可以为0。
msg_prio:消息的优先级;它是一个小于MQ_PRIO_MAX的数,数值越大,优先级越高。
POSIX消息队列在调用mq_receive时总是返回队列中最高优先级的最早消息。
如果消息不需要设定优先级,那么可以在mq_send是置msg_prio为0,mq_receive
的msg_prio置为NULL。
abs_timeout:可超时的时间
转至https://www.cnblogs.com/WindSun/p/11438501.html
3.消息通知mq_notify()
https://blog.csdn.net/qq_35976351/article/details/87024570