一 什么是消息队列
消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。 每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值。我们可以通过发送消息来避免命名管道的同步和阻塞问题。消息队列与管道不同的是,消息队列是基于消息的,而管道是基于字节流的,且消息队列的读取不一定是先⼊入先出。消息队列与命名管道有一样的不足,就是每个消息的最⼤大长度是有上限的(MSGMAX),每个消息队列的总的字节数是有上限的(MSGMNB),系统上消息队列的总数也有⼀一个上限(MSGMNI)。
二 消息队列的结构
1 先介绍一下IPC对象的数据结构:每个内核为每个IPC对象维护一个数据结构(/usr/include/linux/ipc.h)
IPC对象的数据结构
struct kern_ipc_perm{ //内核中记录消息队列的全局数据结构msg_ids能够访问到该结构;
key_t key; //该键值则唯一对应一个消息队列
uid_t uid;
gid_t gid;
uid_t cuid;
gid_t cgid;
mode_t mode;
unsigned long seq;
}
2 消息队列的数据结构
结构msqid_ds用来设置或返回消息队列的信息,存在于用户空间:
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 bytes on queue */
unsigned short msg_qnum; /* number of messages in queue */
unsigned short msg_qbytes; /* max number of bytes on queue */
__kernel_ipc_pid_t msg_lspid; /* pid of last msgsnd */
__kernel_ipc_pid_t msg_lrpid; /* last receive pid */
};
可以看到消息队列的第一个数据成员就是IPC结构体。
三 操作消息队列
Linux提供了一系列消息队列的函数接口来让我们方便地使用它来实现进程间的通信。它的用法与其他两个System V PIC机制,即信号量和共享内存相似。
1 msgget函数
函数原型:int msgget(key_t key, int msgflg);
所在头文件:#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
函数作用:用来创建和访问一个消息队列
函数参数:msgflg:
IPC_CREAT 如果IPC不存在,则创建一个IPC资源,否则打开操作。
IPC_EXCL:只有在共享内存不存在的时候,新的共享内存才建⽴立,否则就产⽣生错误。
如果单独使⽤用IPC_CREAT,该函数要么返回一个已经存在的消息队列对象的标识符,要么返回一个新建的消息队列对象的标识符。如果将IPC_CREAT和IPC_EXCL标志一起使用,该函数将返回一个新建的IPC标识符;如果该IPC资源已存在,或者返回-1。
IPC_EXEL标志本⾝身并没有太大的意义,但是和IPC_CREAT标志一起使⽤用可以⽤用来保证所得的对象是新建的,而不是打开已有的对象。
msgflg还可以有文件的访问权限,可以与IPC_CREAT做或操作
key键:标志一个消息队列,可以用ftok函数获取。
函数返回值:成功返回标识符 ,否则返回-1.
2ftok函数
函数原型:key_t ftok(const char *pathname, int proj_id);
函数头文件: #include <sys/types.h>
#include <sys/ipc.h>
函数作用:通过文件名和项目号获得System V IPC key值
函数参数:proj_id不能为0即可
函数返回值:成功返回key,错误返回-1。
3 msgctl函数
函数原型:int msgctl(int msqid, int cmd, struct msqid_ds *buf);
函数作用:用来控制消息队列
函数参数:cmd:
IPC_STAT:获取消息队列的状态
IPC_SET:改变消息队列的状态
IPC_RMID:删除消息队列
buf:结构体指针用来存放消息队列
函数返回:成功返回0,错误返回-1.
4 msgsnd函数
函数原型:int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
函数参数:msgp是一个指向消息的结构体指针,
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[1]; /* message data */
};
msgtyp:从消息队列内读取的消息形态。如果值为零,则表示消息队列中的所有消息都会被读取
msgsz:是指向结构体中mtext的大小
msgflg:⽤用来指明核⼼心程序在队列没有数据的情况下所应采取的⾏行动。如果msgflg和常数IPC_NOWAIT合⽤用,则在msgsnd()执⾏行时若是消息队列已满,则msgsnd()将不会阻塞,而会立即返回-1,如果执⾏行的是msgrcv(),则在消息队列呈空时,不做等待马上返回-1,并设定错误码为ENOMSG。当msgflg为0,msgsnd()及msgrcv()在队列呈满或呈空的情形时,采取阻塞等待的处理模式。
函数返回:成功返回0,错误返回-1
5 msgrcv函数
函数原型:ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
函数参数:msgp指向消息结构体的指针,msgsz消息的大小,mtext的大小,
函数返回:成功返回0,错误返回-1.
四 具体实现进程间的通信
(1)comm.h 实现接头的头文件
#ifndef _COMM_H
#define _COMM_H
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<string.h>
#define PATHNAME "./"
#define PROJ_ID 0x666
#define SIZE 128 //接收字符数组的大小
#define SERVER_TYPE 1 //
#define CLIENT_TYPE 2
struct msgbuf //接收消息的结构体
{
long mtype;
char mtext[SIZE];
};
int commMsgQueue(int flags);
int CreateMsgQueue(); //创建消息队列
int GetMsgQueue(); //获取消息队列的msgid
int DestoryMsgQueue(int msgid);
int SendMsg(int msgid,long type,const char* info);
int ReceiveMsg(int msgid,long type,char out[]);
#endif
(2)comm.c 具体实现接口
#include"comm.h"
int commMsgQueue(int flags)
{
//get key
key_t key = ftok(PATHNAME,PROJ_ID); //获取key值
if(key <0)
{
perror("ftok");
return -1;
}
int msg_id = msgget(key,flags);
if(msg_id <0)
{
perror("msgget");
return -2;
}
return msg_id;
}
int CreateMsgQueue()//
{
return commMsgQueue(IPC_CREAT|IPC_EXCL|0666);
}
int GetMsgQueue()
{
return commMsgQueue(IPC_CREAT);
}
int DestoryMsgQueue(int msgid)
{
if(msgctl(msgid,IPC_RMID,NULL)<0)
{
perror("msgctl");
return -1;
}
return 0;
}
int SendMsg(int msgid,long type,const char* info)
{
struct msgbuf msg; //指向消息的结构体
msg.mtype = type;
strcpy(msg.mtext,info);
if(msgsnd(msgid,&msg,sizeof(msg.mtext),0)<0)
{
perror("msgsnd");
return -1;
}
return 0;
}
int ReceiveMsg(int msgid,long type,char out[])
{
struct msgbuf msg;
if(msgrcv(msgid,&msg,sizeof(msg.mtext),type,0)<0)
{
perror("msgrcv");
return -1;
}
strcpy(out,msg.mtext); //把信息拷贝到out中
return 0;
}
(3)server.c 先接收 后发送
#include"comm.h"
int main()
{
int msgid = CreateMsgQueue();
printf("msgid:%d\n",msgid);
sleep(5);
char buf[SIZE];
while(1){ //receive -> send
ReceiveMsg(msgid,CLIENT_TYPE,buf);
printf("client#:%s\n",buf);
printf("Please Enter:");
fflush(stdout);
ssize_t s = read(0,buf,sizeof(buf)-1);//最后buf末尾要加\0
if(s>0)
{
buf[s-1] = '\0'; //多读一个回车\n
SendMsg(msgid,SERVER_TYPE,buf);
}
sleep(1);
}
sleep(5);
DestoryMsgQueue(msgid);
return 0;
}
(6)clinet.c 先发送 后接收
#include"comm.h"
int main()
{
int msgid = GetMsgQueue();
printf("msgid:%d\n",msgid);
char buf[SIZE];
while(1){ //send -> receive
printf("Please Enter: ");
fflush(stdout);
ssize_t s = read(0,buf,sizeof(buf)-1);
if(s>0)
{
buf[s-1] = '\0';
SendMsg(msgid,CLIENT_TYPE,buf);
}
ReceiveMsg(msgid,SERVER_TYPE,buf);
printf("server#:%s\n",buf);
}
return 0;
}
Makefile:
client_=client
server_=server
cc=gcc
cliSrc=client.c comm.c
serSrc=server.c comm.c
.PHONY:all
all:$(client_) $(server_)
$(client_): $(cliSrc)
$(cc) -o $@ $^
$(server_):$(serSrc)
$(cc) -o $@ $^
.PHONY:clean
clean:
rm -f $(client_) $(server_)
总结一下:先启动sever 创建消息队列,再启动client.c 来进行通信
编写一个 查看器 查看消息队列:
while :; do ipcs -q; echo "###################################################"; sleep 1; done
每秒查看消息队列
命令:ipcs -q 查看系统消息队列
ipcrm -q 删除消息队列