【仿RabbitMQ实现消息队列项目】交换机智能路由、队列流量隔离、绑定信息精准定向、消息可靠投递——四模块协同打造低耦合消息系统!

文章目录

本篇摘要

  • 本篇将从对应的项目的交换机 队列 绑定信息 消息 这四个模块来介绍如何设计,以及如何实现(如根据标记位是否可持久化分为内存级别还是同时写入文件)。

一.项目背景

我们按照AMQP形式设计的,下面先简单认识下:

AMQP模型:消息队列的"快递规则"

1. AMQP是什么?

AMQP(高级消息队列协议)是消息队列的"交通规则",定义了生产者、交换机、队列、消费者如何协作,确保消息可靠传递,解决异步通信、系统解耦等问题。

2. 核心组件(类比快递站)

组件 作用 快递站
生产者 发送消息(如订单系统) 寄快递的用户
消费者 接收消息(如库存系统) 取快递的用户
交换机 按规则(路由键)分发消息到队列 快递分拣员(看标签分区域)
队列 存储消息的缓冲区 快递货架(存不同收件人包裹)

3. 消息流转流程

  1. 生产者 发消息(包裹+收件标签)→ 交换机 根据标签(路由键)分拣→ 放入对应队列 (货架)→ 消费者从队列取包裹处理。

    比如:标签"支付成功"→ 订单队列;标签"物流更新"→ 物流队列。

仿RabbitMQ项目:简易消息队列实现

1. 核心功能(对标RabbitMQ)

  • 多类型交换机:直连(Direct)、扇出(Fanout)、主题(Topic),支持灵活路由规则。
  • 消息持久化:消息存磁盘,重启不丢失(像快递站存包裹到仓库)。
  • 消费者确认:消费者处理完消息后反馈"已签收",避免丢件。
  • 队列绑定:队列通过路由键绑定到交换机,决定接收哪些消息。

2. 关键设计亮点

  • 轻量级协议:基于TCP自定义简单协议(类似AMQP但更简化),支持生产者/消费者通信。
  • 高可用模拟:通过本地文件或简易数据库存储消息,模拟分布式持久化(实际项目可扩展为Redis/MySQL)。
  • 灵活路由:主题交换机支持通配符(如"订单.*"匹配所有订单相关消息),像快递站按"省份+类型"分拣。

3. 适用场景

  • 异步解耦:订单系统生成订单后,通过消息队列通知库存、物流系统,避免直接调用阻塞。
  • 流量削峰:秒杀活动时,请求先入队列,后端服务按能力消费,防止系统崩溃。
  • 日志收集:多个服务将日志发到队列,由日志服务统一处理存储。

二.全局设计思想

首先需要消费客户端先进行队列申明以及订阅(此时有个自己的信道),然后服务端为它开一个对应的信道,然后消费客户就可以通过信道进行消息订阅(先进入订阅队列,等发布客户端发消息),发布客户端同理,进行(通过对应的信道底层依次调用服务端对应模块:交换机模块 队列模块等等,进行提供对应请求protobuf,等待服务端对应的接收请求的channel进行解析依次调用底层封装的对应模块提供接口进行操作)最后发布给消费客户端进行处理。

  • 注:先声明+订阅,然后再进行对应对列的消息的发布工作(中间的服务端相当于一个中转站的身份)

整体过程图展示:

  • 这里可能还是有点蒙,下面我们展开一步步展示设计,当整个项目疏通后就秒懂了。

三.基于交换机模块设计及实现

模块介绍

当客户端发布一条消息到交换机后,这条消息,应该被入队到该交换机绑定的哪些队列中?交换路由模块就是决定这件事情的。

首先对于交换机有三个模式(对于绑定的队列递送而言):

  • 广播:将消息入队到该交换机的所有绑定队列中。(也就是全发送)
  • 直接:将消息入队到绑定信息中 binding_key 与消息 routing_key 一致的队列中。(也就是直接匹配)
  • 主题:将消息入队到绑定信息中 binding_key 与 routing_key 是匹配成功的队列中。(也就是符合规则就发送)

