【3】消息队列(报文队列)
(1)概述
消息队列是在消息传递过程中保存消息的容器。它是一种以链表式结构组织的一组数据,存放在内核中,由各进程通过消息队列标识符来引用,在消息队列中可以随意根据特定的数据类型值来检索消息
消息队列就是一个消息的链表。每个消息队列都有一个队列头,用结构struct msg_queue来描述。队列头中包含了该消息队列的大量信息,包括消息队列键值、用户ID、消息队列中消息数目等等,甚至记录了最近对消息队列读写进程的ID。用户可以访问这些信息,也可以设置其中的某些信息。
结构msg_queue用来描述消息队列头,存在于系统空间,定义如下:
- 结构msqid_ds用来设置或返回消息队列的信息,存在于用户空间,定义如下:
消息队列的内核持续性要求每个消息队列都在系统范围内对应唯一的键值,所以,要获得一个消息队列的引用标识符(ID)——即创建或打开消息队列,只需提供该消息队列的键值即可。
(2)获得特定文件名键值的系统调用是ftok,函数原型如下:
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(char *pathname,char proj);
返回:若成功返回消息队列的一个键值,若失败返回-1
(在调用msgget()来获得消息队列标识符前,往往调用该函数)
(3)创建或打开一个消息队列的系统调用为msgget,函数原型如下:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key,int msgflg);
返回:若成功则返回与键值key相对应的消息队列标识符(ID),若失败返回-1
参数key是一个键值,由ftok获得
msgflg参数是一些标志位,可以取以下值:IPC_CREAT、IPC_EXCL、IPC_NOWAIT或三者的逻辑或结果
(4)消息队列的读写
使用消息队列进行进程间通信,就是要对消息队列进行读和写操作
写操作即是向消息队列中发送数据,读操作就是从消息队列中读走数据
消息队列所传递的消息由两部分组成,即消息的类型和所传递的数据。
一般用数据结构struct msgbuf来表示,通常消息类型是一个正的长整数,而数据则根据需要设定。
设定一个传递1024字节长度的消息可将结构定义如下:
struct msgbuf { long msgtype; char msgtext[1024]; };
- msgtype:消息类型
msgtext:消息内容
因此,对于发送消息来说,首先预置一个msgbuf缓冲区并写入消息的类型和内容,调用相应的发送函数即可;对读消息来说,首先分配这样一个msgbuf缓冲区,然后把消息读入该缓冲区即可。
(5)向消息队列中发送数据:msgsnd
函数原型:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid,const void *prt,size_t nbytes,int flags);
返回:成功返回0,出错返回-1
msgsnd函数的作用是向一个消息队列发送一个消息,该消息被添加到队列的末尾
参数msqid代表消息队列的引用标识符(ID)
参数prt是一个void类型指针,指向要发送的消息
参数nbytes是以字节表示的消息的数据长度
参数flags用于指定消息队列满时的处理方法
(6)从消息队列接收数据:msgrcv
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgrcv(int msqid,void *prt,size_t nbytes,long,type,int flags);
返回:成功返回消息的数据长度,出错返回-1
此函数用于从指定的消息队列中读取一个消息数据
参数msqid代表消息队列的引用标识符
prt是一个void类型指针,指向存放消息的缓冲区
参数nbytes是以字节表示的要接收消息的数据长度
参数flags用于指定消息队列满时的处理方法
(7)获得或设置消息队列属性(msgctl)
消息队列信息基本上都保存在消息队列头中,因此,可以分配一个类似于消息队列头的结构struct msqid_ds来返回消息队列的属性;同样可以设置该数据结构。
关于消息队列属性的操作的系统调用如下:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid,int cmd,struct msqid_ds *buf);
返回:若成功返回0,否则返回-1
(8)下面的例子基本上涵盖了对消息队列的所有操作,msg_app.c
首先创建一个新的消息队列后,第一次调用msg_stat子函数打印出该消息队列的相关信息;然后向消息队列中发送一个消息后,第二次调用msg_stat打印消息队列此时相关信息;接着调用msgrcv从消息队列中读走消息后,第三次调用msg_stat打印消息队列此时的相关信息;最后试图使用root权限更改消息队列此时的相关属性,并再次调用msg_stat打印更改后的消息队列的信息。程序的最后,调用msgctl关闭该消息队列。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <unistd.h>
void msg_stat(int,struct msqid_ds);
int main(void)
{
int gflags,sflags,rflags;
key_t key;
int msgid;
int reval;
struct msgsbuf
{
int mtype;
char mtext[1];
}msg_sbuf;/*发送缓冲区数据结构*/
struct msgmbuf
{
int mtype;
char mtext[10];
}msg_rbuf;/*接收消息缓冲区数据结构*/
struct msqid_ds msg_ginfo,msg_sinfo;
char *msgpath="/unix/msgquene";
key=ftok(msgpath,'a');/*获取消息队列键值*/
gflags=IPC_CREAT|IPC_EXCL;
msgid=msgget(key,gflags|00666);/*调用msgget创建消息队列*/
if(msgid = -1)
{
printf("msg create error\n");
return;
}
msg_stat(msgid,msg_ginfo);/*创建一个消息队列后,输出队列缺省属性。第一次调用msg_stat子函数*/第一次打印
sflags = IPC_NOWAIT;/*消息队列满时,msgsnd不等待,立刻出错返回*/
msg_sbuf.mtype = 10;
msg_sbuf.mtext[0]='a';/*将要发送的消息数据*/
reval = msgsnd(msgid,&msg_sbuf,sizeof(msg_sbuf.mtext),sflags);/*调用msgs发送消息*/
if(reval == -1)
{
printf("message send error\n");
}
msg_stat(msgid,msg_ginfo);/*成功发送一个消息后,输出此时消息队列属性。第二次调用msg_stat子函数*/
rflags = IPC_NOWAIT|MSG_NOERROR;
reval = msgrcv(msgid,&msg_rbuf,4,10,rflags);/*调用msgrcv接收消息,接收数据长度为4,type > 0*/
if(reval == -1)
{
printf("read msg error\n");
}
运行结果: