一:什么是消息队列?
消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法.每个数据块都被认为是一种类型,接受者进程接收的数据块可以有不同的类型值.我们可以通过发送消息来避免命名管道的同步与阻塞问题.消息队列与管道不同的是,消息队列是基于消息的,而管道是基于字节流的.且消息队列的读取不一定是先入先出.
缺陷是:每个消息的最大长度是有限的(MSGMAX),每个消息 队列的总的字节数是有上限的(MSGMNB),系统的消息队列的总数也有一个上限(MSGMNI).
我们可以通过几条简单的指令来查看:
二:IPC对象数据结构
内核为每个IPC对象维护一个数据结构(/user/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 */
};
消息队列结构:(/usr/include/linux/msg.h)
struct msqid_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 32 bit */
unsigned long msg_lqbytes; /* ditto */
unsigned short msg_cbytes; /* current number of butes on queue */
unsigned short msg_qnum; /* number of messages in queue */
__kernel_ipc_pid_t msg_lspid; /* pid of last msgsnd */
__kernel_ipc_pid_t msg_lrpid; /* last receive pid */
}
三:与消息队列相关的函数
1:创建消息队列或取得一存在消息队列
int msgget(key_t key,int msgflg);
参数信息:
key:可以认为是一个端口号,也可以由消息队列产生
msgflg:IPC_CREAT如果IPC不存在,则创建一个IPC资源,否则代开操作;
如果单独使用IPC_EXCL;只有在共享内存不存在的时候,新的消息队列才建立,否则就产生错误.
如果单独使用IPC_CREAT,XXXget()函数要么返回一个已经存在的共享内存的操作符,要么返回一个新建的共享内存的标志符
如果IPC_CREAT和IPC_EXCL标志一起使用,XXXget()将返回一个新建的IPC标识符,如果存在IPC资源,返回-1
IPC_EXCL标志本身没有太大的意义,但是和IPC_CREAT一起使用可以用来保证所得的对象是新建的,而不是打开已有的对象.
2:向队列读/写消息
msgrcv从队列中取用消息
ssize_t msgrcv(int msqid,void *msqp,size_t msgp,long msgtyp,int msgflg);
msgsnd将数据放到消息队列中
int msgsnd(int msqid,const void*msgp,size_t msgsz,int msgflg);
参数:
msqid:消息队列的标识码
msgp:指向消息缓冲区的指针,次位置用来暂时存储发送和接受的消息,是一个用户可定义的通用结构
struct msgstru
{
long mtype;//大于0
char mtex[用户指定大小]
};
msgsz:消息的大小
msgtyp:从消息队列读取的消息形态.值为0,表示消息队列中的所有消息都会被读取;
msgflg:用来指明核心程序再队列没有数据的情况下所应采取的行动.如果msgflg和常数IPC_NOWAIT合用,则在msgsnd()执行是若是消息队列已满,则msgsnd()将不会阻塞,而会立即返回-1,如果执行的是msgrcv(),则在消息队列呈空时,不做等待马上返回-1,并设定错误码为ENOMSG.当msgflg为0 时,msgsnd()及msgrcv()在队列呈满或呈空的情形时,采取阻塞等待的处理模式.
3:设置消息队列的属性:
原型:
int msgctl(int msgqid,int cmd,struct msgqid)ds *buf);
参数:
msgcytl系桶调用对msgqid标识的消息队列执行cmd操作,系桶定义了3种cmd操作
IPC_STAT:用来获取消息队列对应的msqid_ds数据结构,并将其保存到buf指定的地址空间
IPC_SET:用来设置消息队列的属性,要设置在存储在Buf中
IPC_RMID:从内核中删除msqid标识的消息队列
4:ftok()函数
函数ftok把一个已存在的路径名和一个整数标记得转换成一个key_t 值,称为IPC键
**#include<sys/types.h>
#include<sys/ipc.h>
key_t ftok(const char*pathname,int proj_d);**
该函数把从pathname导出的信息与id的低8位合成一个整数IPC键
ftok的典型实现调⽤用stat函数,然后组合以下三个值:
1.pathname所在的⽂文件系统的信息(stat结构的st_dev成员)
2.该⽂文件在本⽂文件系统内的索引节点号(stat结构的st_ino成员)
3. proj_id的低序8位(不能为0)
从程序运⾏行的结果可以看出,ftok调⽤用返回的整数IPC键由proj_id的低序8位,st_dev成员的
低序8位,st_info的低序16位组合⽽而成。
四:消息队列的实现:
1:编写Makefile文件:
.PHONY:all
all:client server
client:client.c comm.c
gcc -o $@ $^
server:server.c comm.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f server client
2:comm.h文件:
#pragma once
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<stdlib.h>
#define _PATH_NAME_ "/tmp"
#define _SIZE_ 1024
#define _PROJ_ID_ 0X6666
extern int server_type;
extern int client_type;
struct msgbuf
{
long type;
char text[_SIZE_];
};
int Creatmsg();//创建消息队列
int Getmsg();
int Sendmsg(int msg_id,int send_type,const char*msg);
int recvmsg(int msg_id,int recv_type,const char*msg_out);
int Destorymsg(int msg_id);//销毁消息队列
3:comm.c文件:
#include"comm.h"
#include<errno.h>
int server_type =1;
int client_type =2;
static int Commmsg(int flags)
{
key_t _key = ftok(_PATH_NAME_,_PROJ_ID_);
if(_key <0)
{
printf("%d : %s",errno,strerror(errno));
return -1;
}
int msg_id =msgget(_key,flags);
return msg_id;
}
//创建消息队列
int Creatmsg()
{
int flags =IPC_CREAT |IPC_EXCL |0666;
return Commmsg(flags);
}
//获得这个消息队列
int Getmsg()
{
int flags =IPC_CREAT;
return Commmsg(flags);
}
//销毁这个消息队列
int Destorymsg(int msg_id)
{
if(msgctl(msg_id,IPC_RMID,NULL)!=0)
{
printf("%d : %s",errno,strerror(errno));
return -1;
}
return 0;
}
//发送消息
int Sendmsg(int msg_id,int send_type,const char* msg)
{ struct msgbuf _buf;
_buf.type =send_type;
strncpy(_buf.text,msg,strlen(msg)+1);
if(msgsnd(msg_id,&_buf,sizeof(_buf.text),0)<0)
{
printf("%d : %s",errno,strerror(errno));
return -1;
}
return 0;
}
//收消息
int Recvmsg(int msg_id,int recv_type,char*msg_out)
{
struct msgbuf _buf;
_buf.type =0;
memset(_buf.text,'\0',sizeof(_buf.text));
if(msgrcv(msg_id,&_buf,sizeof(_buf.text),recv_type,0)<0)
{
printf("%d : %s",errno,strerror(errno));
return -1;
}
strcpy(msg_out,_buf.text);
return 0;
}
4:client.c文件:
#include"comm.h"
int main()
{
int msg_id =Getmsg();
char buf[_SIZE_];
while(1)
{
printf("please Enter:");
fflush(stdout);
ssize_t _s = read(0,buf,sizeof(buf)-1);
if(_s>0)
{
buf[_s-1] ='\0';
}
Sendmsg(msg_id,client_type,buf);
if(strcasecmp(buf,"quit")==0)
{
break;
}
memset(buf,'\0',sizeof(buf));
Recvmsg(msg_id,server_type,buf);
printf("server# %s\n",buf);
}
return 0;
}
5:server.c文件:
#include"comm.h"
int main()
{
int msg_id =Creatmsg();
if(msg_id<0)
{
printf("%d :%s\n",errno,strerror(errno));
return 1;
}
char buf[_SIZE_];
while(1)
{
memset(buf,'\0',sizeof(buf));
Recvmsg(msg_id,client_type,buf);
printf("client:%s\n",buf);
if(strcasecmp(buf,"quit")==0)
{
break;
}
printf("client say: please Enter#");
fflush(stdout);
ssize_t _s =read(0,buf,sizeof(buf)-1);
if(_s>0)
{
buf[_s-1] ='\0';
}
Sendmsg(msg_id,server_type,buf);
}
Destorymsg(msg_id);
return 0;
}
注意:一定要先打开server端然后再打开client端,否则就会出现17,FIleExistts的提示信息是程序无法运行,因为此时打开消息队列已经创建一个消息队列,并且申请了IPC资源,所以此时必须清除(切换至root权限),然后重新先执行server程序,再执行client程序.
6:几个与消息队列常见的指令:
pcs的用法
ipcs -a 是默认的输出信息 打印出当前系统中所有的进程间通信方式的信息
ipcs -m 打印出使用共享内存进行进程间通信的信息
ipcs -q 打印出使用消息队列进行进程间通信的信息
ipcs -s 打印出使用信号进行进程间通信的信息
ipcrm -q[参数] ——这里的参数是消息队列的编号