6.3 消息队列 message queue
6.3.1 消息队列
一个或多个进程可向消息队列写入消息,而一个或多个进程可从消息队列中读取消息,这种进程间通讯机制通常使用在客户/服务器模型中,客户向服务器发送请求消息,服务器读取消息并执行相应请求。在许多微内核结构的操作系统中,内核和各组件之间的基本通讯方式就是消息队列。例如,在 MINIX 操作系统中,内核、I/O 任务、服务器进程和用户进程之间就是通过消息队列实现通讯的。
Linux中的消息可以被描述成在内核地址空间的一个内部链表,每一个消息队列由一个IPC的标识号唯一的标识。Linux 为系统中所有的消息队列维护一个 msgque 链表,该链表中的每个指针指向一个 msgid_ds 结构,该结构完整描述一个消息队列。
6.3.2数据结构
6.3.2.1消息缓冲区(msgbuf)
我们在这里要介绍的第一个数据结构是msgbuf结构,可以把这个特殊的数据结构看成一个存放消息数据的模板,它在include/linux/msg.h中声明,描述如下:
/* msgsnd 和msgrcv 系统调用使用的消息缓冲区*/
struct msgbuf
{
long mtype; /* 消息的类型,必须为正数 */
char mtext[1]; /* 消息正文 */
};
注意:对于消息数据元素(mtext),不要受其描述的限制。实际上,这个域(mtext)不仅能保存字符数组,而且能保存任何形式的任何数据。这个域本身是任意的,因为这个结构本身可以由应用程序员重新定义:
struct my_msgbuf
{
long mtype; /* 消息类型 */
long request_id; /* 请求识别号 */
struct client info; /* 客户消息结构 */
};
我们看到,消息的类型还是和前面一样,但是结构的剩余部分由两个其它的元素代替,而且有一个是结构。这就是消息队列的优美之处,内核根本不管传送的是什么样的数据,任何信息都可以传送。
但是,消息的长度还是有限制的,在Linux中,给定消息的最大长度在include/linux/msg.h中定义如下:
#define MSGMAX 8192 /* max size of message (bytes) */
消息总的长度不能超过8192字节,包括mtype域,它是4字节长。
6.3.2.2消息结构(msg)
内核把每一条消息存储在以msg结构为框架的队列中,它在include/ linux/msg.h中定义如下:
struct msg {
struct msg *msg_next; /* 队列上的下一条消息 */
long msg_type; /*消息类型*/
char *msg_spot; /* 消息正文的地址 */
short msg_ts; /* 消息正文的大小 */
};
注意:msg_next是指向下一条消息的指针,它们在内核地址空间形成一个单链表。
6.3.2.3消息队列结构(msgid_ds)
当在系统中创建每一个消息队列时,内核创建、存储及维护这个结构的一个实例。
/* 在系统中的每一个消息队列对应一个msqid_ds 结构 */
struct msqid_ds {
struct ipc_perm msg_perm;
struct msg *msg_first; /* 队列上第一条消息,即链表头*/
struct msg *msg_last; /* 队列中的最后一条消息,即链表尾 */
time_t msg_stime; /* 发送给队列的最后一条消息的时间 */
time_t msg_rtime; /* 从消息队列接收到的最后一条消息的时间 */
time_t msg_ctime; /* 最后修改队列的时间*/
ushort msg_cbytes; /*队列上所有消息总的字节数 */
ushort msg_qnum; /*在当前队列上消息的个数 */
ushort msg_qbytes; /* 队列最大的字节数 */
ushort msg_lspid; /* 发送最后一条消息的进程的pid */
ushort msg_lrpid; /* 接收最后一条消息的进程的pid */
};
6.3.3 msgget()创建消息队列
为了创建一个新的消息队列,或存取一个已经存在的队列,要使用msgget()系统调用。
表头文件:#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
定义函数:int msgget ( key_t key, int msgflg );
函数说明:第一个参数 key 是消息队列的关键字值,可由 ftok() 函数获得。 第二个参数 msgflg 是一些标志,包括: IPC_CREAT : 如果这个队列在内核中不存在,则创建它。 IPC_EXCL :当与IPC_CREAT一起使用时,如果这个队列已存在,则返回错误。
返回值:成功,返回消息队列的例子;失败,则返回-1。
例子:
int main(int argc,char **argv)
{
int msgid;
msgid = msgget( 888, 0666|IPC_CREAT);
if( -1 == msgid )
{
perror( "msgget error" );
return -1;
}
return 0;
}
6.3.4 msgsnd() 发送消息
一旦我们有了队列识别号,我们就可以在这个队列上执行操作。要把一条消息传递给一个队列,你必须用msgsnd()系统调用。
定义函数:int msgsnd ( int msqid , struct msgbuf *msgp , int msgsz , int msgflg );
函数说明:msqid 第一个参数是消息队列标识符。
msgp 第二个参数是一个指针,指向我们重新声明和装载的消息缓冲区。是一个数据结构
struct msgbuf
{
long mtype; /* 消息的类型,必须为正数 */
char mtext[N]; /* 消息正文 */
};
msgsz 第三个参数指定了消息的字节大小,但不包括消息类型的长度( 4 个字节)。
msgflg 第四个参数可以设置成: 0 :此时忽略此参数,如果消息队列已满,调用进程将会挂起,直到消息可以写入到队列中。或者:IPC_NOWAIT :如果消息队列满,消息不写到队列中,并且控制权返回给调用进程(继续执行)。如果不指定IPC_NOWAIT,调用进程将挂起(阻塞)直到消息被写到队列中。
返回值:成功返回 0 ,失败返回 -1。
例子:
#define N 512
struct msgbuf
{
long mtype;//消息类型
char mtext[N];//消息正文
};
struct msgbuf msgbuf;
msgbuf.mtype = 1L;
strcpy(msgbuf.mtext,"hello,msg!");
if( msgsnd(msgid,&msgbuf,N,0) < 0 ) //发送消息
{
perror("msgsnd error");
return -1;
}
6.3.5msgrcv() 接收消息
定义函数:ssize_t msgrcv ( int msqid, struct msgbuf *msgp, int msgsz, long mtype, int msgflg );
返回值:成功,则为拷贝到消息缓冲区的字节数,失败为-1。
函数说明:msqid 第一个参数是消息队列的标识符。
msgp 第二个参数是存放消息的缓冲区的地址,也是一个数据结构类型。
msgsz 第三个参数是消息缓冲区的大小,不包括消息类型mtype的长度(4字节)。msgsz = sizeof( struct mymsgbuf ) - sizeof( long );
msgtype 第四个参数是要从消息队列中读取的消息的类型。 0 :接收消息队列中第一个消息。 大于 0 :接受消息队列中第一个类型为msgtype 的消息。 小于 0 :接受消息队列中类型值小于mtype 的绝对值且类型值又最小的消息。
Msgflg 取值为 : 0 从队列中取出最长时间的一条消息。 IPC—NOWAIT 当队列没有消息时,调用会立即返回ENOMSG错误。否则,调用进程将会挂起,直到队列中一条消息满足msgtcv()的参数要求。
让我们来看一个从我们已建的消息队列中检索消息的例子
memset( &msgbuf,0,sizeof(msgbuf) );
if( msgrcv(msgid,&msgbuf,N,1L,0 ) < 0 )//读取或接收消息
{
perror("msgrcv error");
return -1;
}
printf("msgrcv :%s \n",msgbuf.mtext);
6.3.6 msgctl() 消息队列的控制
定义函数 :int msgctl ( int msgid , int cmd , struct msgid ds *buf );
函数说明 : msgid 第一个参数是消息队列的标识符。
cmd 第二个参数指定了的操作:IPC_STAT :读取消息队列的属性,并将其保存在buf 指向的缓冲区中。IPC_SET :设置消息队列的属性。IPC_RMID :从系统中删除消息队列。
例如:
msgctl(msgid,IPC_RMID,0);
例子:
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
#define KEY 888
#define PERMS 0666|IPC_CREAT
#define N 512
#define M 512
struct msgbuf
{
long mtype;//消息类型
char mtext[N];//消息正文
char text[M];
};
int main()
{
int msgid;
struct msgbuf msgbuf;
msgid = msgget(KEY,PERMS);//创建消息队列,成功返回队列ID 号。 ipcs -q 查看
if( -1 == msgid )
{
perror("msgget error");
return -1;
}
printf("msgid is %d\n",msgid);
msgbuf.mtype = 1L;
strcpy(msgbuf.mtext,"hello,msg!");
if(msgsnd(msgid,&msgbuf,N,0) < 0 )//发送消息
{
perror("msgsnd error");
return -1;
}
memset(&msgbuf,0,sizeof(msgbuf));
if(msgrcv(msgid,&msgbuf,N+M,1L,0) < 0 )//读取或接收消息
{
perror("msgrcv error");
return -1;
}
printf("msgrcv :%s \n",msgbuf.mtext);
// msgctl(msgid,IPC_RMID,0);
return 0;
}