消息队列
消息队列提供了一个一种从一个进程向另一个进程发送一个数据块的方法. 每个数据块都被认为是有一个类型,接受者进程接收的数据块可以有不同的
类型值。我们可以通过发送消息来避免命名管道的同步和阻塞问题。 消息队列与管道不同的是,消息队列是基于消息的,而管道是基于字节流的,且
消息队列的读取不一定是先入先出. 消息队列与命名管道有一个的不足,就是每个消息最大长度是有上限的.而且呢消息队列的生命周期是伴随内核.
所以如果你没有显示的删除它,那么在关机前它一直在.这里的消息队列我们可以看成一个链表队列,具体为什么? 让我们来看看消息队列的结构.
消息队列的结构:
在这里它的结构已经解释它的链式队列,_first和_last就是它的头指针和尾指针.接下来我们进行探究.大家都知道消息队列的核心就是如何表示出这
个消息队列?举个例子,就是我要找出消息队列的"身份证".我调出 struct msqid_ds结构体的第一个条目,也就是IPC的共有结构体,我们再看看它的
结构:
我们现在看到的这里的_key,就是消息队列的唯一标识,想找到一个消息队列就是靠他找,这个我们以后可以会说到的.
消息队列基本命令:
核心函数:
1.创建新消息队列或取得已存在的消息队列
原型:int msgget(key_t key,int msgflg);
参数:
key: 可以认为是一个端口号,也可以由函数ftok生产.
msgflg:只有在共享内存不存在的时候,新的共享内存才建立,否则就会产生错误.如果单独使用IPC_CREAT 和msgget()函数么返回一个已经存在的共享
内存的操作符,要么返回一个新建的共享内存的标识符.如果将IPC_CREAT和IPC_EXCL标志一起使用,msgget(),将返回一个新建的IPC标识符;如果该
IPC资源已存在,或者返回-1.IPC_EXCL标志本身并没有太大的意义,但是和IPC_CREAT标志一起使用可以用来保证所得对象是新建的,而不是打开已有
的对象.
2.向队列读/写消息
msgrev从队列中取用消息:
ssize_t msgrcv(int msqid,void *msgp,size_t msgsz,long msgtyp,int masflg);
msgsnd将数据放到消息队列中:
int msgsnd(int msqid,const void* msgp,size_t msgsz,int sagflg):
msqid:消息队列的标识码
msgp:指向消息缓冲区的指针,此位置用来暂时存储发送接收消息,是一个用户可定义的通用结构,形态如下:
struct msgstru
{
long mtype; //大于0
char mtext; 消息的大小
};
msgtyp:从消息队列内读取的消息形态.如果值为0,则表示消息队列中的所有消息都会被读取.
msgflg:用来致命核心程序在队列没有数据的情况的下所采取的行动.如果msgflg和常数IPC_NOWAIT合用,在msgsnd()执行时若是消息队列已满,则
msgsnd()不会阻塞,而会立即返回-1,如果执行的是msgrcv(),则在消息队列呈空时,不做等待马上返回-1,并设定错误码ENOMSG。当msgflg为0时,
msgrev()及msgrcv()在队列呈满或呈空的情形时,采取阻塞等待的处理方式.
3.设置消息队列属性
原型: int msgctl(int msgqid,int cmd,struct msqid_ds *buf);
参数:msgctl系统调用对msgqid标识的消息队列执行cmd操作系统定义了3种cmd操作:IPC_STAT,IPC_SET,IPC_RMID
IPC_STAT:该命令用来获取消息队列对应的msqid_ds数据结构,并将其保存到buf指定的地址空间.
IPC_SET:该命令用来设置消息队列的属性,要设置的属性存储在buf中.
IPC_RMID:从内核中删除msqid标识的消息队列.
浅析key_t:
ftok函数:
函数ftok把一个已存在的路径名和一个整数标识得转换成一个key_t值,成为IPC键:
#include<sys/types.h>
#include<sys/ipc.h>
key_t ftok(const char* pathname,int proj_id);
它的实际实现其实就是把从pathname导出的信息与id的低序8位组合成一个整数IPC键.
这里函数最后生成的key_t类型的_key就是该消息队列的唯一标识.
运用消息队列形成一个双向通信:
/*************************************************************************
> File Name: comm.c
> Author: ma6174
> Mail: ma6174@163.com
> Created Time: Wed 21 Jun 2017 11:50:33 PM PDT
************************************************************************/
#include"comm.h"
static int commMsgQueue(int flags)
{
//这里的两个都是头文件里面定义的宏
key_t _key = ftok(PATHNAME, PROJ_ID);
if (_key < 0){
perror("ftok");
return -1;
}
int msgid = 0;
if (msgid = msgget(_key, flags)<0){
perror("msgget");
return -2;
}
return msgid;
}
//创建消息队列
int creatMsgQueue()
{
return commMsgQueue(IPC_CREAT | IPC_EXCL | 0666);
}
//得到一个消息队列
int getMsgQueue()
{
return commMsgQueue(IPC_CREAT);
}
//销毁一个消息队列
int destroyMsgQueue(int msgid)
{
if (msgctl(msgid, IPC_RMID, NULL) < 0){
perror("msgctl");
return -1;
}
}
//接收一个消息队列
int recvMsg(int msgid, int type, char* _outMsg)
{
struct msgbuf _mb;
if (msgrcv(msgid, &_mb, sizeof(_mb.mtext), type, 0)< 0){
perror("msgrcv");
return -1;
}
strcpy(_outMsg, _mb.mtext);
return 0;
}
//发送一个消息队列
int sendMsg(int msgid, int who, const char *msg)
{
struct msgbuf _mb;
_mb.mtype = who;
strcpy(_mb.mtext, msg);
if (msgsnd(msgid, (void*)&_mb, sizeof(_mb.mtext), 0) < 0)
{
perror("msgsnd");
return -1;
}
return 0;
}
这里都是灵活运用上面的几个函数,认真的看一看代码相信你一定可以看明白的.
最后总结:
1.消息队列也可以独立于发送和接收进程而存在,从而消除了在同步命名管道的打开和关闭时可能产生的困难。
2.同时通过发送消息还可以避免命名管道的同步和阻塞问题,不需要由进程自己来提供同步方法。
3.接收程序可以通过消息类型有选择地接收数据,而不是像命名管道中那样,只能默认地接收。