Linux进程通信-消息队列

1.消息队列

消息队列也称为报文队列,消息队列是随内核持续的,只有在内核重起或显示删除一个消息队列时,该消息队列才会真正删除
系统中记录消息队列的数据结构struct ipc_ids msg_ids位于内核中,系统中所有消息队列都可以在结构msg_ids中找到访问入口
消息队列其实就是一个消息的链表,每个消息队列有一个队列头,称为struct msg_queue,这个队列头描述了消息队列的key值,用户ID,组ID等信息,但它存于内核中而结构体struct msqid_ds能够返回或设置消息队列的信息,这个结构体位于用户空间中,与msg_queue结构相似
消息队列允许一个或多个进程向它写入或读取消息,消息队列是消息的链表。消息是按消息类型访问,进程必须指定消息类型来读取消息,同样,当向消息队列中写入消息时也必须给出消息的类型,如果读队列使用的消息类型为0,则读取队列中的第一条消息。

内核空间的结构体msg_queue描述了对应key值消息队列的情况,而对应于用户空间的msqid_ds这个结构体,因此,可以操作msgid_ds这个结构体来操作消息队列。

当向消息队列发送消息那么队列会增加一条消息,读消息队列时,消息队列会把此消息删除掉。

2.  消息队列相关的接口函数

 key_t ftok(char*pathname,char proj);
//生成一个IPC对象标识符,外部key

int msgget(key_t key,int msgflg);
这个函数返回与key对应的消息队列的标识符。但有两种情况会创建队列:
(1)如果没有消息队列与key相对应,且msgflg包含了IPC_CREAT标志 (IPC_CREAT|IPC_EXCL)一起用如果消息队列存在那么errno会被设置为EEXIST
(2)key参数为IPC_PRIVATE,此时系统为消息队列指定一个key
调用成功后返回消息队列标识符,失败返回-1

int msgrcv(int msqid,struct msgbuf* msgp,int msgsz,long msgtype,int msgflg);
该系统调用从msgid代表的消息队列中读取一个消息,并把消息存储在msgp指向的msgbuf结构体中
第一个参数 msqid为消息队列描述字
第二个参数消息返回后存储的地址msgbuf*
第三个参数指定msgbuf中的第二个参数mtext的长度
第四个参数为消息类型
第五个参数为消息标识符msgflg
msgflg可以为以下值:
IPC_NOWAIT 如果没有满足条件的消息则会立即返回,errno=ENOMSG
IPC_EXCEPT 与msgtyp配合使用,返回队列中第一个类型不为msgtyp的消息 
IPC_NOERROR 如果队列中满足条件的消息内容大于请求的msgsz,则把消息截断,截断的部分将会丢失

成功返回读出消息实际字节数,否则返回-1

int msgsnd(int msqid,struct msgbuf* msgp,int msgsz,int msgflg);
向消息队列中发送一个消息,发送的消息存在msgbuf结构体指针中
msgsz指定发送消息的第二个参数mtext的大小
msgflg如下:
如果msgflg为IPC_NOWAIT如果消息队列没有足够的空间容纳发送的消息 ,则消息队列不会等待,否则会等待。

成功返回0,否则返回-1

int msgctl(int msqid,int cmd,struct msqid_ds *buf);
该函数的功能是对消息队列进行cmd工作,cmd 有三种方式: IPC_STAT,IPC_SET,IPC_RMID
msqid_ds是用户空间的类似于msg_queue的结构体。
(1)IPC_STAT 用来获取队列的信息,返回的信息存于buf指向的结构体中
(2)IPC_SET 用来设置消息队列的属性,设置的属性存于buf指向的结构体
(3) IPC_RMID 删除由msqid标识的消息队列
调用成功后返回0,否则返回-1

 

3. 消息队列实例

 

