进程间通信之消息队列MSG

1. system-v IPC简介

  消息队列、共享内存和信号量统称为system-V IPC,V是罗马数字5,是UNIX的AT&T分支中的一个版本,一般习惯称之为IPC对象。这些对象的操作接口比较相似,在系统中它们都使用一种名为key的值也唯一标识,而且它们是“可持续”资源–它们被创建以后,不会因为进程的退出而消息,而会持续存在,除非调用特殊的函数或者命令来删除。
 进程每次“打开”一个IPC对象,就会获得一个表征这个对象的ID,进而再使用这个ID来操作这个对象。IPC对象的key是唯一的,但是ID是可变的。key类似文件的路径名,ID类似文件的描述符
系统IPC对象

2. 函数ftok()函数介绍

  IPC对象的键值key是怎么产生的呢?理论上它就是一个整数,一般用函数ftok()函数来产生,函数ftok()的接口规范如下:

函数项描述函数项说明
函数功能获取一个当前未用的IPC的key
头文件#include <sys/types.h>#include <sys/ipc.h>
原型key_t ftok(const char *pathname, int proj_id);
参数1pathname一个合法的路径,比如/目录或者./当前目录
参数2proj_id工程ID,非0,仅仅使用低8位,通常传入一个unsigned char
返回值成功:返回合法未用的键值失败:返回-1

这个函数需要注意以下几点:

  1. 如果两个参数相同,那么产生的key值也相同
  2. 第一个参数一般取进程所在的目录,因为一个项目中需要通信的几个进程通常会出现在一个工程目录中
  3. 如果同一个目录中的进程需要使用超过一个IPC对象,可以通过第2个参数来标识
  4. 系统中只有一套key标识,也就是说,不同类型的IPC对象也不能重复

可以使用如下命令来查看或者删除系统中的IPC对象

  • 查看消息队列:ipcs -q
  • 查看共享内存:ipcs -m
  • 查看信号量:ipcs -s
  • 查看所有IPC对象:ipcs -a
  • 删除指定的消息队列:ipcs -q MSG_ID 或者 ipcrm -Q msg_key
  • 删除执行的共享内存:ipcs -m SHM_ID 或者 ipcrm -M shm_key
  • 删除指定的信号量:ipcs -s SEM_ID 或者 ipcrm -S sem_key

IPC对象

3. 消息队列MSG

 我们熟知的PIPE或者FIFO管道的通信机制有一个缺点:读端无法读取一个指定类型的数据,只能将管道中的数据不加筛选的读出来,因为这些管道中的数据是没有标识的,所以读端只能按照顺序依次读取,因此多进程之间不同数据类型数据之间的传递,除非使用多个管道,否则无法使用一条管道完成该需求。
 那么怎么解决这个问题呢?消息队列是种带有数据标识的特殊管道,因为每一段写入的数据都带有数据标识,所以对于读端来讲,只需要筛选出自己关注的带某些带标识的数据就可以了

消息队列的使用方法如下

发送者:

  1. 获取消息队列的ID
  2. 将数据放入一个附带有标识的特殊结构体中,发送给消息队列

接收者:

  1. 获取消息队列的ID
  2. 将关注的带消息标识的数据读出

当发送者和接收者不再使用消息队列时,及时删除消息队列以释放系统资源

4. 消息队列相关接口函数

获取消息队列ID的函数原型:

int msgget(key_t key, int msgflg);

发送函数原型:

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

接收函数原型:

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

使用发送和接收函数时需要注意以下几点:

  1. 发送消息时,消息必须被组织成以下形式:
struct msgbuf{
    long msg_type;      //消息的标识,大于0的整数
    char msg_text[0];      //消息的正文,具体的数据
};

//也就是说,发送出去的消息必须是一个long类型数据打头,作为消息的标识,正文部分数据没有要求
  1. 消息的标识可以是任意长度的整数,但是不能是0L
  2. 参数msgsz是消息正文部分的大小,不包含消息标识

5. 消息队列代码示例

示例展示了一个进程Jack如何使用消息队列给另一个进程Rose发送消息的过程,以及如何使用msgctl()函数删除不再使用的消息队列

Jack向消息队列中发消息

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

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

#include <signal.h>

#define MSG_SIZE 64         //单个消息的最大字节数
#define PROJ_PATH "."       //使用当前路径来产生消息队列的Key值
#define PROJ_ID 1

#define J2R 1L              //Jack发送给Rose的消息标识
#define R2J 2L              //Rose发送给Jack的消息标识

struct T_msgbuf{              //带标识的消息结构体
    long msg_type;
    char msg_text[MSG_SIZE];
};

