IPC之消息队列详解与使用

一、    概念

 消息队列就是一个消息的链表。对消息队列有写权限的进程可以向其中按照一定的规则添加新消息;对消息队列有读权限的进程可以从消息队列中读出消息。消息队列是随内核持续的。下面介绍三个概念:

1;随进程持续:IPC一直存在,直至打开IPC对象的最后一个进程关闭该对象为止,如管道和有名管道

2;随内核持续:IPC一直持续到内核重新自举或者显示删除对象为止。如:消息队列,信号量,共享内存

3;随文件系统持续:IPC一直持续的显示删除该对象为止

System V消息队列目前被大量使用。

二、    消息队列的信息

每个消息队列都有一个队列头,用结构struct msg_queue来描述。队列头中包含了该消息队列的大量信息,包括消息队列键值、用户ID、组ID、消息队列中消息数目等等,甚至记录了最近对消息队列读写进程的ID。读者可以访问这些信息,也可以设置其中的某些信息。这个结构存于系统空间。

 

struct msg_queue {
    structkern_ipc_perm q_perm;
    time_tq_stime;         /* last msgsndtime */
    time_tq_rtime;         /* last msgrcvtime */
    time_tq_ctime;         /* last changetime */
    unsignedlong q_cbytes;     /* current number of bytes on queue*/
    unsignedlong q_qnum;       /* number of messages inqueue */
    unsignedlong q_qbytes;     /* max number of bytes on queue */
    pid_tq_lspid;          /* pid oflast msgsnd */
    pid_tq_lrpid;          /* lastreceive pid */
    structlist_head q_messages;
    structlist_head q_receivers;
    structlist_head q_senders;
};
 



 

结构msqid_ds用来设置或返回消息队列的信息,存在于用户空间;

 

structmsqid_ds{ 
    struct ipc_perm msg_perm; 
    struct msg *msg_first; /* first message on queue,unused*/ 
    struct msg *msg_last; /* last message in queue,unused*/ 
    __kernel_time_t msg_stime; /* last msgsnd time */ 
    __kernel_time_t msg_rtime; /* last msgrcv time */
    __kernel_time_t msg_ctime; /* last change time */ 
    unsigned long msg_lcbytes; /* Reuse junk fields for 32bit */
    unsigned long msg_lqbytes; /* ditto */ 
    unsigned short msg_cbytes; /* current number of byteson queue */ 
    unsigned short msg_qnum; /* number of messages in queue*/
    unsigned short msg_qbytes; /* max number of bytes onqueue */ 
    __kernel_ipc_pid_t msg_lspid; /* pid of last msgsnd */
    __kernel_ipc_pid_t msg_lrpid; /* last receive pid*/ 
};



 

三、    打开创建消息队列

  消息队列的内核持续性要求每个消息队列都在系统范围内对应唯一的键值,所以,要获得一个消息队列的描述字,只需提供该消息队列的键值即可。msgget用于创建一个消息队列或打开一个现存的队列。

名称::

msgget

功能:

创建消息队列

头文件:

#include <sys/types.h>

#include <sys/msg.h>

#inlcude <sys/ipc.h>

函数原形:

int msgget(key_t key,int msgflag);

参数:

key 消息队列的键

flag 一些标志位

返回值:

若成功则为消息队列描述字若出错则为-1。

参数key是一个键值,由ftok获得;msgflg参数是一些标志位。该调用返回与健值key相对应的消息队列描述字。

       在以下两种情况下,该调用将创建一个新的消息队列:

       1.如果没有消息队列与健值key相对应,并且msgflg中包含了IPC_CREAT标志位;

       2.key参数为IPC_PRIVATE;

       参数msgflg可以为以下:IPC_CREAT(创建消息队列)、IPC_EXCL(  )、IPC_NOWAIT(  )或三者的或结果。

       还有注意的是:当创建一个新队列时,系统自动初始化struct msqid_ds结构的下列成员。

ipc_perm结构按我们以前说的进行初始化。该结构中mode成员按flag中的相应权限位设置。

msg_qnum,msg_lspid,msg_lrpid,msg_stime,msg_rtime都设置为0。

msg_ctime设置为当前时间。

msg_qbytes设置为系统限制值。

 

四、获得和修改消息队列属性,删除消息队列

名称::

msgctl

功能:

对消息队列进行多种操作

头文件:

#include <sys/msg.h>

函数原形:

int msgctl(int msqid, int cmd,struct msqid_ds *buf);

参数:

msqid   消息队列描述字

cmd    要执行的操作

buf     此队列的struct msqid_ds结构

返回值:

若成功返回0,若出错返回-1。

 

 该系统调用对由msqid标识的消息队列执行cmd操作,共有三种cmd操作:IPC_STAT、IPC_SET、IPC_RMID。

IPC_STAT:该命令用来获取消息队列信息,返回的信息存贮在buf指向的msqid_da结构中;

IPC_SET:该命令用来设置消息队列的属性,要设置的属性存储在buf指向的msqid_ds结构中;可设置属性包括:msg_perm.uid、msg_perm.gid、msg_perm.mode以及msg_qbytes,同时,也影响msg_ctime成员。

IPC_RMID:删除msqid_ds标识的消息队列.

 

五、用消息队列发送和接收消息

名称::

msgsnd

功能:

将数据放到消息队列上

头文件:

#include <sys/types.h>

#include <sys/msg.h>

#inlcude <sys/ipc.h>

函数原形:

int msgsnd(int msqid, struct msgbuf *msgp, int msgsz, int msgflg);

