14-3-进程间通信-消息队列

前面提到的管道pipe和fifo是半双工的,在某些场景不能发挥作用;

接下来描述的是消息队列(一种全双工的通信方式);

比如消息队列可以实现两个进程互发消息(不像管道,只能1个进程发消息,1个进程读消息);

注意:消息队列是由内核进行管理的,初级开发者可以先不了解其机制。

初级开发者应该关注:如何把消息加到队列?如何从队列获取消息。

一、消息队列的基本概念

1.特点

消息队列是消息的链接表,存放在内核中。一个消息队列由标识符(即队列ID)来标识。

(1)消息队列是面向记录的,其中的消息具有特定的格式及特定的优先级;

(2)消息队列独立于发送和接收进程。进程终止时,消息队列及其内容并不会被删除;

(3)消息队列可以实现消息的随机查询;消息不一定要 以先进先出的次序读取,也可以按消息的类型读取;(初级开发者一般 采用按消息的类型读取数据)

2.API

头文件:#include<sys/msg.h>

(1)创建或打开消息队列(成功返回队列ID,失败返回-1)

int msgget(key_ t key, int msgflg); 
参数:

    key:消息队列的标识符
    msgflg:创建的标志,例如IPC_CREAT
    IPC_CREAT:如果不存在就创建:按位或上一个权限(8进制的数字)

返回值:

    成功:返回队列ID
    失败:返回-1,并设置erron

注意:以下两种勤情况,msgget将创建1个新的消息队列
    (1)如果没有与键值key相对应的消息队列,并且flag中 包含了IPC_CREAT标志位。
    (2)key参数为IPC_PRIVATE.
    

(2)添加消息(成功返回0,失败返回-1)

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
msgid:消息队列ID
msgp:指向msgbuf 的指针,用来指定发送的消息 
msgsz:要发送消息的长度,消息内容的长度
msgflg:创建标记,如果指定IPC_NOWAIT,失败会立即返回
        0:阻塞发送
        IPC_NOWAIT:非阻塞发送

返回值:
    成功:返回0
    失败:返回-1,并设置erron

 

 

(3)读取消息(成功返回消息的数据长度,失败返回-1)

 int msgrcv(int msqid, void *msgp, sizet msgsz, long msgtyp, int msgflg); 
msgid:消息队列ID
msgp:指向msgbuf的指针,用来接收消息
msgsz:要接收消息的长度
        注意:参数msgsz 指定由msgp 参数指向的结构的成员mtext的最大大小(以字节为单位),msgtyp 也有,3种方式:
msgtyp:接收消息的方式
        1. msgtyp = 0:读取队列中的第一条消息(不在乎当前对头元素时什么消息类型,将他当作普通队列来处理)
        2. msgtyp > 0:读取队列中类型为msgtyp 的第一条消息。(就是读取对列元素中第一个香蕉)除非在msgflg中指定了MSG_ EXCEPT, 将读取类型不等于msgtyp 绝对值的第一条消息
        3. msgtyp< : 0:读取队列中最小类型小于或等于msgtyp 绝对值的第一条消息
msgflg:创建标记,如果指定IPC_ NOWAIT,获取失败会立刻返回

返回值:
    成功返回实际读取消息的字节数,,
    失败返回-1,并设置erron

注意 :msgflg置为0时,是以默认的方式读数据,如果读不到数据会阻塞。

 (4)控制消息队列(成功返回0,失败返回-1)

int msgctl(int msqid, int cmd, struct msqid_ ds *buf);
参数:
msqid:消息队列ID
cmd:控制命令,
        例如IPC_ RMID,删除命令 ,
        IPC STAT,获取状态
buf:存储消息队列的相关信息的buf

返回值:
    成功根据不同的cmd有不同的返回值,
    失败返回-1,并设置erron

3.基本开发流程

.针对消息队列,
若A想对B进行通信(假设A从B读信息),
A需要做如下操作:
(1)获取队列
(2)A读B发送过来的消息
B要做的操作:
(1)创建队列
(2)B向A发送消息(即写数据放到队列中)

4.基于API的基本实验

实验要求:创建两个进程(进程A和进程B),两个进程相互发送和接收消息。

以下是进程A的步骤:

(1)创建/打开 消息队列(key=0x1234)

(2)将要发送的消息:

struct DuiLie_data{
        long type;
        char send_buff[128];
};

        struct DuiLie_data STU_sendbuff = {111,"hello\n"};

注意:STU_sendbuff 的type要和msgrcv()的倒数第二个参数一致;

消息队列独立于发送和接收进程。进程终止时,消息队列及其内容并不会被删除;

(3)创建消息队列,返回值为消息队列ID;msgID = msgget(0x1234,IPC_CREAT|0777);

        判断返回值msgID;

        返回值为-1,则表示队列 创建失败;

        否则创建成功;