因此需要定义不同的枚举来表示(这里我们定义在proto里):

cpp 复制代码
enum   ExchangeType {

    UNKNOWTYPE = 0;
    DIRECT = 1;
    FANOUT = 2;
    TOPIC = 3;
};

模块设计图:

详细设计代码

Exchange模块代码(点击即可跳转)

基于交换机模块测试

面基于gtest的全局模式的测试对exchange模块的增删查进行测试:

测试代码:

cpp 复制代码
#include "../mqserver/exchange.hpp"
#include <gtest/gtest.h>

ExchangeManager ::ptr emp;
class ExchangeTest : public testing::Environment
{
public:
    virtual void SetUp() override
    {
        emp = std::make_shared<ExchangeManager>("./data/meta.db");
    }
    virtual void TearDown() override
    {
        emp->Clear();
        std::cout << "最后的清理!!\n";
    }
};


//因为涉及文件的永久删除故测试应联合TEST(queue_test, insert_test)这个模块一起进行!

TEST(exchange_test, insert_test)
{

    google::protobuf::Map<std::string, std::string> map;
    map.insert({"k1", "v1"});
    emp->DeclareExchange("exchange1", msg::ExchangeType::DIRECT, true, false, map);
    emp->DeclareExchange("exchange2", msg::ExchangeType::DIRECT, true, false, map);
    emp->DeclareExchange("exchange3", msg::ExchangeType::DIRECT, true, false, map);
    emp->DeclareExchange("exchange4", msg::ExchangeType::DIRECT, true, false, map);
    ASSERT_EQ(emp->Size(), 4);
}

TEST(exchange_test, select_test)
{
    ASSERT_EQ(emp->ExchangeIsExists("exchange1"), true);
    ASSERT_EQ(emp->ExchangeIsExists("exchange2"), true);
    ASSERT_EQ(emp->ExchangeIsExists("exchange3"), true);
    ASSERT_EQ(emp->ExchangeIsExists("exchange4"), true);
    Exchange::ptr exp = emp->SelectExchange("exchange2");
    ASSERT_NE(exp.get(), nullptr);
    ASSERT_EQ(exp->name, "exchange2");
    ASSERT_EQ(exp->durable, true);
    ASSERT_EQ(exp->auto_delete, false);
    ASSERT_EQ(exp->type, msg::ExchangeType::DIRECT);
    ASSERT_EQ(exp->GetArgs(), std::string("k1=v1&"));
}

TEST(exchange_test, remove_test)
{
    emp->DeleteExchange("exchange2");
    Exchange::ptr exp = emp->SelectExchange("exchange2");
    ASSERT_EQ(exp.get(), nullptr);
    ASSERT_EQ(emp->ExchangeIsExists("exchange2"), false);
}

int main(int argc, char *argv[])
{
    testing::InitGoogleTest(&argc, argv);
    testing::AddGlobalTestEnvironment(new ExchangeTest());
    int ret = RUN_ALL_TESTS();
    if (!ret)
        std::cout << "成功的一次全局测试\n";
    return 0;
}

测试效果:

对应SQlite3数据库查询(gtest测试最后不进行整体Clear操作):

  • 全部符合预期。

四.基于队列模块设计及实现

模块介绍

  • 这里与上面的交换机模块类似,即通过队列管理者管理者文件内的队列集合以及内存中的队列集合(提供的接口也差不多)。

全局视角看待大致过程:

详细设计代码

MsgQueue模块代码(点击即可跳转)

基于队列模块测试

面基于gtest的全局模式的测试对queue模块的增删查进行测试:
测试代码:

cpp 复制代码
#include "../mqserver/msgqueue.hpp"
#include <gtest/gtest.h>

