Linux初学第九天<进程间通信 二、消息队列>

一、消息队列概念及原理

       消息队列是消息的结构体,我们在发送消息的时候,是以结构体的方式发送,结构体的元素包含消息类型和消息内容。消息队列存放内核当中,一个消息队列由一个标识符(即队列ID)来标识;。

消息队列的特点

(1) 消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级
(2) 消息队列独立于发送与接收进程。进程终止时,消息队列的内容并不会被删除;
(3) 消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取,我们通常会使用按类型来读取消息。

二、消息队列相关API介绍

       这些函数都在以下头文件当中,使用时不要忘了引用:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

1. int msgget( key_t key,int flag);

       获取一个消息队列,成功返回0,失败返回 -1
参数介绍:
              1. key 是一个索引值,可以通过它来找到相应的消息队列;
              2. flag 为标志位,它其中包含了PIC_CREAT 宏,可以用它来创建一个消息队列;
返回值: 成功返回0;失败返回 -1

2. int msgsnd(int msgid, const void *ptr, size_t size ,int flag)

       消息发送函数。
参数介绍:
              1. msgid 需要发送的消息队列ID
              2. *ptr 需要发送的消息(消息结构体);
              3. size 消息内容的大小;
              4. flg 消息的标志位,0 为默认发送。

返回值: 成功返回 0;失败返回 -1

3.int msgrcv(int msgid, const void *ptr, size_t size ,long type,int flag)

       这个是读取消息函数。

参数介绍:
              1. msgid 需要读取的消息队列的ID;
              2. *ptr 读取的消息,读取到的消息放在这个,这个同样是结构体;
              3. size 想要读取消息内容的大小;
              4. type 要读取消息的类型,为保证读取正常,type应该和发送的保持一致;
              5. flg 消息的标志位,0 为默认方式读取。
返回值: 成功返回 0;失败返回 -1

4. int msgctl(int msgid, int cmd,struct msgid_ds *buf)

       消息控制函数,可以启动消息或移除等操作。
参数介绍:
               1. msgid 需要控制的消息队列的ID;
               2. cmd 控制指令;
               3. *buf 用来存放指令执行之后的一些信息,大部分情况都赋值:NULL。
返回值: 成功返回 0;失败返回 -1
        API介绍完毕,为了加深印象,我们可以用做两个小实验来熟悉一下它们的用法。

三、练手小实验

实验一、消息的读取及发送

        编写两个程序,暂且命名为 MSG_demo1.c 负责读取消息队列的数据,MSG_demo2.c 负责往消息队列写入消息。
MSG_demo1.c 代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <string.h>
#define msgkey 0x5466 //定义消息队列的索引值
//定义一个需要读取的消息结构体
struct msgbuf
{
        long msgtype; //消息的类型
        char ptr[128];//消息的内容 

};

int main()
{
        struct msgbuf Rcv_Buf; //定义一个结构体变量
        int MsgID=msgget(msgkey,IPC_CREAT|0777);//创建消息队列,并赋予可读可写可执行权限
        /*    调试信息      */
        if(MsgID==-1){               
                printf("MSG creat failuer!\n");
        }

        int nread=msgrcv(MsgID,&Rcv_Buf,sizeof(Rcv_Buf),123,0);//开始读取,注意 123 这个消息类型
        if(nread==-1){
                printf("msgrcv failuer!\n");
        }
        printf("MsgRcv data:%s\n",Rcv_Buf.ptr);//打印读取的消息内容
        return 0;
}

        如果该消息队列里面的消息是空的话,即消息链表中没有节点,这个程序会一直卡在读取函数及msgrcv 处,直到队列里面被发送了消息才会读取成功并结束程序。

MSG_demo2.c 代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <string.h>
#define msgkey 0x5466  //定义消息队列的索引,和上面的程序保持一致,否则会读取失败
//定义一个需要发送的消息结构体
struct msgbuf
{
        long mytype;
        char mtext[128];

};

int main()
{
        struct msgbuf Snd_Buf={123,"This Msg form Send!"};//定义消息的类型和内容
        int MsgID=msgget(msgkey,IPC_CREAT|0777); //创建消息队列,并赋予可读可写可执行权限
        /*    调试信息      */
        if(MsgID==-1){
                printf("MSG creat failuer!\n");
        }

        int nsend=msgsnd(MsgID,&Snd_Buf,strlen(Snd_Buf.mtext),0);//开始发送消息
        if(nsend==-1){
                printf("msgsend failuer!\n");
        }
        printf("MsgSend data succes!\n"); //打印调试是信息
        return 0;
}

       这个程序会往消息队列发送一个类型为123,内容为“This Msg form Send!"的消息。
实验结果:
在这里插入图片描述
       左边为MSG_demo1.c 运行的结果,右边为MSG_demo2.c 运行的结果,在 MSG_demo2.c 提示消息发送成功(MsgSend data succes),然后MSG_demo1.c成功读取了MSG_demo2.c发送的消息。

实验二、同时读写

       消息队列这种通信方式区别于管道的一点是:不分读取端和发送端,即读取消息和发送消息可以在同一程序下进行,我们以上面的两个程序 做一下修改,只要再定一个消息就可以了:
MSG_demo1.c 修改后代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <string.h>
#define msgkey 0x5466 
struct msgbuf
{
       long msgtype;
       char ptr[128];

};