参数:

msqid   消息队列描述字

msgp       指向消息数据的指针

msgsz    发送消息的大小

msgflg       标志位

返回值:

若成功则为0,若出错则为-1。

      

 

 

 

 

 

 

 

 向msgid代表的消息队列发送一个消息,即将发送的消息存储在msgp指向的msgbuf结构中,消息的大小由msgze指定。

       structmsgbuf{

              longmtype; /*消息类型*/

              charmtext[1]; /*消息数据*/

       };

       我们可以把msgbuf结构看成是一个模版,程序员可以根据自己的需要来设计直接的消息结构。举例来说,如果某个应用需要交换由一个整数后跟一个8字节字符数组构成的消息,那它可以如下定义自己的结构:

       typedefstruct my_msgbuf{

              longmtypel

              int    mshort;

              char mchar[MY_DATA];

       }Message;

       对发送消息来说,有意义的msgflg标志为IPC_NOWAIT,指明在消息队列没有足够空间容纳要发送的消息时,msgsnd是否等待。造成msgsnd()等待的条件有两种:

       1.当前消息的大小与当前消息队列中的字节数之和超过了消息队列的总容量;

       2.当前消息队列的消息数(单位"个")不小于消息队列的总容量(单位"字节数"),此时,虽然消息队列中的消息数目很多,但基本上都只有一个字节。

       msgsnd()解除阻塞的条件有三个:

       1.不满足上述两个条件,即消息队列中有容纳该消息的空间;

       2.msqid代表的消息队列被删除;

      3.调用msgsnd()的进程被信号中断;

       当msgsnd成功返回,与消息队列相关的msqid_ds结构得到更新,以标明发出该调用的进程ID(msg_lspid),进行该调用的时间(msg_stime),并指示队列中增加了一条消息。

 六、消息队列的使用

 

#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/msgqueue";
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); /*调用msgsnd发送消息*/
if(reval==-1)
{
       printf("message send error\n");
}
msg_stat(msgid,msg_ginfo);
/*成功发送一个消息后,输出此时消息队列属性。第二次调用msg_stat 子函数*/
rflags=IPC_NOWAIT|MSG_NOERROR;       /*含义见表10.1*/
reval=msgrcv(msgid,&msg_rbuf,4,10,rflags);  
/*调用msgrcv接收消息,接收数据长度为4,type > 0,含义见表10.2*/
if(reval==-1)
{
       printf("read msg error\n");
    }
else
{
       printf("read from msg queue %d bytes\n",reval);   /*打印接收到数据的字节数*/
    }
msg_stat(msgid,msg_ginfo);
/*从消息队列中读出消息后,再次输出消息队列属性。第三次调用msg_stat 子函数*/
msg_sinfo.msg_perm.uid=8;
/*试图更改消息队列的缺省属性(要求root用户权限),所有者有效用户ID更改为8*/
msg_sinfo.msg_perm.gid=8;
/*消息队列的所有者有效组ID更改为8*/
msg_sinfo.msg_qbytes=16388;
/*消息队列可容纳最大字节数更改为16388(缺省为16384)*/
reval=msgctl(msgid,IPC_SET,&msg_sinfo);   /*调用msgctl设置消息队列属性*/
if(reval==-1)
{
       printf("msg set info error\n");
       return;
}
msg_stat(msgid,msg_ginfo);  /*验证设置消息队列属性。第三次调用msg_stat 子函数*/
reval=msgctl(msgid,IPC_RMID,NULL);  /* 操作完毕,调用msgctl删除消息队列*/
if(reval==-1)
{
       printf("unlink msg queue error\n");
       return;
}
return 0;
}

void msg_stat (int msgid,struct msqid_ds msg_info)
{
int reval;
sleep(1);   /*只是为了后面输出时间的方便*/
reval=msgctl(msgid,IPC_STAT,&msg_info);  /*调用msgctl 获得消息队列属性信息*/
if(reval==-1)
{
      printf("get msg info error\n");
      return;
}
printf("\n");
printf("current number of bytes on queue is %d\n",msg_info.msg_cbytes);
printf("number of messages in queue is %d\n",msg_info.msg_qnum);
printf("max number of bytes on queue is %d\n",msg_info.msg_qbytes);
/*每个消息队列的容量(字节数)都有限制MSGMNB,值的大小因系统而异。在创建*/
/*新的消息队列时,msg_qbytes的缺省值就是MSGMNB*/
printf("pid of last msgsnd is %d\n",msg_info.msg_lspid);
/*最近一个执行msgsnd函数的进程ID*/
printf("pid of last msgrcv is %d\n",msg_info.msg_lrpid);
/*最近一个执行msgrcv函数的进程ID*/
printf("last msgsnd time is %s", ctime(&(msg_info.msg_stime)));
/*最近一次执行msgsnd函数的时间。ctime()将时间转变成周、月、日、时分秒、年的*/
/*形式,属于标准C函数*/
printf("last msgrcv time is %s", ctime(&(msg_info.msg_rtime)));
/*最近一次执行msgrcv函数的时间*/
printf("last change time is %s", ctime(&(msg_info.msg_ctime)));
/*最近一次改变该消息队列的时间*/
printf("msg uid is %d\n",msg_info.msg_perm.uid);/*消息队列所有者的有效用户ID*/
printf("msg gid is %d\n",msg_info.msg_perm.gid); /*消息队列所有者的有效组ID*/
}


 

 

你可能感兴趣的:(消息队列)