MsgqueueManager ::ptr emp;
class MsgqueueTest : public testing::Environment
{
public:
    virtual void SetUp() override
    {
        emp = std::make_shared<MsgqueueManager>("./data/meta.db");
    }
    virtual void TearDown() override
    {
       // emp->Clear();
        std::cout << "最后的清理!!\n";
    }
};


//因为涉及文件的永久删除故测试应联合TEST(queue_test, insert_test)这个模块一起进行!

TEST(msgqueue_test, insert_test)
{

 google::protobuf::Map<std::string, std::string> map;
    map.insert({"k1", "v1"});
    emp->DeclareMsgqueue("mq1", 1, true, false, map);
    emp->DeclareMsgqueue("mq2", 1, true, false, map);
    emp->DeclareMsgqueue("mq3", 1, true, false, map);
    emp->DeclareMsgqueue("mq4", 1, true, false, map);
    ASSERT_EQ(emp->Size(), 4);
}

TEST(msgqueue_test, select_test)
{
    ASSERT_EQ(emp->MsgqueueIsExists("mq1"), true);
    ASSERT_EQ(emp->MsgqueueIsExists("mq2"), true);
    ASSERT_EQ(emp->MsgqueueIsExists("mq3"), true);
    ASSERT_EQ(emp->MsgqueueIsExists("mq4"), true);
    Msgqueue::ptr exp = emp->SelectMsgqueue("mq2");
    ASSERT_NE(exp.get(), nullptr);
    ASSERT_EQ(exp->name, "mq2");
    ASSERT_EQ(exp->exclusive, 1);
    ASSERT_EQ(exp->auto_delete, false);
    ASSERT_EQ(exp->exclusive, 1);
    ASSERT_EQ(exp->GetArgs(), std::string("k1=v1&"));
}

TEST(msgqueue_test, remove_test)
{
    emp->DeleteMsgqueue("mq2");
   Msgqueue::ptr exp = emp->SelectMsgqueue("mq2");
    ASSERT_EQ(exp.get(), nullptr);
    ASSERT_EQ(emp->MsgqueueIsExists("mq2"), false);
}

int main(int argc, char *argv[])
{
    testing::InitGoogleTest(&argc, argv);
    testing::AddGlobalTestEnvironment(new MsgqueueTest());
    int ret = RUN_ALL_TESTS();
    if (!ret)
        std::cout << "成功的一次全局测试\n";
    return 0;
}

测试效果:

对应SQlite3数据库查询(gtest测试最后不进行整体Clear操作):

  • 符合预期。

五.基于绑定信息模块设计及实现

模块介绍:

这里我们设计的是绑定信息(一个交换机对应有一个和很多队列的一个绑定信息集合)。

绑定信息具体成员:

  • 交换机名称。
  • 队列名称。
  • Binding_key(进行对应交换机路由匹配找队列有关)。

如下:

cpp 复制代码
struct Binding
{

  using ptr = std::shared_ptr<Binding>;
  // 一组绑定关系
  std::string exchange_name;
  std::string msgqueue_name;
  std::string binding_key;
  Binding(const std::string &ename, const std::string &qname, const std::string &key) : exchange_name(ename), msgqueue_name(qname), binding_key(key) {}
};

下面还是基于上面的那俩模块进行的基于文件与内存级别实现(这里只有对应的交换机和队列都是持久化的,绑定信息才能持久化并存储文件中)。

其次就是这里我们实现的是一个交换机对应的是一个map(绑定的队列与对应binding的映射);但是为什么这么实现?

这里绑定采取这种映射而不是直接拿交换机映射binding(这样每次删除指定队列先关绑定需要遍历1次交换机数组+每次对应的binding) 而采取交换机--Queue+binding映射方式,只需要遍历一遍交换机直接拿到second进行删除second[Queue]即可。

如下:

cpp 复制代码
using MsgqueueBindingMap = std::unordered_map<std::string, Binding::ptr>;  // MsgQueue---binding
using ExchangeMBMap = std::unordered_map<std::string, MsgqueueBindingMap>; // Exchange---mb