int main()
{
       struct msgbuf Rcv_Buf;
       struct msgbuf Send_Buf={521,"This MSG form Rcv!"}; //定义一个需要发送的消息
       int MsgID=msgget(msgkey,IPC_CREAT|0777);
       if(MsgID==-1){
               printf("MSG creat failuer!\n");
       }

       int nread=msgrcv(MsgID,&Rcv_Buf,sizeof(Rcv_Buf),123,0);
       if(nread==-1){
               printf("msgrcv failuer!\n");
       }
       printf("MsgRcv data:%s\n",Rcv_Buf.ptr);

       int nSend=msgsnd(MsgID,&Send_Buf,strlen(Send_Buf.ptr),0);//开始发送
       
       if(nSend==-1){
               printf("msgsenf failuer!");
       }
       printf("Msg send success!\n"); //打印消息发送结果

       return 0;
}

这次,MSG_demo1.c 发送的是:类型为521,内容为“This MSG form Rcv!"的消息
MSG_demo2.c 修改后代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <string.h>
#define msgkey 0x5466
struct msgbuf
{
        long mytype;
        char mtext[128];

};

int main()
{
        struct msgbuf Snd_Buf={123,"This Msg form Send!"};
        struct msgbuf Rcv_Buf;  //定义一个接收的消息
        int MsgID=msgget(msgkey,IPC_CREAT|0777);
        if(MsgID==-1){
                printf("MSG creat failuer!\n");
        }

        int nsend=msgsnd(MsgID,&Snd_Buf,strlen(Snd_Buf.mtext),0);
        if(nsend==-1){
                printf("msgsend failuer!\n");
        }
        printf("MsgSend data succes!\n");

        int nread=msgrcv(MsgID,&Rcv_Buf,sizeof(Rcv_Buf.mtext),521,0);//开始读取类型为521的消息

        if(nread==-1){
                printf("Msg Rcv failuer!\n");
        }

        printf("Msg Rcv Data:%s\n",Rcv_Buf.mtext);

        return 0;
}

运行结果:
       同样把两个 .c 文件编译为 readsend;
在这里插入图片描述
       可以看到,当 ./send 运行之后,打印出"MsgSend data succes!",证明发送成功,而 ./read 同时打印了读取到的消息内容和发送成功信息。右边也读取到了 ./read 发送的消息内容。

实验三、键值获取和消息移除

       以上的两个实验,我们都是写死了一个索引 (#define msgkey 0x5466),但是实际编程中,内核是有自己的索引的,也就是索引节点。所以用键值来让内核分配索引,就不用我们自己定义索引,可以避免因为消息索引而出现读取错误的情况出现。下面介绍键值转换函数:

ftok函数:

       系统IPC键值的格式转换函数。系统建立IPC通信时,必须指定一个值,通常情况下,该ID 值通过 ftok函数得到。使用它,需要 <sys/type.h>和<sys/ipc.h> 两个头文件。
函数原型: key_t ftok(consy char * fname,int id)
参数说明:
                     1. *fname:就是你指定得文件名(已经存在的文件),一般使用当前目录 “.”;
                     2. id: 这是子序号。虽然时int 类型,但是使用8bits(1~255);
       在一般的UNIX实现中,是将文件的索引节点号取出,前面加上子序号得到key_t的返回值。如指定文件的索引节点号为65538,换算成16进制为 0x010002,而你指定的ID值为38,换算成16进制为0x26,则最后的key_t返回值为0x26010002。
查询文件索引节点号的方法是: ls -i
       而消息移除就是使用msgctl函数来实现,具体是个怎么样的实现方式,我们放在代码里面说。我们把实验一的代码修改一下,并且重命名为:MSG_demo1Key.c 和 MSG_demo2Key.c ;
MSG_demo1Key.c的代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <string.h>

struct msgbuf
{
        long msgtype;
        char ptr[128];

};
int main()
{
        key_t key;//定义键值索引
        struct msgbuf Rcv_Buf;

        key=ftok(".",20);//键值格式转换
        printf("Key=%x\n",key);//打印键值 16进制
        int MsgID=msgget(key,IPC_CREAT|0777);
        if(MsgID==-1){
                printf("MSG creat failuer!\n");
        }

        int nread=msgrcv(MsgID,&Rcv_Buf,sizeof(Rcv_Buf),123,0);
        if(nread==-1){
                printf("msgrcv failuer!\n");
        }
        printf("MsgRcv data:%s\n",Rcv_Buf.ptr);

        msgctl(MsgID,IPC_RMID,NULL);//移除消息队列
        return 0;
}

MSG_demo1Key.c的功能: 读取消息之后,把消息队列从内核中移除。

MSG_demo2Key.c的代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <string.h>

struct msgbuf
{
        long mytype;
        char mtext[128];

};

int main()
{
        key_t key;//定义键值索引
        struct msgbuf Snd_Buf={123,"This Msg form Send!"};

        key =ftok(".",20); //键值格式转换

        printf("Key=%x\n",key);//打印键值16进制
        int MsgID=msgget(key,IPC_CREAT|0777);
        if(MsgID==-1){
                printf("MSG creat failuer!\n");
        }
        int nsend=msgsnd(MsgID,&Snd_Buf,strlen(Snd_Buf.mtext),0);
        if(nsend==-1){
                printf("msgsend failuer!\n");
        }
        printf("MsgSend data succes!\n");

        return 0;
}

MSG_demo1Key.c的功能和原本的一样。
运行结果:
在这里插入图片描述
       两边的Key=14053f05。
       在实验过程中,我们应该知道,消息队列创建时,只是内核创建了消息链表。但是链表中没有任何的节点,发送消息其实就是往链表里面写入节点。为确保读取发送的消息,要保持两个一致:1.消息队列索引一致;2.消息类型一致。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值