前面我们讲了进程间通信的其中一种方式,进程间通信(一)—管道,现在我们来讲一下另外一种方式就是消息队列。
1: 消息队列提供了从一个进程向另外一个进程发送一块数据的方法。
2:每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值。
3:消息队列也有管道一样的不足,就是每个消息的最大长度是有上限的(MSGMAX),每个消息队列的总的字节数也是有上限的(MSGMNB),系统上消息队列的总数也有一个上限(MSGMNI)。
IPC对象数据结构
struct ipc_perm
{
__kernel_key_t key;//消息队列的标识符
__kernel_uid_t uid;
__kernel_gid_t gid;
__kernel_uid_t cuid;
__kernel_gid_t cgid;
__kernel_mode_t mode; //权限
unsigned short seq;
};
消息队列结构
struct msqid_ds
{
struct ipc_perm msg_perm;
//first message on queue,unused
struct msg *msg_first;
//last message in queue,unused
struct msg *msg_last;
//last msgsnd time
__kernel_time_t msg_stime;
//last msgrcv time
__kernel_time_t msg_rtime;
//last change time
__kernel_time_t msg_ctime;
//Reuse junk fields for 32 bit
unsigned long msg_lcbytes;
//ditto
unsigned long msg_lqbytes;
//current number of bytes on queue
unsigned short msg_cbytes;
//number of messages in queue
unsigned short msg_qnum;
//max number of bytes on queue
unsigned short msg_qbytes;
//pid of last msgsnd
__kernel_ipc_pid_t msg_lspid;
//last receive pid
__kernel_ipc_pid_t msg_lrpid;
};
先要使用消息队列我们就必须先来认识一下相关的消息队列函数
msgget函数
创建一个消息队列
int msgget(key_t key,int msgflg);
//参数:key表示某个消息队列的名字
//msgflg有9个权限标志构成,他们的用法和创建文件时使用的mode模式标志是一样的
//返回值:成功返回一个非负整数,即该消息队列的标识码,失败返回-1
msgflg设置值:IPC_CREAT和IPC_EXCL
1:同时设置,要创建的消息队列已经存在则出错返回
2:同时设置,要创建的消息队列不存在则创建新的消息队列成功返回
3:单独设置IPC_CREAT,要创建的消息队列不存在则创建返回
4:单独设置IPC_CREAT,要创建的消息队列已存在则获取该消息队列
5:key_t ftok(const char *pathname,int proj_id);//获取队列标识符的函数
msgctl函数
控制消息队列
int msgctl(int msgid,int cmd,struct msqid_ds *buf);
//参数:msgid由msgget函数返回的消息队列标识码
//cmd:是将要采取的动作(有三个可取值)
//成功返回0,失败返回-1
cmd的取值
IPC_STAT:把msqid_ds结构中的数据设置为消息队列的当前关联值
IPC_SET:在进程有足够权限的前提下,把消息队列的当前关联值设置为msqid_ds数据结构中给出的值
IPC_RMID:删除消息队列
msgsnd函数
把一条消息添加到消息队列中
int msgsnd(int msqid,const void *msgp,size_t msgsz,int msgflg);
//参数:msgid由msgget函数返回的消息队列标识码
//msgp:是一个指针,指针指向准备发送的消息
//msgsz:是msgp指向的消息长度
//msgflg:控制着当前消息队列满或到达西永上限时将要发生的事情
//msgflg=IPC_NOWAIT表示队列满不等待,返回EAGAIN错误。
//返回值:成功返回0,失败返回-1
1:消息队列在两方面受到制约:首先它必须小于系统规定的上限值,其次,它必须以一个long int长整数开始,接收者函数将利用这个长整数确定消息的类型
2:消息结构参考形式如下:
struct msgbuf
{
long mtype;
char mtext[1];
}
msgrcv函数
从一个消息队列接收消息
ssize_t msgrcv(int msgid,void *msgp,size_t msgsz,long msgtyp,int msgflg);
//参数:msgid由msgget函数返回的消息队列标识码
//msgp:是一个指向准备接收的消息
//msgsz:由msgp指向的消息长度,这个长度不含保存消息类型的那个long int 长整形
//msgtyp:他可以实现接收优先级的简单形式
//msgflg:控制着队列中没有相应类型的消息可供接收收时将要发生的事情
//返回值:成功返回实际放到接收缓冲区离去的字符个数
msgtyp=0返回队列第一条信息
msgtyp<0返回队列第一条类型小于等于msgtyp绝对值的消息,并且是满足条件的消息类型最小的消息
msgtyp>0返回队列第一条类型等于msgtyp的消息
msgflg=IPCNOWAIT,队列没有可读消息不等待,返回ENOMSG错误。
msgflg=MSG_NOERRNO,消息大小超过msgsz时被截断
msgtype>0qiemsgflg=MSG_EXCEPT,接收类型不等于msgtype的第一条消息。
下面我们以代码实例演示两个进程间的通信,实现两个进程间的对话效果。
Makefile文件
.PHONY:all
all:process1.c process2.c
process1:process1.c
gcc $^ -o $@
process2:process2.c
gcc $^ -o $@
.PHONY:clean
clean:
rm -f process1 process2
com.h文件
#pragma once
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/types.h>
#include<sys/msg.h>
#include<string.h>
#define PATHNAME "."
#define PROJ_ID 0X6666
#define process1_type 1
#define process2_type 2
struct msgbuf
{
long mtype;
char mtext[1024];
};
//创建消息队列函数
int createMsgQueue();
//获取消息队列函数
int getMsgQueue();
//销毁消息队列函数
int destroyQueue(int msgid);
//发送消息函数
int sendMsg(int msgid,int who,char *msg);
//接收消息函数
int recvMsg(int msgid,int recvType,char out[]);
com.c文件
#include"com.h"
//success > 0 ,failed == -1
static int comMsgQueue(int flags)
{
//获取队列标识符赋值给key
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 createMsgQueue()
{
return comMsgQueue(IPC_CREAT|IPC_EXCL|0666);
}
int getMsgQueue()
{
return comMsgQueue(IPC_CREAT);
}
//封装msgctl函数
int destroyMsgQueue(int msgid)
{
if(msgctl(msgid,IPC_RMID,NULL) < 0)
{
perror("msgctl");
return -1;
}
return 0
}
//封装msgsnd函数
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;
}
//封装msgrcv函数
int recvMsg(int msgid,int recvType,char out[])
{
struct msgbuf buf;
if(msgrcv(msgid,(void*)&buf,sizeof(buf.mtext),recvType,0) < 0)
{
perror("msgrcv");
return -1;
}
strcpy(out,buf.mtext);
return 0;
}
process1.c文件
#include"com.h"
int main()
{
int msgid = createMsgQueue();
char buf[1024];
while(1)
{
buf[0] = 0;
recvMsg(msgid,process2_type,buf);
printf("process2 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,process1_type,buf);
printf("send done,wait recv...\n");
}
}
destroyMsgQueue(msgid);
return 0;
}
process2.c文件
#include"com.h"
int main()
{
int msgid = getMsgQueue();
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,process2_type,buf);
printf("send done,wait recv...\n");
}
recvMsg(msgid,process1_type,buf);
printf("process1 say %s\n",buf);
}
return 0;
}
运行结果如下:
需要注意的是,异常终止process1和process2,再次运行时就会报错
这时我们可以使用ipcs 和ipcrm命令
ipcs:显示IPC资源
ipcrm:手动删除IPC资源
当我们在命令行上执行ipcs -q 命令时,我们可以看到当前的消息队列的相关信息。使用ipcrm -q [msgid]就可以删除对应id的消息队列。结果如下图:
再次运行程序就可以正常运行了。