下面剩下的模式几乎和上面他俩相同:

基于内存+文件管理:

  • 内存管理模块就是进行绑定信息的增删,查取并恢复进内存,根据队列名/交换机名,进行相关绑定信息删除。
  • 管理者就是对外提供调用接口:绑定,解绑,根据交换机名/队列名移除绑定信息,获取交换机所有相关绑定信息map,获取特定绑定信息等。

全局视角看待大致过程:

详细设计代码

Binding模块代码(点击即可跳转)

基于绑定信息模块测试

面基于gtest的全局模式的测试对binding模块的增删查进行测试:
测试代码:

cpp 复制代码
#include "../mqserver/binding.hpp"
#include <gtest/gtest.h>

BindingManager::ptr bmp;

class bindingTest : public testing::Environment
{
public:
    virtual void SetUp() override
    {
        bmp = std::make_shared<BindingManager>("./data/meta.db");
    }
    virtual void TearDown() override
    {
        // bmp->Clear();
    }
};
 

//因为涉及文件的永久删除故测试应联合TEST(queue_test, insert_test)这个模块一起进行!


TEST(queue_test, insert_test) {
     bmp->Bind("exchange1", "queue1", "news.music.#", true);
     bmp->Bind("exchange1", "queue2", "news.sport.#", true);
     bmp->Bind("exchange1", "queue3", "news.gossip.#", true);
     bmp->Bind("exchange2", "queue1", "news.music.pop", true);
     bmp->Bind("exchange2", "queue2", "news.sport.football", true);
    bmp->Bind("exchange2", "queue3", "news.gossip.#", true);
     ASSERT_EQ(bmp->Size(), 6);
}
TEST(queue_test, select_test) {
    ASSERT_EQ(bmp->Exists("exchange1", "queue1"), true);
    ASSERT_EQ(bmp->Exists("exchange1", "queue2"), true);
    ASSERT_EQ(bmp->Exists("exchange1", "queue3"), true);
    ASSERT_EQ(bmp->Exists("exchange2", "queue1"), true);
    ASSERT_EQ(bmp->Exists("exchange2", "queue2"), true);
    ASSERT_EQ(bmp->Exists("exchange2", "queue3"), true);
    ASSERT_EQ(bmp->Exists("exchange3", "queue3"), false);

  Binding::ptr bp = bmp->GetBinding("exchange1", "queue1");
    ASSERT_NE(bp.get(), nullptr);
    ASSERT_EQ(bp->exchange_name, std::string("exchange1"));
    ASSERT_EQ(bp->msgqueue_name, std::string("queue1"));
    ASSERT_EQ(bp->binding_key, std::string("news.music.#"));
}

// TEST(queue_test, recovery_test) {
//     ASSERT_EQ(bmp->Exists("exchange1", "queue1"), true);
//     ASSERT_EQ(bmp->Exists("exchange1", "queue2"), true);
//     ASSERT_EQ(bmp->Exists("exchange1", "queue3"), true);
//     ASSERT_EQ(bmp->Exists("exchange2", "queue1"), true);
//     ASSERT_EQ(bmp->Exists("exchange2", "queue2"), true);
//     ASSERT_EQ(bmp->Exists("exchange2", "queue3"), true);
// }

// TEST(queue_test, select_exchange_test)
// {
//     MsgqueueBindingMap mqbm = bmp->GetExchangeBindings("exchange1");
//     ASSERT_EQ(mqbm.size(), 3);
//     ASSERT_NE(mqbm.find("queue1"), mqbm.end());
//     ASSERT_NE(mqbm.find("queue2"), mqbm.end());
//     ASSERT_NE(mqbm.find("queue3"), mqbm.end());
// }

// TEST(queue_test, Unbind_test)
// {

