消息队列
- 消息队列(也叫做报文队列)能够克服早期unix通信机制的一些缺点。作为早期unix通信机制之一的信号能够传送的信息量有限,后来虽然POSIX 1003.1b在信号的实时性方面作了拓广,使得信号在传递信息量方面有了相当程度的改进,但是信号这种通信方式更像"即时"的通信方式,它要求接受信号的进程在某个时间范围内对信号做出反应,因此该信号最多在接受信号进程的生命周期内才有意义,信号所传递的信息是接近于随进程持续的概念(process-persistent);管道及有名管道则是典型的随进程持续IPC,并且,只能传送无格式的字节流无疑会给应用程序开发带来不便,另外,它的缓冲区大小也受到限制。
- 消息队列就是一个消息的链表。可以把消息看作一个记录,具有特定的格式以及特定的优先级。对消息队列有写权限的进程可以向消息队列中按照一定的规则添加新消息;对消息队列有读权限的进程则可以从消息队列中读走消息。消息队列是随内核持续的。
- 目前主要有两种类型的消息队列:POSIX消息队列以及系统V消息队列,系统V消息队列目前被大量使用。考虑到程序的可移植性,新开发的应用程序应尽量使用POSIX消息队列。
- 系统V消息队列是随内核持续的,只有在内核重起或者显示删除一个消息队列时,该消息队列才会真正被删除。因此系统中记录消息队列的数据结构(struct ipc_ids msg_ids)位于内核中,系统中的所有消息队列都可以在结构msg_ids中找到访问入口。 消息队列就是一个消息的链表。每个消息队列都有一个队列头,用结构struct msg_queue来描述。队列头中包含了该消息队列的大量信息,包括消息队列键值、用户ID、组ID、消息队列中消息数目等等,甚至记录了最近对消息队列读写进程的ID。
- 但是同管道类似,它有一个不足就是每个消息的最大长度是有上限的(MSGMAX),每个消息队列的总的字节数(MSGMNB),系统上消息队列的总数上限(MSGMNI)。可以用
cat /proc/sys/kernel/msgmax
查看具体的数据。 - 内核为每个IPC对象维护了一个数据结构struct ipc_perm,用于标识消息队列,让进程知道当前操作的是哪个消息队列。每一个msqid_ds表示一个消息队列,并通过
msqid_ds.msg_first、msg_last
维护一个先进先出的msg链表队列,当发送一个消息到该消息队列时,把发送的消息构造成一个msg的结构对象,并添加到msqid_ds.msg_first、msg_last维护的链表队列。在内核中的表示如下:
消息队列的函数
一、消息队列之创建
创建一个消息队列,需要用到一个函数:
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
int msgget(key_t key,int msgflg);
- key:需要调用ftok函数来获取.
- msgflg :
- IPC_CREAT,不存在则创建,存在则返回已有的qid.
- IPC_CREAT|IPC_EXCL,不存在则创建,存在则返回出错.
- ftok函数原型如下:
#include<sys/types.h>
#include<sys/ipc.h>
key_t ftok(const char*pathname,int proj_id);
- ftok函数通过给定的路径名称取得其stat结构中的st_dev字段和st_info字段,然后将它们和项目id结合起来,然后产生一个键返回。
- 为了方便使用,我们将创建消息队列与获取消息队列封装为两个函数:
//创建与打开消息队列公共函数
int MessageCommon(key_t key,int flag){
int ret = 0;
if((ret=msgget(key,flag))==-1){
perror("msgget:");
exit(-1);
}
return ret;
}
//创建全新的消息队列(服务端)
int CreateMessage(key_t qid){
//消息队列也是具有权限结构的,因此在创建时给666权限
return MessageCommon(qid,IPC_CREAT|IPC_EXCL|0666);
}
//打开已有的消息队列(客户端)
int GetMessage(key_t qid){
return MessageCommon(qid,IPC_CREAT);
}
二、消息队列之发送消息
- 使用消息队列发送消息用到了一个函数和一个结构体:
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
int msgsnd(int msgid,const void* msgp,size_t magsz,int msgflg);
-
msgid:消息队列标识符
-
msgp:发送的消息结构体指针
-
msgsz:结构体中消息的大小(不是整个结构体的大小)
-
msgflg:IPC_NOWAIT,消息队列满时返回 -1 , 消息队列满时阻塞.
-
在通过msgsnd发送数据时,需要一个结构体来存放所发送的数据,及发送者的类型.
struct msgbuf{
long mtype; //消息类型,由用户自定义
char mtext[1024];//发送的消息(长度、类型可以自行指定)
}
- 因此,我们将发送消息也做一层封装:
//发送消息
void SendMessage(int msgid,const char* msg,int who){
msgbuf buf;
buf.mtype = who; //消息类型
strcpy(buf.mtext,msg); //消息内容
if(msgsnd(msgid,&buf,sizeof(buf.mtext),0) == -1){
perror("msgsnd");
DestoryMessage(msgid);
exit(-2);
}
}
三、消息队列之接收消息
- 消息队列接收消息的函数原型如下:
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
ssize_t msgrcv(int qid,void *msgp,size_t msgsz,long msgtyp,int msgflg);
-
qid:消息队列的标识符
-
msgp:消息结构体指针
-
msgsz:消息内容大小
-
msgtyp:消息类型
-
msgflg:消息类型
-
封装之后的代码如下:
void ReceiveMessage(int msgid,char* msg,int who){
msgbuf buf;
if(msgrcv(msgid,&buf,sizeof(buf.mtext),who,0)==-1){
perror("msgrcv");
DestoryMessage(msgid);
exit(-3);
}
strcpy(msg,buf.mtext);
}
四、消息队列的删除
- 其函数原型如下:
#include<sys/type.h>
#include<sys/ipc.h>
#Include<sys/msg.h>
int msgctl(int msgid,int cmd,struct msgid_ds *buf);
- msgid:消息队列标识符
- cmd:所要采取的命令.IPC_RMID删除消息队列
- buf:权限信息
- 封装后函数如下:
//消息队列的删除
void DestoryMessage(int msgid){
if(msgctl(msgid,IPC_RMID,NULL) == -1){
perror("msgctl");
exit(-4);
}
}
代码实现
- 模拟实现进程client和进程server之间进行通信
comm.h
#ifndef _COMM_H_
#define _COMM_H_
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/msg.h>
struct msgbuf
{
long mtype;
char mtext[1024];
};
#define SERVER_TYPE 1
#define CLIENT_TYPE 2
int createMsgQueue();
int getMsgQueue();
int destoryMsgQueue(int msg_id);
int sendMsgQueue(int msg_id, int who, char* msg);
int recvMsgQueue(int msg_id, int recvType, char out[]);
#endif
comm.c
#include "comm.h"
static int commMsgQueue(int flags)
{
key_t key = ftok("/tmp", 0x6666);
if(key < 0)
{
perror("ftok");
return -1;
}
int msg_id = msgget(key, flags);
if(msg_id < 0)
{
perror("msgget");
}
return msg_id;
}
int createMsgQueue()
{
return commMsgQueue(IPC_CREAT|IPC_EXCL|0666);
}
int getMsgQueue()
{
return commMsgQueue(IPC_CREAT);
}
int destoryMsgQueue(int msg_id)
{
if(msgctl(msg_id, IPC_RMID, NULL) < 0)
{
perror("msgctl");
return -1;
}
return 0;
}
int sendMsgQueue(int msg_id, int who, char* msg)
{
struct msgbuf buf;
buf.mtype = who;
strcpy(buf.mtext, msg);
if(msgsnd(msg_id, (void*)&buf, sizeof(buf.mtext), 0) < 0)
{
perror("msgsnd");
return -1;
}
return 0;
}
int recvMsgQueue(int msg_id, int recvType, char out[])
{
struct msgbuf buf;
int size=sizeof(buf.mtext);
if(msgrcv(msg_id, (void*)&buf, size, recvType, 0) < 0)
{
perror("msgrcv");
return -1;
}
strncpy(out, buf.mtext, size);
out[size] = 0;
return 0;
}
server.c
#include "comm.h"
int main()
{
int msgid = createMsgQueue();
char buf[1024] = {0};
while(1)
{
recvMsgQueue(msgid, CLIENT_TYPE, buf);
if(strcasecmp("quit", buf) == 0)
break;
printf("client# %s\n", buf);
printf("Please enter# ");
fflush(stdout);
ssize_t s = read(0, buf, sizeof(buf));
if(s>0)
{
buf[s-1]=0;
sendMsgQueue(msgid, SERVER_TYPE, buf);
printf("send done, wait recv...\n");
}
}
destoryMsgQueue(msgid);
return 0;
}
client.c
#include "comm.h"
int main()
{
int msgid = getMsgQueue();
char buf[1024] = {0};
while(1)
{
printf("Please Enter# ");
fflush(stdout);
ssize_t s = read(0, buf, sizeof(buf));
if(s > 0)
{
buf[s-1]=0;
sendMsgQueue(msgid, CLIENT_TYPE, buf);
if(strcasecmp("quit", buf) == 0)
break;
printf("send done, wait recv...\n");
}
recvMsgQueue(msgid, SERVER_TYPE, buf);
printf("server# %s\n", buf);
}
return 0;
}