实例1:消息队列的属性测试

 
#include <sys/types.h>
#include <sys/msg.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
void msg_stat(int msgid,struct msqid_ds msg_info);//自定义的函数,获取消息队列相关属性信息
int main(){


int gflags,sflags,rflags;
key_t key;
int msgid;
int reval;
struct msgsbuf{//定义发送消息的结构体
int mtype;
char mtext[1];
}msg_sbuf;



struct msgmbuf{//定义接收消息的结构体
int mtype;
char mtext[10];
}msg_rbuf;

struct msqid_ds msg_ginfo,msg_sinfo;//定义消息队列信息的msqid_ds
char *msgpath="/etc/inittab";
key=ftok(msgpath,'a');//返回IPC对象标识符

gflags=IPC_CREAT|IPC_EXCL;//创建消息队列的标志,如果与key关联的消息队列存在,则errno为EEXIST
msgid=msgget(key,gflags|0666);//创建消息队列并返回消息队列标识符
if(msgid==-1){
perror("msg create error/n");
return ;
}

//创建消息队列中输出消息队列信息
msg_stat(msgid,msg_ginfo);
sflags=IPC_NOWAIT;//向消息队列发送消息,如果消息队列不能容纳发送的消息 则立即返回


msg_sbuf.mtype=10;
msg_sbuf.mtext[0]='f';
msg_sbuf.mtext[1]=0;
reval=msgsnd(msgid,&msg_sbuf,sizeof(msg_sbuf.mtext),sflags);

if(reval==-1){
perror("send message error/n");
}

//发送完消息后输出消息队列属性

msg_stat(msgid,msg_ginfo);

rflags=IPC_NOWAIT|MSG_NOERROR; //如果没有相应类型的消息立即返回,如果队列满足条件消息的内容长度大于请求的长度则把消息截断
reval=msgrcv(msgid,&msg_rbuf,4,10,rflags);
if(reval==-1){
printf("read msg error/n");
}

else {
printf("收到的信息为%c/n",msg_rbuf.mtext[0]);

}

//从队列读取后,输出消息队列的属性
msg_stat(msgid,msg_ginfo);

//设置队列属性
msg_sinfo.msg_perm.uid=8;
msg_sinfo.msg_perm.gid=8;
msg_sinfo.msg_qbytes=16388;//消息队列容纳的最多字节数

reval=msgctl(msgid,IPC_SET,&msg_sinfo);//进行设置

if(reval==-1){
perror("msg set info error");
return;
}

msg_stat(msgid,msg_ginfo);

//删除消息队列

reval=msgctl(msgid,IPC_RMID,NULL);
if(reval==-1){
// perror("unlink msg queue error/n");
return;
}
}


void msg_stat(int msgid,struct msqid_ds msg_info){
//输出消息队列的属性信息

int reval;
sleep(1);
reval=msgctl(msgid,IPC_STAT,&msg_info);
//把消息队列的属性输入到msqid_ds中
if(reval==-1){
perror("get msg info error/n");
return;
}

printf("/n");
printf("current number of bytes on queue is%d/n",msg_info.msg_cbytes);
printf("number of messages in queue is %d/n",msg_info.msg_qnum);
printf("max number of bytes on queue is %d/n",msg_info.msg_qbytes);
printf("pid of last msgsnd is %d/n",msg_info.msg_lspid);
printf("pid of last msgrcv is %d/n",msg_info.msg_lrpid);
printf("last msgsnd time is %s/n",ctime(&(msg_info.msg_stime)));
printf("last msgrcv time is %s/n",ctime(&(msg_info.msg_rtime)));
printf("last change time is %s/n",ctime(&(msg_info.msg_ctime)));
printf("msg uid is %d/n",msg_info.msg_perm.uid);
printf("msg gid is %d/n",msg_info.msg_perm.gid);
}

 

实例2: 消息队列实现双向通信


