多进程编程通信——消息队列

多进程编程通信——消息队列


管道的通信方式是效率低的,因此管道不适合进程间频繁地交换数据。

消息队列的通信模式则高效许多。消息队列提供了异步的通信协议,是内核中的一个优先级队列,多个进程通过访问同一个队列,进行添加结点或者获取结点实现通信。

操作流程

  1. 创建消息队列,即在内核中创建一个优先级队列
  2. 多个进程可以向队列中添加或获取节点
  3. 节点被全部读取时,删除消息队列

比如,进程A 要给 进程 B发送消息,进程A 把数据放在对应的消息队列后就可以正常返回了,B 进程需要的时候再去读取数据就可以了。同理,B 进程要给 A 进程发送消息也是如此。通信方式和管道类似,不过消息队列是保存在内核中的消息链表

在发送数据时,消息队列会分成多个独立的数据单元,也就是消息体(数据块),消息体是用户自定义的数据类型,消息体的数据类型由发送方和接收方提前约定好,所以每个消息体都是固定大小的存储块,不像管道是无格式的字节流数据。

如果进程从消息队列中读取了消息体,内核就会把这个消息体删除。

实现

消息队列常常保存在链表结构中。拥有权限的进程可以向消息队列中写入或读取消息。

POSIX消息队列头文件

#include <fcntl.h>           // For O_* constants
#include <sys/stat.h>    // For mode constants
#include <mqueue.h>

消息队列API接口:

// 创建消息队列实例。 name: 消息队列名称。成功返回0,失败返回-1,错误码存于error中
mqd_t mq_open(const char *name, int oflag,  mode_t mode, struct mq_attr *attr);

// 无限阻塞方式接收消息。成功返回0,失败返回-1
mqd_t mq_receive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned *msg_prio);

// 指定超时时间阻塞方式接收消息。成功返回0,失败返回-1
mqd_t mq_timedreceive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned *msg_prio, const struct timespec *abs_timeout);

// 无限阻塞方式发送消息。成功返回0,失败返回-1
mqd_t mq_send(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned msg_prio);

// 指定超时时间阻塞方式发送消息。成功返回0,失败返回-1
mqd_t mq_timedsend(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned msg_prio, const struct timespec *abs_timeout);

// 关闭消息队列。 成功返回0,失败返回-1
mqd_t mq_close(mqd_t mqdes);

// 分离消息队列。 成功返回0,失败返回-1
mqd_t mq_unlink(const char *name);

实现代码

sender.c

#include <fcntl.h>  // For O_* constants
#include <mqueue.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>  // For mode constants
#include <unistd.h>

#define MQ_MSG_MAX_SIZE 512  // 最大消息长度
#define MQ_MSG_MAX_ITEM 5    // 最大消息数目

static mqd_t s_mq;

typedef struct _msg_data
{
    char buf[128];
    int num;
}msg_data_t;


void send_data(void)
{
    static int num = 0;
    msg_data_t send_data = {0};

    ++num;
    strcpy(send_data.buf, "Hello Randy");
    send_data.num = num;
    int ret = mq_send(s_mq, (char*)&send_data, sizeof(send_data), 0);
    if (ret < 0)
    {
        perror("mq_send error");
        return;
    }
    printf("send msg = %s, num = %d\n", send_data.buf, send_data.num);
}

int main(void) {
    int ret = 0;
    struct mq_attr attr;

    // 创建消息队列
    memset(&attr, 0, sizeof(attr));
    attr.mq_maxmsg = MQ_MSG_MAX_ITEM;
    attr.mq_msgsize = MQ_MSG_MAX_SIZE;
    attr.mq_flags = 0;
    s_mq = mq_open("/randy", O_CREAT | O_RDWR, 0777, &attr);
    if (-1 == s_mq) {
        perror("mq_open error");
        return -1;
    }

    for (size_t i = 0; i < 10; i++) {
        send_data();
        sleep(1);
    }

    mq_close(s_mq);

    return 0;
}

receiver.c

#include <fcntl.h>  // For O_* constants
#include <mqueue.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>  // For mode constants
#include <unistd.h>

#define MQ_MSG_MAX_SIZE 512  // 最大消息长度
#define MQ_MSG_MAX_ITEM 5    // 最大消息数目

static mqd_t s_mq;

typedef struct _msg_data
{
    char buf[128];
    int num;
}msg_data_t;


void send_data(void)
{
    static int num = 0;
    msg_data_t send_data = {0};

    ++num;
    strcpy(send_data.buf, "Hello Randy");
    send_data.num = num;
    int ret = mq_send(s_mq, (char*)&send_data, sizeof(send_data), 0);
    if (ret < 0)
    {
        perror("mq_send error");
        return;
    }
    printf("send msg = %s, num = %d\n", send_data.buf, send_data.num);
}

int main(void) {
    int ret = 0;
    struct mq_attr attr;

    // 创建消息队列
    memset(&attr, 0, sizeof(attr));
    attr.mq_maxmsg = MQ_MSG_MAX_ITEM;
    attr.mq_msgsize = MQ_MSG_MAX_SIZE;
    attr.mq_flags = 0;
    s_mq = mq_open("/randy", O_CREAT | O_RDWR, 0777, &attr);
    if (-1 == s_mq) {
        perror("mq_open error");
        return -1;
    }

    for (size_t i = 0; i < 10; i++) {
        send_data();
        sleep(1);
    }

    mq_close(s_mq);

    return 0;
}

编译并执行:

gcc -o send sender.c -lrt
gcc -o receive receiver.c -lrt
    
两个终端分别执行
./send
./reveive

优缺点

消息队列本身是异步的,它允许接收者在消息发送很长时间后再取回消息,这和大多数通信协议是不同的。例如WWW中使用的HTTP协议(HTTP/2之前)是同步的,因为客户端在发出请求后必须等待服务器回应。

然而,很多情况下我们需要异步的通信协议。比如,一个进程通知另一个进程发生了一个事件,但不需要等待回应。但消息队列的异步特点,也造成了一个缺点,就是接收者必须轮询消息队列,才能收到最近的消息。

和信号相比,消息队列能够传递更多的信息。与管道相比,消息队列提供了有格式的数据,这可以减少开发人员的工作量。但消息队列仍然有大小限制。

消息队列除了可以当不同线程或进程间的缓冲外,更可以透过消息队列当前讯息数量来侦测接收线程或进程效能是否有问题。

消息队列通信过程中,存在用户态与内核态之间的数据拷贝开销,因为进程写入数据到内核中的消息队列时,会发生从用户态拷贝数据到内核态的过程,同理另一进程读取内核中的消息数据时,会发生从内核态拷贝数据到用户态的过程。

Reference

  • https://mp.weixin.qq.com/s?__biz=MzUxMjEyNDgyNw==&mid=2247514907&idx=1&sn=0989d590cd479eee5b9d86cb7356ce1a&chksm=f96bc9efce1c40f943823ee8354573134b84a3cc842e96fb4412fe985f13ece3ec5c7c816fb7&mpshare=1&scene=1&srcid=0816z8wo8qDR630oSFqCITSZ&sharer_sharetime=1692179392154&sharer_shareid=9282c33bd664bcf868293e52a214b059#rd

  • https://cloud.tencent.com/developer/article/1736932

  • https://huaweicloud.csdn.net/63564439d3efff3090b5cdec.html#FIFO_108

  • https://xiaolincoding.com/os/4_process/process_commu.html#%E7%AE%A1%E9%81%93

  • https://zh.wikipedia.org/zh-hans/%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97


>>>>> 欢迎关注公众号【三戒纪元】 <<<<<

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值