消息队列
Linux中的消息队列是进程间通信的一种方式,通过创建一个消息队列可以完成一个或者多个进程的信息交汇。因此,首先我们要了解消息队列是什么。消息队列的本质其实是一个内核提供的链表,内核基于这个链表,实现了一个数据结构,并且通过维护这个数据结构来维护这个消息队列。向消息队列中写数据,实际上是向这个数据结构中插入一个新结点;从消息队列汇总读数据,实际上是从这个数据结构中删除一个结点。
1.消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法
2.每个数据块都被认为是有一个类型,称为消息,消息与消息之间是有边界的(管道的字节流是无边界的)接收者进程接收的数据块可以有不同的类型值
3.消息队列也有管道一样的不足,就是每个消息的最大长度是有上限的(MSGMAX),每个消息队列的总的字节数是由上限的(MSGMNB),系统上消息队列的总数也有一个上限(MSGMNI)
IPC对象数据结构
什么是IPC对象?
IPC对象是活动在内核级别的一种进程间通信的工具。存在的IPC对象通过它的标识符来引用和访问,这个标识符是一个非负整数,它唯一的标识了一个IPC对象,这个IPC对象可以是消息队列或信号量或共享存储器中的任意一种类型。在Linux系统中标识符被声明成整数,所以可能存在的最大标识符为65535。这里标识符与文件描述符有所不同,使用open函数打开一个文件时,返回的文件描述符的值为当前进程最小可用的文件描述符数组的下标。IPC对象删除或创建时相应的标识符的值会不断增加到最大的值,归零循环分配使用。
查看IPC对象的命令:ipcs
ipcrm命令:
语法:ipcrm [ -m SharedMemoryID ] [ -M SharedMemoryKey ] [ -q MessageID ] [ -Q MessageKey ] [ -s SemaphoreID ] [ -S SemaphoreKey ]
选项
-m SharedMemory id 删除共享内存标识 SharedMemoryID。与 SharedMemoryID 有关联的共享内存段以及数据结构都会在最后一次拆离操作后删除。
-M SharedMemoryKey 删除用关键字 SharedMemoryKey 创建的共享内存标识。与其相关的共享内存段和数据结构段都将在最后一次拆离操作后删除。
-q MessageID 删除消息队列标识 MessageID 和与其相关的消息队列和数据结构。
-Q MessageKey 删除由关键字 MessageKey 创建的消息队列标识和与其相关的消息队列和数据结构。
-s SemaphoreID 删除信号量标识 SemaphoreID 和与其相关的信号量集及数据结构。
-S SemaphoreKey 删除由关键字 SemaphoreKey 创建的信号标识和与其相关的信号量集和数据结构。
内核为每个IPC对象维护一个数据结构
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; //访问权限
unsigned short __seq; //序号
};
不管是消息队列、共享内存、信号量,都具有上述的数据结构,这些IPC对象都是随内核持续的,也就是说,当访问这些对象的最后一个进程结束的时候,内核也不会删除这些对象,直到我们显示删除这些对象,才能从内核中删除掉。
消息队列结构
struct msqid_ds {
struct ipc_perm msg_perm; //IPC对象数据结构
time_t msg_stime; //最后一次往消息队列中发送数据的时间
time_t msg_rtime; //最后一次从消息队列中接收数据的时间
time_t msg_ctime; //最后一次改变的时间
unsigned long __msg_cbytes; //当前队列中消息的字节数
msgqnum_t msg_qnum; //当前队列中消息的个数
msglen_t msg_qbytes; //队列中允许存放的最大字节数
pid_t msg_lspid; //最后一个往消息队列中发送数据的进程号
pid_t msg_lrpid; //最后一个从消息队列中接收数据的进程号
};
消息队列在内核中的表示
消息队列中所存放的消息是按照链表的方式组织的
消息队列函数
msgget函数
功能:
用来创建和访问一个消息队列
原型:
int msgget(key_t key,int msgflg);
参数:
key:某个消息队列的名字
msgflg:由9个权限标志构成,它们的用法和创建文件时使用的mode模式标志一样
返回值:
成功返回一个非负整数,即该消息队列的标识码;失败返回-1
应用代码:
int main(void)
{
int msgid;
//如果消息队列已经存在,则是打开
msgid=msgget(1234,0666 | IPC_CREAT);
//只要指定IPC_PRIVATE,就创建一个新的消息队列
//IPC_PRIVATE创建出来的队列,是不能被其它进程共享的,因为其它进程也用IPC_PRIVATE的话,会创建一个不同的IPC队列出来(也就意味着两个进程不能共享一个消息队列)
//msgid=msgget(IPC_PRIVATE,0666 | IPC_CREAT | IPC_EXCL);
if(msgid==-1)
ERR_EXIT("msgget");
printf("msgget succ\n");
return 0;
}
说明:
msgctl函数
功能:
消息队列的控制函数
原型:
int msgctl(int msqid,int cmd,struct msqid_ds *buf);
参数:
msqid:由msgget函数返回的消息队列标识码
cmd:将要采取的动作(有三个可取值)
buff:动作需要传递的参数
返回值:
成功返回0,失败返回-1
IPC_RMID使用代码
//删除消息队列
int main(void)
{
int msgid;
//先打开消息队列
msgid=msgget(1234,0);
if(msgid==-1)
ERR_EXIT("msgget");
printf("msgget succ\n");
printf("msgid=%d\n",msgid);
//从系统中删除由msgid指定的消息队列
msgctl(msgid,IPC_RMID,NULL);
return 0;
}
IPC_SET使用代码
//设置消息队列状态
//把权限666改为600
int main(void)
{
int msgid;
//先打开消息队列
msgid=msgget(1234,0);
if(msgid==-1)
ERR_EXIT("msgget");
printf("msgget succ\n");
printf("msgid=%d\n",msgid);
struct msqid_ds buf;
msgctl(msgid,IPC_STAT,&buf);
sscanf("600","%o",(unsigned int*)&buf.msg_perm.mode);
msgctl(msgid,IPC_SET,&buf);
return 0;
}
IPC_STAT使用代码
//查看消息队列的状态
int main(void)
{
int msgid;
//先打开消息队列
msgid=msgget(1234,0);
if(msgid==-1)
ERR_EXIT("msgget");
printf("msgget succ\n");
printf("msgid=%d\n",msgid);
struct msqid_ds buf;
msgctl(msgid,IPC_STAT,&buf);
//IPC对象的存取权限
printf("mode=%o\n",buf.msg_perm.mode);
//当前队列中消息的字节数
printf("bytes=%ld\n",buf.msg_cbytes);
//当前队列中消息的个数
printf("number=%d\n",(int)buf.msg_qnum);
//队列中允许存放的最大字节数
printf("msgmax=%d\n",(int)buf.msg_qbytes);
return 0;
}
msgsnd函数
功能:
把一条消息添加到消息队列中
原型:
int msgsnd(int msqid,const void *msgp,size_t msgsz,int msgflg);
参数:
msgid:由msgget函数返回的消息队列标识码
msgp:是一个指针,指针指向准备发送的信息
msgsz:是msgp指向的消息长度,这个长度不包含保存消息类型的那个long int长整型
msgflg:控制着当前消息队列满或到达系统上限时将要发生的事情
msgflg=IPC_NOWAIT表示队列满不等待,返回EAGAIN错误
返回值:
成功返回0,失败-1
说明:
消息结构在两方面收到制约。首先,它必须小于系统规定的上限值;其次,它必须以一个long int长整数开始,接收者函数将利用这个长整型确定消息的类型
消息结构参考形式如下:
struct msgbuf
{
long mtype;//消息的类型
char mtext[1];//存放消息的场所
}
应用代码
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
}while(0)
//消息结构参考格式
struct msgbuf
{
long mtype;
char mtext[1];
};
/*
argc表示main函数的参数个数
argv表示传入main函数的参数序列或指针,并且第一个参数argv[0]一定是
程序的名称,并且包含了程序所在的完整路径
*/
//两个参数,消息长度和消息类型
int main(int argc,char *argv[])
{
if(argc!=3)
{
fprintf(stderr,"Usage:%s <bytes> <type>\n",argv[0]);
exit(EXIT_FAILURE);
}
//保存消息长度
//atoi是把字符串转换成整形数的一个函数
int len=atoi(argv[1]);
//保存消息类型
int type=atoi(argv[2]);
int msgid;
//先打开消息队列,要往消息队列中发送数据
msgid=msgget(1234,0);
if(msgid==-1)
ERR_EXIT("msgget");
struct msgbuf *ptr;
//分配的字节数,len是消息的长度
ptr=(struct msgbuf*)malloc(sizeof(long)+len);
ptr->mtype=type;
//0表示当消息队列满的时候,以阻塞的方式发送
//if(msgsnd(msgid,ptr,len,IPC_NOWAIT)<0)则不会阻塞,返回一个错误
if(msgsnd(msgid,ptr,len,0)<0)
ERR_EXIT("msgsnd");
return 0;
}
msgrcv函数
功能:
从一个消息队列接收信息
原型:
ssize_t msgrcv(int msqid,void *msgp,size_t msgsz,long msgtype,int msgflg);
参数:
msgid:由msgget函数返回的消息队列标识码
msgp:是一个指针,指向准备接收的消息
msgsz:是msgp指向的消息长度,这个长度不包含保存消息类型的那个long int长整型
msgtype:可以实现接收优先级的简单形式
msgtype=0返回队列第一条消息
msgtype>0返回队列第一条类型等于msgtype的消息
msgtype<0返回队列第一条类型小于等于msgtype绝对值的消息
msgflg:控制着队列中没有相应类型的消息可供接收时将要发生的事
msgflg=IPC_NOWAIT,队列没有可读消息不等待,返回ENOMSG错误
msgflg=MSS_NOERROR,消息大小超过msgsz时被截断
msgflg>0且msgflg=MSC_EXCEPT,接收类型不等于msgtype的第一条消息
返回值:
成功返回实际放到接收缓冲区去的字符个数,失败返回-1
应用代码
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
}while(0)
#define MSGMAX 8192
//消息结构参考格式
struct msgbuf
{
long mtype;
char mtext[1];
};
int main(int argc,char *argv[])
{
int flag=0;
int type=0;
int opt;
while(1)
{
/*
getopt()用来分析命令行参数,参数argc和argv代表参数个数
和内容,跟main()函数参数一样,参数optstring为选项字符串
*/
opt=getopt(argc,argv,"nt:");
//表示解析到了不认识的参数
if(opt=='?')
exit(EXIT_FAILURE);
if(opt==-1)
break;
}
}
int msgid;
msgid=msgget(1234,0);
if(msgid==-1)
ERR_EXIT("msgget");
struct msgbuf *ptr;
//消息的最大值不能超过8192
ptr=(struct msgbuf*)malloc(sizeof(long)+MSGMAX);
//由解析到的信息得来
ptr->mtype=type;
int n=0;
if((n=msgrcv(msgid,ptr,MSGMAX,type,flag))<0)
ERR_EXIT("msgsnd");
printf("read %d bytes type=%ld\n",n,ptr->mtype);
return 0;
}