昨天我们看了进程间通信的一种方式-----管道,那么今天我们继续学习进程间通信的另一种方式----消息队列。
消息队列
消息队列是由内核维护的一种链式结构。链表中每一个记录又称作消息,消息具有特定的格式和优先级别。
1、消息队列提供了一个从一个进程向另一个进程发送一块数据的方法
2、每个数据块都被认为是有⼀个类型,接收者进程接收的数据块可以有不同的类型值
3、消息队列也有管道⼀样的不足,就是每个消息的最⼤长度是有上限的(MSGMAX),每个消息队
列的总的字节数是有上限的(MSGMNB),系统上消息队列的总数也有一个上限(MSGMNI)
IPC对象数据结构 可以在/usr/include/linux/ipc.h打开
内核为每个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; /* 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 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 */
};
消息队列在内核中表示:
消息队列函数
msgget函数
功能:⽤用来创建和访问⼀一个消息队列
原型
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
参数
key: 某个消息队列的名字
msgflg:由九个权限标志构成,它们的⽤用法和创建⽂文件时使⽤用的mode模式标志是⼀样的。
参数msgflg是一些标志位,常用的有IPC_CREAT、IPC_EXCL、IPC_NOWAIT三个取值
返回值:成功返回⼀个非负整数,即该消息队列的标识码;失败返回-1
msgctl函数
功能:消息队列的控制函数
原型:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
//成功返回0,失败返回-11
参数:
msqid:由msgget函数返回的消息队列标识码
IPC_STAT:该命令用来获取消息队列对应的 msqid_ds 数据结构,并将其保存到 buf 指定的地址空间。
IPC_SET:该命令用来设置消息队列的属性,要设置的属性存储在buf中。
IPC_RMID:从内核中删除 msqid 标识的消息队列。
cmd:是将要采取的动作(有三个可取值)
返回值:成功返回0,失败返回-1
msgsnd函数
功能:把⼀一条消息添加到消息队列中
原型
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//成功返回0,失败返回-1
参数
msgid: 由msgget函数返回的消息队列标识码
msgp:是一个指针,指针指向准备发送的消息,
msgsz:是msgp指向的消息长度,这个长度不含保存消息类型的那个long int长整型
msgflg:控制着当前消息队列满或到达系统上限时将要发生的事情
msgflg=IPC_NOWAIT表示队列满不等待,返回EAGAIN错误。
返回值:成功返回0;失败返回-1说明:
1.消息结构在两⽅方⾯面受到制约:
首先,它必须⼩于系统规定的上限值;其次,它必须以⼀个long int⻓整数开始,接收者函数将利⽤用这个⻓整数确定消息的类型
2.消息结构参考形式如下:struct msgbuf {
long mtype;
char mtext[1];
}
msgrcv函数
功能:是从⼀一个消息队列接收消息
原型
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
参数
msgid: 由msgget函数返回的消息队列标识码
msgp:是一个指针,指针指向准备接收的消息,
msgsz:是msgp指向的消息⻓度,这个长度不含保存消息类型的那个long int长整型
msgtype:它可以实现接收优先级的简单形式
msgflg:控制着队列中没有相应类型的消息可供接收时将要发生的事
注意:
如果msgflg和常数IPC_NOWAIT合用,则在msgsnd()执行时若是消息队列已满,则msgsnd()将不会阻塞,而会立即返回-1。如果执行的是msgrcv(),则在消息队列为空时,不做等待马上返回-1,并设定错误码为ENOMSG。当msgflg为0时,msgsnd()及msgrcv()在队列为满或为空的情形时,采取阻塞等待的处理模式
返回值: 成功返回实际放到接收缓冲区⾥里去的字符个数,失败返回-1接下来我们应用这些函数来实现client/server两进程进行通信:直接看代码:
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
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;
}
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资源
ipcrm:手动删除IPC资源
我们要查看当前系统中的消息队列就可以加上 -q 选项,如图:
这个时候我们可以看到当前是没有消息队列的,现在来运行我们的代码,先运行server.c,再开启一个终端查看消息队列
这时我们会发现,存在了消息队列,但是由于我们没有进行通信,因此mesages为0.
现在我们来进行通信:
最后,我们需要手动来删除消息队列,利用我们刚刚提到的命令:
ipcrm -q +msqid
ipcrm -Q +key
如上图所示,两种方法均可以删除消息队列。。。
总结:
消息队列的特性:
1、生命周期随内核
2、不是面向字节流,而是基于消息的