//     bmp->UnBind("exchange1", "queue1");
//     MsgqueueBindingMap mqbm = bmp->GetExchangeBindings("exchange1");
//     ASSERT_EQ(mqbm.find("queue1"), mqbm.end());
//      mqbm = bmp->GetExchangeBindings("exchange2");
//     ASSERT_NE(mqbm.find("queue1"), mqbm.end());
// }

// TEST(queue_test, remove_queue_test)
// {
//     bmp->RemoveMsgQueueBindings("queue1");
//     ASSERT_EQ(bmp->Exists("exchange1", "queue1"), false);
//     ASSERT_EQ(bmp->Exists("exchange2", "queue1"), false);
// }
TEST(queue_test, remove_exchange_test)
{
    bmp->RemoveExchangeBindings("exchange1");
    ASSERT_EQ(bmp->Exists("exchange1", "queue1"), false);
    ASSERT_EQ(bmp->Exists("exchange1", "queue2"), false);
    ASSERT_EQ(bmp->Exists("exchange1", "queue3"), false);
}
int main(int argc, char *argv[])
{

    testing::AddGlobalTestEnvironment(new bindingTest);

    testing::InitGoogleTest(&argc, argv);

    int ret = RUN_ALL_TESTS();
    if (!ret)
        std::cout << "成功的一次全局测试\n";

        std::shared_ptr<bindingTest> ();
    return 0;
}

基于插入,查找,操作测试:

数据库内:

  • 符合预期。

文件恢复到内存的操作测试:

解除绑定测试:

  • 数据库中确实被移除了,符合预期。

根据交换机与队列分别进行移除测试:


  • 内存效果与数据库效果都符合预期。

六.基于消息模块设计及实现

模块介绍

  • 消息包含: 有效载荷(属性 内容 持久化标志),长度(在文件中的长度),偏移量(在文件中)。

具体如下(proto文件):

cpp 复制代码
enum   DeliveryMode {
    UNKNOWMODE = 0;
    UNDURABLE = 1;
    DURABLE = 2;
};

//这里BasicProperties在外面而Payload进行嵌套:
//BasicProperties需要外界单独创建穿进去进行消息插入故能外界直接创建,但是payload只能通过先创建Message对象在进行创建更合乎情理
message BasicProperties{
      string id = 1;
    DeliveryMode delivery_mode = 2;
    string routing_key = 3;
};

  
message Message{
  message Payload {
        BasicProperties properties = 1;
        string body = 2;
        string valid = 3;
    };
    Payload payload = 1;
    uint32 offset = 2;//对应有效载荷的文件偏移量(方便移除写0覆盖)
    uint32 length = 3;//有效载荷长度(方便移除写0覆盖)
};

本模块相对于前面三个确实有点复杂(代码也是比较多的),下面梳理下一些要点:

以队列形式管理该队列的内存消息+文件消息:

消息是否持久化与它要插入的队列是否持久化有关。

1·文件操作:

  • 每个队列有个文件用于存储消息(四字节长度+有效载荷(属性 内容 是否有效))。

  • 删除操作 :这里只是往文件对应位置标记"0"为无效(把传进来的message对应位置标记无效再写回去),而有恢复操作 :就是把文件有效的数据(写入文件后对应位置是"1"的消息)转移到临时文件再重命名成对应消息文件即可),这种方式避免了文件频繁被操作带来不便(这里恢复操作是符合特定要求(持久化的消息总量大于2000, 且其中有效比例低于50%则需要持久化))。

  • 其他细节见代码。

2·queuemessage部分:

  • 这里提供插入 删除 恢复 回收 取出消息 等操作(持久化的话,文件也跟着操作)。
  • 对于删除:每次删除完(也就是标记完),都要去检查对应的文件有无效消息占比,进行是否回收等(这里恢复不仅要文件,还要与内存同步(比如文件变了对应的msg的偏移量等就变了,需要及时同步更新内存的。))
  • 其他细节见代码。