(5)通过队列发送消息:

send_flag =msgsnd(msgID,&STU_sendbuff,strlen(STU_sendbuff.send_buff),0);

        判断返回值send_flag ;

        返回值为-1,表示发送失败;

        返回值为0,表示发送成功;

(6)通过队列读取消息:msgrcv(msgID,&STU_readbuff,sizeof(STU_readbuff),111,0);

参数1:消息队列的ID

参数2:一个指针,指向自定义的结构体,该结构体有2个成员(成员1:消息类型,成员2:消息内容)

参数3:参数2所指向的结构体的大小。

参数 4:

        等于0,读取队列中的第一条消息(不在乎当前对头元素时什么消息类型,将他当作普通队列来处理);

        大于0,读取队列中类型为msgtyp 的第一条消息。(就是读取对列元素中第一个消息)除非在msgflg中指定了MSG_ EXCEPT, 将读取类型不等于msgtyp 绝对值的第一条消息。

        小于0,读取队列中最小类型小于或等于msgtyp 绝对值的第一条消息

参数5:msgflg置为0时,是以默认的方式读数据,如果读不到数据会阻塞。

------

进程B的步骤与进程 A 的步骤基本一致,不再赘述。

main.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
struct DuiLie_data{
        long type;
        char send_buff[128];
};

int main()
{
        int msgID;
        char send_flag;
//      char send_buff[128] = "12345\n";
        struct DuiLie_data STU_readbuff;
        struct DuiLie_data STU_sendbuff = {111,"hello\n"};

        msgID = msgget(0x1223,IPC_CREAT|0777);

        if( msgID == 0)
        {
                printf("creat failed\n");
        }
        else
        {
                printf("duilie creat success! msgID = %x\n",msgID);
                send_flag = msgsnd(msgID,&STU_sendbuff,strlen(STU_sendbuff.send_buff),0);

                if(send_flag == 0)
                {
                        printf("send success\n");
                }
                else if(send_flag == -1)
                {
                        printf("send failed\n");
                }
                printf("A will get data from queue\n");
                msgrcv(msgID,&STU_readbuff,sizeof(STU_readbuff),111,0);
                printf("return from get B:%s\n",STU_readbuff.send_buff);

        }
        return 0;
}

send.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
struct DuiLie_data{
        long type;
        char send_buff[128];
};

int main()
{
        int msgID;
        char send_flag;
        struct DuiLie_data STU1 = {111,"123456789\n"};
        //char read_buff[128];
        struct DuiLie_data readSTU ;

        msgID = msgget(0x1223,IPC_CREAT|0777);

        if( msgID == -1)
        {
                printf("creat failed\n");
        }
        else
        {
                printf("duilie creat success! msgID = %x\n",msgID);
                printf("B will get data from queue\n");
                msgrcv(msgID,&readSTU,sizeof(readSTU.send_buff),111,0);
                printf("return from get A:%s\n",readSTU.send_buff);

                send_flag = msgsnd(msgID,&STU1,sizeof(STU1),0);
                if(send_flag == 0)
                {
                        printf("send success\n");
                }
                else if(send_flag == -1)
                {
                        printf("send failed\n");
                }


        }
        return 0;
}

运行结果如图所示:

 5.补充的知识点

(1)键值的生成及消息队列的移除

系统IPC键值的格式转换函数:ftok;

建立IPC通信(消息队列,信号量,共享内存)时,必须指定1个ID值。通常情况下,该ID通过ftok函数得到;
 

头文件:#include <sys/types.h>

        #include <sys//ipc.h>


函数原型:key_t ftok(const char *fname,int id)

fname :是开发者指定的文件名(已存在的文件名),一般使用当前目录;


如:
ket_t key;

key = ftok(".",1);//这样就是把当前目录设置为fname;

id是子序号,虽然是整型,但只能 使用8bits(1~255)

在一般的UNIX实现中,是将文件的索引节点号取出,前面加上子序号得到key_t的返回值。

如指定文件的索引节点号为65538,换算成16进制为0x010002,而你指定的ID值为38,换算成16进制为0x26,则最后的key_t返回值为0x26010002。

 可以使用 ls-ai查看 当前目录的索引节点;

如图当前 目录的索引节点为1179979

 修改前面的实验;

把 msgID = msgget(0x1223,IPC_CREAT|0777);//手动生成键值

改为用ftok生成键值

    key_t key;
    key = ftok(".",'z');
    msgID = msgget(key,IPC_CREAT|0777);
    printf("key :%x\n",key);

运行结果如下:

自动生成的键值为7a01014b

 (2)msgctl移除队列

msgctl(msgID,IPC_RMID,NULL);//实现移除队列

两个文件都添加这句话,实现移除队列

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值