在简单了解了进程间通信之后,我们知道进程间通信的方式不止有管道,还有其他的system V IPC资源。
其中就有消息队列。
消息队列是什么?
消息队列:提供一个进程像另一个进程发送有类型数据块的方法。消息队列的声明周期岁内核,其意思就是
就算进程结束,由操作系统所创建的队列也不会结束,它需要我们手动删除。
当然消息队列也有他的缺点:消息队列与命名管道有一样的不足,就是每个消息的最大长度是有上限的
(MSGMAX),每个消息队列的总的字节数(MSGMNB)、系统上消息队列的总数也都有上限(MSGMNI)。
由操作系统所提供的消息队列也不会只有一条,那我们是如何来保证两个进程通过同一个消息队列来进行通信的呢?
这就要提出一个词Key,Key是消息队列的标号,当两个进程看到标号相同的消息队列时,才能够进行通信。
一、接下来我们就看看消息队列的有关结构和函数。
1、IPC对象数据结构:(可以敲命令 cat /usr/include/linux/ipc.h进行查看)
内核为每个IPC对象维护了一个数据结构
结构体中的第一个变量Key就代表的是消息队列的标号,那么Key是怎么得出的呢?
这里我们就需要提一个函数:
Key_t ftok(const char *pathname,int proj_id);
第一个参数为获取路径,第二个参数为工程号。
2、 消息队列结构:(可以敲命令 cat /usr/include/linux/msg.h进行查看)
3、消息队列在内核中的表示(以图表的形式说明)
4、 消息队列相关函数
(1)创建和访问一个消息队列
int msgget(key_t key, int msgflg);
返回值:成功返回一个 非负整数,即该消息队列的标识符;失败返回-1;
参数:Key即为消息队列的标识符。由fork函数得出;
masflag由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的。
我们比较常用的就是IPC_CREAT、IPC_EXCL (IPC_CREAT单独使用时,不存在就创建,存在就返回。和IPC_EXCL同时使用时,
消息队列不存在就创建,存在就出错返回)
(2)消息队列的控制函数
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
返回值:成功返回0;失败返回 - 1
(3)发送消息到队列中的函数
返回值:成功返回0;失败返回 - 1
(4)从消息队列中取消息的函数
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
5、制约
(1)消息结构在两方面受到制约:
首先,它必须小于系统规定的上限值;
其次,它必须以一个long int长整型开始,接收者函数将利用这个长整数确定消息的类型。
(2)消息结构参考形式输入下:
struct msgstru
{
long mtype; //大于0
char mtext[1];//用户可以自己指定大小
};
二、消息队列的实现
comm.c
#include "comm.h"
static int commMsg(int flags)
{
key_t _key = ftok(PATHNAME, PROJ_ID);
if (_key<0)
{
perror("ftok");
return -1;
}
int msgid = msgget(_key, flags);
if (msgid < 0)
{
perror("msgget");
}
return msgid;
}
int creatMsg()
{
return commMsg(IPC_CREAT|IPC_EXCL|0666);
}
int getMsg()
{
return commMsg(IPC_CREAT);
}
int destoryMsg(int msgid )
{
if(msgctl(msgid,IPC_RMID,NULL)<0)
{
perror("msgctl");
return -1;
}
return 0;
}
int sendMsg(int msgid,int who,char *msg)
{
struct msgbuf buf;//用户自己定义
buf.mtype=who;
strcpy(buf.mtext,msg);
if(msgsnd(msgid,(void*)&buf,sizeof(buf.mtext),0)<0)
{
perror("msgsnd");
return -1;
}
return 0;
}
int recvMsg(int msgid,int recvType,char out[])
{
struct msgbuf buf;
int p=msgrcv(msgid,(void*)&buf,sizeof(buf.mtext),recvType,0);
if(p<0)
{
perror("msgrcv");
return -1;
}
strncpy(out,buf.mtext,p);
return 0;
}
comm.h
#ifndef _COMM_H_
#define _COMM_H_
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/msg.h>
#include <string.h>
#define PATHNAME "."
#define PROJ_ID 0x6656
#define SERVER_TYPE 1
#define CLIENT_TYPE 2
struct msgbuf
{
long mtype;
char mtext[1024];
};
int creatMsg();//创建消息队列
int getMsg();//获得消息队列
int destoryMsg(int msgid );//销毁消息队列
int sendMsg(int msgid,int who,char* msg);//发送消息
int recvMsg(int msgid,int recvType,char out[]);//接收消息
#endif
server.c
#include "comm.h"
int main()
{
int msgid=creatMsg();//server创建消息队列
char buf[1024];
while(1)
{
buf[0]=0;
recvMsg(msgid,CLIENT_TYPE,buf);//读取消息队列中的消息
printf("client say :%s\n",buf);
printf("Please Enter:");
fflush(stdout);
ssize_t s=read(0,buf,sizeof(buf));
if(s>0)
{
buf[s-1]=0;
sendMsg(msgid,SERVER_TYPE,buf);//发送消息
printf("waiting \n ");
}
}
destoryMsg(msgid);
return 0;
}
client.c
#include "comm.h"
int main()
{
int msgid=getMsg();
char buf[1024];
while(1)
{
buf[0]=0;
printf("Please Enter:");
fflush(stdout);
ssize_t s = read(0,buf,sizeof(buf));
if(s>0)
{
buf[s-1]=0;
sendMsg(msgid,CLIENT_TYPE,buf);
printf("waiting \n");
}
recvMsg(msgid,SERVER_TYPE,buf);
printf("server say:%s\n",buf);
}
return 0;
}
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 client server
在消息队列中有一对特别重要的命令,
ipcs :显示IPC资源 我们要查看当前系统中的消息队列就可以加上 -q 选项
ipcrm:手动删除IPC资源
我们可以看到,在没运行server之前,系统中没有消息队列的存在。接下来我们就运行server
可以看到,系统中以及存在一个编号为131072 的消息队列,然后运行client我们就可以进行通信了。
在通信结束之后,我们还需要使用上面我们所讲到的命令ipcrm -q 加队列ID进行删除消息队列,
因为我们知道,消息队列由操作系统创建,生命周期随内核,不会自动销毁。
好了,通过消息队列进行进程间通信就完成了。