发送消息端:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/msg.h>
#include <sys/stat.h>
#define SNDMSG 1
#define RCVMSG 2
int main(){
int res;
int msqid;//消息队列标识符
//用于接收消息的结构体
struct msgrbuf{

int mtype;//定义消息类型
char mtext[100];//定义接收消息的长度
}msg_rbuf;

struct msgsbuf{
int mtype;
char mtext[100];
}msg_sbuf;
//首先返回一个IPC对象标识符
key_t key;//定义外键
key=ftok("/etc/inittab",10);
//新建一个消息队列
msqid=msgget(key,IPC_CREAT|IPC_EXCL|0666);//0666是权限,表示拥有者,组用户,其它用户具有读写权限,即能够向消息队列发送消息(写消息)与接收消息(读消息)
if(msqid==-1){
perror("消息队列已经存在");
exit(1);
}

//首先发送消息 
while(1){
printf("LUCY:");
msg_sbuf.mtype=SNDMSG;//消息类型
fgets(msg_sbuf.mtext,100,stdin);//从标准输入得到消息内容,阻塞等待输入
msgsnd(msqid,&msg_sbuf,strlen(msg_sbuf.mtext)+1,0);//如果消息队列满就会阻塞
msgrcv(msqid,&msg_rbuf,100,RCVMSG,0);//如果消息队列没有满足条件的消息就会阻塞
printf("Peter:%s/n",msg_rbuf.mtext);

}
exit(0);
}

 

 

接收消息端:

 


/**
接收消息端
**/

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/msg.h>
#define SNDMSG 1
#define RCVMSG 2
int main(){
int msqid;//消息队列标识符
//接收消息的结构体
struct msgrbuf{
int mtype;//消息类型
char mtext[100];//消息内容
}msg_rbuf;

//发送消息的结构体
struct msgsbuf{
int mtype;
char mtext[100];
}msg_sbuf;

//先接收后发送
key_t key;//IPC对象标识符
key=ftok("/etc/inittab",10);
//得到消息队列标识符
msqid=msgget(key,0);//返回消息队列标识符,消息队列已经创建它是一个随内核持续

if(msqid==-1){
perror("msgget error");
exit(1);
}

while(1){

msgrcv(msqid,&msg_rbuf,100,SNDMSG,0);//如果没有满足条件的消息队列那么就会阻塞
printf("LUCY:%s/n",msg_rbuf.mtext);
printf("Peter:");
fgets(msg_sbuf.mtext,100,stdin);//得到输入内容
msg_sbuf.mtext[strlen(msg_sbuf.mtext)-1]='/0';
msg_sbuf.mtype=RCVMSG;
msgsnd(msqid,&msg_sbuf,strlen(msg_sbuf.mtext)+1,0);//发送消息
}
}

 

运行结果:

发送方:
[root@localhost ~]# ./sndmsg
LUCY:hello
Peter:hi
LUCY:how are you
Peter:fine thank you
LUCY:

接收方:

[root@localhost ~]# ./rcvmsg
LUCY:hello
Peter:hi
LUCY:how are you
Peter:fine thank you
LUCY:/**
Peter:

对于消息队列,要记住以下几点:

(1) 消息队列是随内核持续的,只有在内核重起时,或者显示删除消息队列,这个队列才会消失

(2)消息队列是一个消息的链表,而msq_queue这个结构体描述了这个队列的基本信息,包括这个队列的key值,用户id,组id,等信息,与用户空间的msqid_ds相对应。

(3)操作消息队列,首先要得到这个消息队列的消息队列标识符,消息队列的消息是根据类型来发送与读取的。

(4)内核中记录消息队列的全局数据结构struct ipc_ids msg_ids可以访问到消息队列头的第一个成员struct kern_ipc_perm,这个结构中有一个key,能够与具体的消息队列对应起来。这样,内核就可以控制消息队列了。

关于消息队列就总结到这了,希望大家一起交流。

 

 参考:

 

结构msg_queue用来描述消息队列头,存在于系统空间:

struct msg_queue {
struct kern_ipc_perm q_perm;
time_t q_stime; /* last msgsnd time */
time_t q_rtime; /* last msgrcv time */
time_t q_ctime; /* last change time */
unsigned long q_cbytes; /* current number of bytes on queue */
unsigned long q_qnum; /* number of messages in queue */
unsigned long q_qbytes; /* max number of bytes on queue */
pid_t q_lspid; /* pid of last msgsnd */
pid_t q_lrpid; /* last receive pid */
struct list_head q_messages;
struct list_head q_receivers;
struct list_head q_senders;
};



结构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 */
};

 

 

 

 

 

阅读更多
个人分类: LINUX
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