3·消息总的管理者:

  • 提供初始化与销毁队列,插入队列消息 取出队列消息 对队列消息进行应答等操作。
  • 这里接口其实就是对之前封装的进行调用即可,具体见代码。

下面博主手绘了一张关系图(绝对秒懂):

详细设计代码

Message模块代码(点击即可跳转)

基于消息模块测试

面基于gtest的全局模式的测试对message模块的增删查进行测试:
测试代码:

cpp 复制代码
#include "../mqserver/message.hpp"
#include <gtest/gtest.h>

MessageManager ::ptr mp;
class MsgqueueTest : public testing::Environment
{
public:
    virtual void SetUp() override
    {
        mp = std::make_shared<MessageManager>("./message");
    }
    virtual void TearDown() override
    {
        // mp->Clear();
        std::cout << "最后的清理!!\n";
    }
};
// 新增消息测试:新增消息,然后观察可获取消息数量,以及持久化消息数量
// TEST(message_test, insert_test)
// { // 创建q1:
//     mp->InitQueueMessage("queue1");
//     msg::BasicProperties properties;
//     properties.set_id(Uuid::uuid());
//     properties.set_delivery_mode(msg::DeliveryMode::DURABLE);
//     properties.set_routing_key("news.music.pop");
//     mp->Insert("queue1", &properties, "Hello World-1", 1);
//     mp->Insert("queue1", nullptr, "Hello World-2", 1);
//   mp->Insert("queue1", nullptr, "Hello World-3", 1);
//    mp->Insert("queue1", nullptr, "Hello World-4", 1);
//     mp->Insert("queue1", nullptr, "Hello World-5", 0);
//     ASSERT_EQ(mp->GetAll_count("queue1"), 5);
//     ASSERT_EQ(mp->Total_count("queue1"), 4);
//     ASSERT_EQ(mp->Durable_count("queue1"), 4);
//     ASSERT_EQ(mp->Waitack_count("queue1"), 0);
//     // mp->DestroyQueueMessage("queue1");
// }
// 获取消息测试:获取一条消息,然后在不进行确认的情况下,查看可获取消息数量,待确认消息数量,以及测试消息获取的顺序
//  TEST(message_test, select_test)
// {
//     ASSERT_EQ(mp->GetAll_count("queue1"), 5);
//     Messageptr msg1 = mp->Front("queue1");
//     ASSERT_NE(msg1.get(), nullptr);
//     ASSERT_EQ(msg1->payload().body(), std::string("Hello World-1"));
//     ASSERT_EQ(mp->GetAll_count("queue1"), 4);
//     ASSERT_EQ(mp->Waitack_count("queue1"), 1);

//     Messageptr msg2 = mp->Front("queue1");
//     ASSERT_NE(msg2.get(), nullptr);
//     ASSERT_EQ(msg2->payload().body(), std::string("Hello World-2"));
//     ASSERT_EQ(mp->GetAll_count("queue1"), 3);
//     ASSERT_EQ(mp->Waitack_count("queue1"), 2);
// }

// 删除消息测试:确认一条消息,查看持久化消息数量,待确认消息数量
//  TEST(message_test, delete_test) {
//      ASSERT_EQ(mp->GetAll_count("queue1"), 5);
//      Messageptr msg1 = mp->Front("queue1");
//      ASSERT_NE(msg1.get(), nullptr);
//      ASSERT_EQ(msg1->payload().body(), std::string("Hello World-1"));
//      ASSERT_EQ(mp->GetAll_count("queue1"), 4);
//       ASSERT_EQ(mp->Waitack_count("queue1"), 1);
//      mp->Ack("queue1", msg1->payload().properties().id());
//      ASSERT_EQ(mp->Waitack_count("queue1"), 0);
//      ASSERT_EQ(mp->Durable_count("queue1"), 3);
//      ASSERT_EQ(mp->Total_count("queue1"), 4);

// }

