1、什么是消息队列
消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值。我们可以通过发送消息来避免命名管道的同步和阻塞问题。
消息队列与管道不同的是,消息队列是基于消息的,而管道是基于字节流的,且消息队列的读取不一定是先入先出。
消息队列与命名管道有一样的不足,就是每个消息的最大长度是有上限的(MSGMAX)
每个消息队列的总的字节数是有上限的(MSGMNB)
系统上消息队列的总数也有一个上限(MSGMNI)。
每个消息的最大长度MSGMAX:—->8192
每个消息队列总的字节数MSGMNB:—->16384
系统消息队列的总数MSGMNI:—->32000
2、IPC对象的数据结构
内核为每个IPC对象维护一个数据结构(/usr/include/linux/ipc.h)
struct ipc_perm{
key_t __key; /* Key supplied to xxxget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */unsigned short mode; /* Permissions */unsigned short __seq; /* Sequence number */
};
key:每个消息队列都有唯一的标识
cuid:创建者ID
cgid:创建者组ID
mode:读写权限
消息队列,共享内存和信号量都有这样一个共同的数据结构。
3、消息队列的数据结构(/usr/include/linux/msg.h)
struct msqid_ds {
struct ipc_perm msg_perm; /* Ownership and permissions */
time_t msg_stime; /* Time of last msgsnd(2) */
time_t msg_rtime; /* Time of last msgrcv(2) */
time_t msg_ctime; /* Time of last change */
unsigned long __msg_cbytes; /* Current number of bytes in
queue (non-standard) */
msgqnum_t msg_qnum; /* Current number of messages
in queue */
msglen_t msg_qbytes; /* Maximum number of bytes
allowed in queue */
pid_t msg_lspid; /* PID of last msgsnd(2) */
pid_t msg_lrpid; /* PID of last msgrcv(2) */
};
可以看到第一个条目就是IPC结构体,即是共有的,后面的都是消息队列所私有的成员。消息队列是用
链表
实现的。
4、消息队列的相关函数
(1)创建消息队列或取得已存在的消息队列
原型:
#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不存在,则创建一个IPC资源,否则打开操作。
IPC_EXCL:只有在共享内存不存在的时候,新的共享内存才建立,否 则就产生错误。
如果单独使用IPC_CREAT,XXXget()函数要么返回一个已经存在的共享内存的操作符,要么返回一个新建的共享内存的标识符。
如果将IPC_CREAT和IPC_EXCL标志一起使用,XXXget()将返回一个新建的IPC标识符;如果该IPC资源已存在,或者返回-1。
IPC_EXEL标志本身并没有太大的意义,但是和IPC_CREAT标志一起使用可以用来保证所得的对象是新建的,而不是打开已有的对象。
(2)向消息队列读/写消息
原型:
msgrcv从队列中取用消息:
msgsnd将数据放到消息队列中:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
参数:
msqid:消息队列的标识码
msgp:指向一个结构体,此位置用来暂时存储发送和接收的消息,是一个用户可定义的通用结构,形态如下:
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[1]; /* message data */
};
msgsz:消息的大小。
msgtyp:消息类型
msgtyp等于0 则返回队列的最早的一个消息。
msgtyp大于0,则返回其类型为mtype的第一个消息。
msgtyp小于0,则返回其类型小于或等于mtype参数的绝对值的最小的一个消息。
msgflg:用来指明核心程序在队列没有数据的情况下所应采取的行动。如果msgflg和常数IPC_NOWAIT合用,则在msgsnd()执行时若是消息队列已满,则msgsnd()将不会阻塞,而会立即返回-1,如果执行的是msgrcv(),则在消息队列呈空时,不做等待马上返回-1,并设定错误码为ENOMSG。当msgflg为0时,msgsnd()及msgrcv()在队列呈满或呈空的情形时,采取阻塞等待的处理模式。
(3)设置消息队列的属性
原型:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
参数:msgctl系统调用对msgqid标识的消息队列执行cmd操作,系统定义了3种cmd操作 :
IPC_STAT : 该命令用来获取消息队列对应的 msqid_ds 数据结构,并将其保存到buf指 定的地址空间。
IPC_SET : 该命令用来设置消息队列的属性,要设置的属性存储在buf中。
IPC_RMID : 从内核中删除 msqid 标识的消息队列。
(4)key_t键
System V IPC使用key_t值作为它们的名字,在Redhat linux(后续验证默认都在该平台下)下key_t被定义为int类型,追溯如下:
/usr/include/sys/ipc.h#ifndef __key_t_defined
typedef __key_t key_t;#define __key_t_defined#endif
/usr/include/bits/types.h
typedef __DADDR_T_TYPE__daddr_t;/* The type of a disk address. */
typedef __SWBLK_T_TYPE__swblk_t;/* Type of a swap block maybe? */
typedef __KEY_T_TYPE__key_t;/* Type of an IPC key */
/usr/include/bits/typesizes.h#define __KEY_T_TYPE __S32_TYPE
/usr/include/bits/types.h#define __S32_TYPE int
(5)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键。
5、消息队列实例
comm.h
#ifndef _COMM_H_
#define _COMM_H_
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#define PATH "."
#define PROJ_ID 0x6666
#define CLIENT 1
#define SERVER 2
struct msgbuf
{
long mtype; /* message type, must be > 0 */
char mtext[1024]; /* message data */
};
int SendMsg(int msgid, int who,const char *str);
int VastuMsg(int msgid, int who, char *str);
int CreatMsgget();
int GetMsgget();
int DestroyMsgQueue(int msgid);
#endif
comm.c
#include"comm.h"
static int CommMsgget(int flags)
{
key_t _key = ftok(PATH,PROJ_ID);
if(_key == -1)
{
perror("ftok error");
return 1;
}
int _msg_id = msgget(_key,flags);//创建或获取消息队列
if(_msg_id < 0)
{
perror("msgget error");
return 2;
}
return _msg_id;
}
int CreatMsgget()
{
return CommMsgget(IPC_CREAT | IPC_EXCL | 0666);//如果将IPC_CREAT和IPC_EXCL标志> 一起\
}
int GetMsgget()//获取消息读列 不需要新创建
{
return CommMsgget(IPC_CREAT);
}
int DestroyMsgQueue(int msgid)
{
if(msgctl(msgid, IPC_RMID,NULL)<0)
{
perror("msgctl");
return -1;
}
}
int SendMsg(int msgid, int who,const char *str)
{
struct msgbuf msg;
msg.mtype = who;
strcpy(msg.mtext,str);
int msg_snd = msgsnd(msgid, (void*)&msg, sizeof(msg),0);
if(msg_snd<0)
{
perror("msgsnd");
return -1;
}
return 0;
}
int VastuMsg(int msgid, int who, char *str)
{
struct msgbuf msg;
int msg_rcv = msgrcv(msgid,(void*)&msg, sizeof(msg), who, 0);
if(msg_rcv<0)
{
perror("msgrcv");
return -1;
}
strcpy(str,msg.mtext);
return 0;
}
server.c
#include"comm.h"
int main()
{
int msgid = CreatMsgget();
char str[1024];
//printf("server creat msgget");
while(1)
{
printf("[server say]$");
fflush(stdout);
ssize_t i = read(0,str,sizeof(str)-1);
if(i > 0)
{
str[i-1] = 0;
SendMsg(msgid,SERVER,str);
}
VastuMsg(msgid,CLIENT,str);
printf("[client say]$%s\n",str);
}
DestroyMsgQueue(msgid);
// sleep(2);
}
client.c
#include"comm.h"
int main()
{
char str[1024];
int msgid = GetMsgget();
while(1)
{
VastuMsg(msgid,SERVER,str);
printf("[server say]$%s\n",str);
printf("[client say]$");
fflush(stdout);
ssize_t i = read(0,str,sizeof(str)-1);
if(i>0)
{
str[i-1] = 0;
SendMsg(msgid,CLIENT,str);
}
}
return 0;
}
打开两个终端,分别模拟client和server,刚开始由服务器先给客户端发送数据,客户端接收来自服务器发送的数据,紧接着由客户端发数据,服务器接收数据,后续客户端和服务器交替的进行收发数据。
同命名管道一样,在每次运行之前必须将系统已有的消息队列删除。
ipcs -q:查看系统中的消息队列(ipcs:查看系统的资源-q:查看消息队列)
ipcrm -q 0:删除0号消息队列
消息队列的生命周期随内核,直到删除或系统关机