int main(int argc, char**argv){
    key_t key = ftok(PROJ_PATH, PROJ_ID);       //获取Key
    
    int msgid = msgget(key, IPC_CREAT | 0666); //获取消息队列ID

    struct T_msgbuf message1;
    struct T_msgbuf message2;
    bzero(&message1, sizeof(message1));
    bzero(&message2, sizeof(message2));
    message1.msg_type = J2R;         //指定消息标识
    strncpy(message1.msg_text, "I love you, Rose!!!\n", MSG_SIZE);
    message2.msg_type = 8L;         //指定消息标识
    strncpy(message2.msg_text, "Other message!!!\n", MSG_SIZE);

    int i;
    for(i = 0; i < 10; i++){
        int res1 = msgsnd(msgid, &message1, strlen(message1.msg_text), 0);
        int res2 = msgsnd(msgid, &message2, strlen(message2.msg_text), 0);
        if((res1 != 0) || (res2 != 0)){
            perror("msgsend() error");
            exit(1);
        }
    }

    return 0;
}

Rose从消息队列中读消息

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <string.h>
#include <errno.h>

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

#define MSG_SIZE 64         //单个消息的最大字节数
#define PROJ_PATH "."       //使用当前路径来产生消息队列的Key值
#define PROJ_ID 1

#define J2R 1L              //Jack发送给Rose的消息标识
#define R2J 2L              //Rose发送给Jack的消息标识

struct T_MsgBuf{              //带标识的消息结构体
    long msg_type;
    char msg_text[MSG_SIZE];
};

int main(int argc, char**argv){
    key_t key = ftok(PROJ_PATH, PROJ_ID);
    int mgsid = msgget(key, IPC_CREAT | 0666);  //获取消息队列的ID
    struct T_MsgBuf msgbuf;
    bzero(&msgbuf, sizeof(msgbuf));

    int i;
    for(i = 0; i < 10; i++){
        if(-1 == msgrcv(mgsid, &msgbuf, MSG_SIZE, J2R, 0)){
            perror("msgrcv()");
            exit(1);
        }
        printf("from msg: %s\n", msgbuf.msg_text);
    }
     
    msgctl(mgsid, IPC_RMID, NULL);      //删除消息队列

    return 0;
}

Rose收到Jack的消息

 消息队列使用简单,但它和管道一样,都需要“代理人”的进程通信机制:内核充当了这个代理人,内核为使用者发呢配内存、边界检查、设置阻塞,以及各种权限控制,使得我们用起来非常省心,省力。但任何事都是有代价的:代理人机制使得它们的效率不是很高,因为两个进程的消息传输不是直接的,而是要经过内核转发,因此消息队列不适合用来传输海量数据

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
很高兴为您解答这个问题。消息队列是一种常用的进程间通信方式。它可以使不同进程之间通过消息传递来通信。递归实现进程间通信的原理是,在消息队列内部将消息作为一个树形结构进行组织,每个节点代表一条消息,每个节点中包含消息、发送方进程ID、接收方进程ID等信息。这样,当一个进程发送消息时,消息将通过递归方式到达消息队列尾部,并被追加到树形结构的最底层;当一个进程接收消息时,它将从头部开始遍历这个树形结构,寻找合适的消息。下面是一个简单的消息队列递归实现进程间通信的demo,可以供您参考: ```python import queue class MsgNode: def __init__(self, msg, sender, receiver): self.msg = msg self.sender = sender self.receiver = receiver self.children = [] def send_msg(msg, sender, receiver, msg_queue): if not msg_queue: msg_queue.append(MsgNode(msg, sender, receiver)) else: _send_msg(msg, sender, receiver, msg_queue[0]) def _send_msg(msg, sender, receiver, node): if node.receiver == receiver: node.children.append(MsgNode(msg, sender, receiver)) else: for child in node.children: _send_msg(msg, sender, receiver, child) def receive_msg(receiver, msg_queue): if not msg_queue: return None else: msg_node = _receive_msg(receiver, msg_queue[0]) if msg_node: msg_queue[0].children.remove(msg_node) return msg_node.msg def _receive_msg(receiver, node): if node.receiver == receiver: if node.children: return node.children[0] else: return node else: for child in node.children: msg_node = _receive_msg(receiver, child) if msg_node: return msg_node return None # 示例代码 msg_queue = [] send_msg("Hello, World!", "P1", "P2", msg_queue) send_msg("How are you?", "P2", "P1", msg_queue) print(receive_msg("P1", msg_queue)) print(receive_msg("P1", msg_queue)) ``` 以上是本人提供的示例代码,仅供参考。如果您有任何疑问或者需要进一步的帮助,欢迎继续追问。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

博可睿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值