// TEST(message_test, clear) {
//        mp->DestroyQueueMessage("queue1");

// }


//先要insert让他写入queue1文件,再次跑动加载:
TEST(message_test2, recovery_test)
{
    mp->InitQueueMessage("queue1");
    ASSERT_EQ(mp->GetAll_count("queue1"), 4);
    Messageptr msg1 = mp->Front("queue1");
    ASSERT_NE(msg1.get(), nullptr);
    ASSERT_EQ(msg1->payload().body(), std::string("Hello World-1"));
    ASSERT_EQ(mp->GetAll_count("queue1"), 3);
    ASSERT_EQ(mp->Waitack_count("queue1"), 1);

    Messageptr msg2 = mp->Front("queue1");
    ASSERT_NE(msg2.get(), nullptr);
    ASSERT_EQ(msg2->payload().body(), std::string("Hello World-2"));
    ASSERT_EQ(mp->GetAll_count("queue1"), 2);
    ASSERT_EQ(mp->Waitack_count("queue1"), 2);

   
    Messageptr msg3 = mp->Front("queue1");
    ASSERT_NE(msg3.get(), nullptr);
    ASSERT_EQ(msg3->payload().body(), std::string("Hello World-3"));
    ASSERT_EQ(mp->GetAll_count("queue1"), 1);
    ASSERT_EQ(mp->Waitack_count("queue1"), 3);

        Messageptr msg4 = mp->Front("queue1");
    ASSERT_NE(msg4.get(), nullptr);
    ASSERT_EQ(msg4->payload().body(), std::string("Hello World-4"));
    ASSERT_EQ(mp->GetAll_count("queue1"), 0);
    ASSERT_EQ(mp->Waitack_count("queue1"), 4);
}

int main(int argc, char *argv[])
{
    testing::InitGoogleTest(&argc, argv);
    testing::AddGlobalTestEnvironment(new MsgqueueTest());
    int ret = RUN_ALL_TESTS();
    if (!ret)
        std::cout << "成功的一次全局测试\n";
    return 0;
}

测试效果:

基于插入的测试:


  • 内存级别与文件级别都符合预期。

基于取出+顺序的测试(这里没有确认以及更新故文件消息不变):



  • 全部符合预期。

基于确认应答后文件对应被标记操作测试:



  • 文件中对应的消息确实也被标记0了。

下面测试下从文件中恢复这四条消息,然后判断下对应取出的顺序等是否合理:



  • 因为取出后没确认应答,故文件中还是标记的1,符合预期。

七.本篇小结

本篇介绍了这个项目的server大模块的几个分支,基于书写到测试并未发现问题,后面将更新对应的 路由匹配 consumer channel connection broker(服务器)等模块,欢迎订阅!

相关推荐
Ronin3053 小时前
【Linux网络】NAT、代理服务、内网穿透
linux·网络·智能路由器·内网穿透·nat·代理服务器·内网打洞
DeeplyMind3 小时前
AMD rocr-libhsakmt分析系列3-1: Apertures
linux·amdgpu·rocm·kfd·rocr
无奈笑天下5 小时前
银河麒麟桌面OS使用分区编辑器将/backup分区删除并扩容至根分区参考教程
linux·数据库·经验分享·编辑器
CheungChunChiu12 小时前
Linux 内核设备模型与驱动框架解析 ——以 rk-pcie 为例
linux·运维·ubuntu
列逍12 小时前
Linux进程(三)
linux·运维·服务器·环境变量·命令行参数
水天需01013 小时前
VS Code Ctrl+Shift+V 预览 Markdown 无效的解决方案
linux
赖small强15 小时前
【Linux C/C++开发】Linux 平台 Stack Protector 机制深度解析
linux·c语言·c++·stack protector·stack-protector·金丝雀机制
陌路2016 小时前
Linux42 守护进程
linux
liteblue16 小时前
DEB包解包与打包笔记
linux·笔记