├── .gitignore ├── HareMQ ├── demo │ ├── asynchronous │ │ ├── async.cc │ │ ├── makefile │ │ ├── packaged_task.cc │ │ └── promise.cc │ ├── gtest │ │ ├── makefile │ │ ├── test_ASSERT.cc │ │ ├── test_GLOBAL.cc │ │ └── test_SUIT.cc │ ├── log.hpp │ ├── muduo │ │ ├── dict_client.cc │ │ ├── dict_server.cc │ │ ├── makefile │ │ ├── proto_client.cc │ │ ├── proto_server.cc │ │ ├── request.pb.cc │ │ ├── request.pb.h │ │ └── request.proto │ ├── protobuf │ │ ├── contacts.pb.cc │ │ ├── contacts.pb.h │ │ ├── contacts.proto │ │ ├── main.cc │ │ └── makefile │ ├── sqlite │ │ ├── db.hpp │ │ ├── makefile │ │ └── test_sql.cc │ └── thread_pool │ │ ├── makefile │ │ ├── test.cc │ │ └── thread_pool.hpp ├── mqclient │ ├── async_worker.hpp │ ├── channel.hpp │ ├── client.cc │ ├── config │ │ └── for_test.cfg │ ├── connection.hpp │ ├── consume_client.cc │ ├── consumer.hpp │ ├── makefile │ └── publish_client.cc ├── mqcommon │ ├── helper.hpp │ ├── logger.hpp │ ├── msg.pb.cc │ ├── msg.pb.h │ ├── msg.proto │ ├── protocol.pb.cc │ ├── protocol.pb.h │ ├── protocol.proto │ └── thread_pool.hpp ├── mqserver │ ├── binding.hpp │ ├── broker_server.hpp │ ├── channel.hpp │ ├── connection.hpp │ ├── consumer.hpp │ ├── exchange.hpp │ ├── makefile │ ├── message.hpp │ ├── queue.hpp │ ├── route.hpp │ ├── server.cc │ └── virtual_host.hpp └── mqtest │ ├── binding_test.cc │ ├── channel_simple_test.cc │ ├── connection_simple_test.cc │ ├── consumer_test.cc │ ├── data-binding │ └── meta.db │ ├── exchange_test.cc │ ├── file_test.cc │ ├── host1 │ └── host1.db │ ├── host_test.cc │ ├── makefile │ ├── map_helper_test.cc │ ├── msg_test.cc │ ├── queue_test.cc │ ├── route_test.cc │ ├── server_simple_test.cc │ └── test_logger.cc ├── LICENSE ├── README-cn.md ├── README.md ├── assets ├── 1.png ├── 2.png ├── 3.png ├── 4.png ├── 5.png ├── 6.png └── 7.png ├── docs ├── assets │ ├── 1.png │ ├── 10.png │ ├── 11.png │ ├── 12.png │ ├── 13.png │ ├── 14.png │ ├── 15.png │ ├── 16.png │ ├── 17.png │ ├── 18.png │ ├── 19.png │ ├── 2.png │ ├── 20.png │ ├── 21.png │ ├── 22.png │ ├── 23.png │ ├── 3.png │ ├── 4.png │ ├── 5.png │ ├── 6.png │ ├── 7.png │ ├── 8.png │ └── 9.png ├── asynchronous-en.md ├── asynchronous.md ├── documentation-en.md ├── documentation.md ├── gtest-en.md ├── gtest.md ├── muduo-en.md ├── muduo.md ├── proto-en.md ├── proto.md ├── sqlite-en.md ├── sqlite.md ├── thread_pool-en.md ├── thread_pool.md ├── use-assets │ ├── 1.png │ ├── 10.png │ ├── 11.png │ ├── 12.png │ ├── 13.png │ ├── 2.png │ ├── 3.png │ ├── 4.png │ ├── 5.png │ ├── 6.png │ ├── 7.png │ ├── 8.png │ └── 9.png ├── work-assets │ ├── 1.png │ ├── 2.png │ ├── 3.png │ ├── 4.png │ ├── 5.png │ ├── 6.png │ ├── 7.png │ ├── 8.png │ └── 9.png ├── work-en.md └── work.md └── env └── test.cc /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | # vscode 35 | .vscode 36 | 37 | # libs 38 | 39 | HareMQ/tools/* -------------------------------------------------------------------------------- /HareMQ/demo/asynchronous/async.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | #include "../log.hpp" 8 | #include 9 | #include 10 | #include 11 | 12 | int add(int num1, int num2) { 13 | LOG(INFO) << "called: add(int, int)" << std::endl; 14 | return num1 + num2; 15 | } 16 | int main() { 17 | // std::async(func, ...), std::async(policy, func, ...) 18 | std::future res = std::async(std::launch::async, add, 11, 12); 19 | // std::future res = std::async(std::launch::deferred, add, 11, 12); 20 | LOG(INFO) << "get res: " << res.get() << std::endl; 21 | return 0; 22 | } -------------------------------------------------------------------------------- /HareMQ/demo/asynchronous/makefile: -------------------------------------------------------------------------------- 1 | .PHONY:all 2 | all: async promise packaged_task 3 | 4 | async:async.cc 5 | g++ -o $@ $^ -std=c++11 6 | promise: promise.cc 7 | g++ -o $@ $^ -std=c++11 8 | packaged_task: packaged_task.cc 9 | g++ -o $@ $^ -std=c++11 10 | 11 | .PHONY:clean 12 | clean: 13 | rm -f async promise packaged_task -------------------------------------------------------------------------------- /HareMQ/demo/asynchronous/packaged_task.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | #include "../log.hpp" 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | /** 15 | * packaged_task 的使用 16 | * packaged_task 是一个模版类,实例化的对象可以对一个函数进行二次封装 17 | * packaged_task 可以通过 get_future 获取一个 future 对象,来获取封装的这个函数的异步执行结果 18 | */ 19 | 20 | int add(int num1, int num2) { 21 | std::this_thread::sleep_for(std::chrono::seconds(3)); 22 | LOG(INFO) << "called: add(int, int)" << std::endl; 23 | return num1 + num2; 24 | } 25 | 26 | int main() { 27 | // std::packaged_task task(add); 28 | // std::future fu = task.get_future(); 29 | 30 | // task(11, 22) 可以这样调用 31 | // 但是不能当作函数对象,也就是说,不能这样调用 std::thread thr(task) 这样是不行的 32 | // std::thread thr(task) 和 std::async(std::launch::async, task) 本质是一样的,都是异步操作,async里面也是起线程 33 | // std::async(std::launch::async, task, 11, 22); // 不能这样调用 34 | 35 | // 但是我们可以把task定义成一个指针,传递到线程中,然后进行解引用执行 36 | // 如果用普通指针,容易出现指针的生命周期问题,所以使用智能指针 37 | 38 | auto ptask = std::make_shared>(add); 39 | std::thread thr([ptask]() { 40 | (*ptask)(11, 12); // 调用这个函数 41 | }); 42 | std::future fu = ptask->get_future(); 43 | LOG(INFO) << "result: " << fu.get() << std::endl; 44 | thr.join(); 45 | return 0; 46 | } -------------------------------------------------------------------------------- /HareMQ/demo/asynchronous/promise.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | #include "../log.hpp" 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | int add(int num1, int num2, std::promise& prom) { 14 | std::this_thread::sleep_for(std::chrono::seconds(3)); 15 | LOG(INFO) << "called: add(int, int)" << std::endl; 16 | prom.set_value(num1 + num2); 17 | return num1 + num2; 18 | } 19 | 20 | // int add(int num1, int num2, int* result) { 21 | // std::this_thread::sleep_for(std::chrono::seconds(3)); 22 | // LOG(INFO) << "called: add(int, int)" << std::endl; 23 | // *result = num1 + num2; 24 | // return num1 + num2; 25 | // } 26 | 27 | 28 | int main() { 29 | // 通过 get_future 来建立 prom和fu 的关联 30 | std::promise prom; 31 | std::future fu = prom.get_future(); 32 | // int result = 0; 33 | std::thread thr(add, 11, 22, std::ref(prom)); 34 | int res = fu.get(); 35 | LOG(INFO) << "sum: " << res << std::endl; 36 | thr.join(); 37 | return 0; 38 | } -------------------------------------------------------------------------------- /HareMQ/demo/gtest/makefile: -------------------------------------------------------------------------------- 1 | .PHONY:all 2 | all: test_assert test_global test_suit 3 | 4 | test_assert:test_ASSERT.cc 5 | g++ -o $@ $^ -std=c++11 -lgtest 6 | test_global:test_GLOBAL.cc 7 | g++ -o $@ $^ -std=c++11 -lgtest 8 | test_suit:test_SUIT.cc 9 | g++ -o $@ $^ -std=c++11 -lgtest 10 | .PHONY:clean 11 | clean: 12 | rm -f test_assert test_global test_suit -------------------------------------------------------------------------------- /HareMQ/demo/gtest/test_ASSERT.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | #include "../log.hpp" 8 | #include 9 | #include 10 | 11 | /** 12 | * 断言宏的使用: 13 | * ASSERT_ 断言失败则推出 14 | * EXPECT_ 断言失败则继续运行 15 | * 注意: 16 | * 断言宏,必须在单元测试宏函数中使用 17 | */ 18 | 19 | TEST(test1, MYTEST) { 20 | int age = 20; 21 | ASSERT_GT(age, 18); 22 | std::cout << "OK" << std::endl; 23 | } 24 | 25 | int main(int argc, char** argv) { 26 | testing::InitGoogleTest(&argc, argv); 27 | auto res = RUN_ALL_TESTS(); 28 | LOG(INFO) << "res: " << res << std::endl; 29 | return 0; 30 | } -------------------------------------------------------------------------------- /HareMQ/demo/gtest/test_GLOBAL.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | #include "../log.hpp" 8 | #include 9 | #include 10 | 11 | class MyEnv : public testing::Environment { 12 | public: 13 | virtual void SetUp() override { 14 | LOG(INFO) << "before testing, init ..." << std::endl; 15 | } 16 | virtual void TearDown() override { 17 | LOG(INFO) << "after testing, clearing ..." << std::endl; 18 | } 19 | }; 20 | TEST(MyEnv, test1) { 21 | LOG(INFO) << "unit test1" << std::endl; 22 | } 23 | TEST(MyEnv, test2) { 24 | LOG(INFO) << "unit test2" << std::endl; 25 | } 26 | int main(int argc, char** argv) { 27 | testing::AddGlobalTestEnvironment(new MyEnv); 28 | testing::InitGoogleTest(&argc, argv); 29 | auto res = RUN_ALL_TESTS(); 30 | LOG(INFO) << "res: " << res << std::endl; 31 | return 0; 32 | } -------------------------------------------------------------------------------- /HareMQ/demo/gtest/test_SUIT.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | 8 | #include "../log.hpp" 9 | #include 10 | #include 11 | #include 12 | 13 | class MyTest : public testing::Test { 14 | public: 15 | virtual void SetUp() override { } 16 | virtual void TearDown() override { } 17 | static void SetUpTestCase() { 18 | LOG(INFO) << "before each suit test, init ..." << std::endl; 19 | } 20 | static void TearDownTestCase() { 21 | LOG(INFO) << "after each suit test, clearing ..." << std::endl; 22 | } 23 | 24 | public: 25 | std::unordered_map __map; 26 | }; 27 | 28 | TEST_F(MyTest, insert_test) { 29 | __map.insert({ "hello", "nihao" }); 30 | __map.insert({ "bye", "zaijian" }); 31 | } 32 | TEST_F(MyTest, size_test) { 33 | ASSERT_EQ(__map.size(), 2); 34 | } 35 | 36 | int main(int argc, char** argv) { 37 | testing::InitGoogleTest(&argc, argv); 38 | auto res = RUN_ALL_TESTS(); 39 | LOG(INFO) << "res: " << res << std::endl; 40 | return 0; 41 | } -------------------------------------------------------------------------------- /HareMQ/demo/log.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | #ifndef __YUFC_LOG__ 8 | #define __YUFC_LOG__ 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | enum STATUES // 日志等级 15 | { 16 | INFO, 17 | DEBUG, 18 | WARNING, 19 | ERROR, 20 | FATAL 21 | }; 22 | 23 | 24 | 25 | // 定义颜色代码 26 | #define RESET "\033[0m" 27 | #define RED "\033[1;31m" // 加粗红色 28 | #define GREEN "\033[1;32m" // 加粗绿色 29 | #define YELLOW "\033[1;33m" // 加粗黄色 30 | #define BLUE "\033[1;34m" // 加粗蓝色 31 | #define MAGENTA "\033[1;35m" // 加粗洋红 32 | 33 | // 根据日志等级获取相应颜色 34 | inline const char* GetColor(const std::string& level) { 35 | std::map m = { { "INFO", 0 }, { "DEBUG", 1 }, { "WARNING", 2 }, { "ERROR", 3 }, { "FATAL", 4 } }; 36 | switch (m[level]) { 37 | case INFO: 38 | return BLUE; 39 | case DEBUG: 40 | return GREEN; 41 | case WARNING: 42 | return YELLOW; 43 | case ERROR: 44 | return MAGENTA; 45 | case FATAL: 46 | return RED; 47 | default: 48 | return RESET; 49 | } 50 | } 51 | 52 | // LOG() << "message" 53 | inline std::ostream& Log(const std::string& level, const std::string& file_name, int line) { 54 | // 添加日志等级 55 | std::string levelTag = std::string("[") + level + "]"; 56 | std::string coloredLevelTag = std::string(GetColor(level)) + levelTag + RESET; 57 | std::string message = coloredLevelTag; 58 | // 添加报错文件名称 59 | message += "["; 60 | message += file_name; 61 | message += "]"; 62 | // 添加当前文件的行数 63 | message += "["; 64 | message += std::to_string(line); 65 | message += "]"; 66 | // cout 本质内部是包含缓冲区的 67 | std::cout << message << " "; // 不要endl进行刷新 68 | return std::cout; 69 | } 70 | 71 | // 这种就是开放式的日志 72 | #define LOG(level) Log(#level, __FILE__, __LINE__) 73 | 74 | #endif -------------------------------------------------------------------------------- /HareMQ/demo/muduo/dict_client.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | #ifndef __YUFC_DEMO_DICT_CLIENT_USE_MUDUO__ 8 | #define __YUFC_DEMO_DICT_CLIENT_USE_MUDUO__ 9 | 10 | #include "../log.hpp" 11 | #include "muduo/base/CountDownLatch.h" 12 | #include "muduo/net/EventLoopThread.h" 13 | #include "muduo/net/TcpClient.h" 14 | #include "muduo/net/TcpConnection.h" 15 | 16 | /* 注意,客户端连接服务器是需要阻塞等待连接建立成功之后才返回的,所以才需要使用 CountDownLatch */ 17 | 18 | class translate_client { 19 | private: 20 | muduo::CountDownLatch __latch; 21 | muduo::net::EventLoopThread __loop_thread; 22 | muduo::net::TcpClient __client; 23 | muduo::net::TcpConnectionPtr __conn; 24 | 25 | private: 26 | // 连接成功的回调 27 | void onConnection(const muduo::net::TcpConnectionPtr& conn) { 28 | if (conn->connected()) { 29 | // 如果连接建立成功了,就计数器-- 30 | __latch.countDown(); 31 | LOG(INFO) << "connection to server success" << std::endl; 32 | __conn = conn; // 保存这个连接 33 | } else { 34 | // 连接关闭 35 | LOG(INFO) << "connection to server end" << std::endl; 36 | __conn.reset(); // 清空 37 | } 38 | } 39 | // 收到服务器发来的消息的回调 40 | void onMessage(const muduo::net::TcpConnectionPtr& conn, muduo::net::Buffer* buf, muduo::Timestamp) { 41 | std::cout << "server# " << buf->retrieveAllAsString() << std::endl; 42 | } 43 | 44 | public: 45 | translate_client(const std::string& sip, int sport) 46 | : __latch(1) 47 | , __client(__loop_thread.startLoop(), 48 | muduo::net::InetAddress(sip, sport), 49 | "translate_client") { 50 | __client.setConnectionCallback(std::bind(&translate_client::onConnection, 51 | this, std::placeholders::_1)); 52 | __client.setMessageCallback(std::bind(&translate_client::onMessage, 53 | this, std::placeholders::_1, 54 | std::placeholders::_2, 55 | std::placeholders::_3)); 56 | } 57 | void connect() { 58 | __client.connect(); // 这里是立即返回的,但是我们需要控制阻塞等待! 59 | __latch.wait(); 60 | } 61 | bool send(const std::string& mesg) { 62 | // 因为muduo里面的所有操作都是异步的,不知道什么时候可能连接都关闭了,所以是要判断的 63 | if (__conn->connected()) { 64 | __conn->send(mesg); 65 | return true; 66 | } 67 | return false; 68 | } 69 | }; 70 | 71 | #endif 72 | 73 | int main() { 74 | translate_client client("127.0.0.1", 8085); 75 | client.connect(); 76 | while (1) { 77 | std::string buf; 78 | std::cin >> buf; 79 | client.send(buf); 80 | } 81 | return 0; 82 | } -------------------------------------------------------------------------------- /HareMQ/demo/muduo/dict_server.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | #ifndef __YUFC_DEMO_DICT_SERVER_USE_MUDUO__ 8 | #define __YUFC_DEMO_DICT_SERVER_USE_MUDUO__ 9 | 10 | // #include "../../libs/muduo/include/muduo/net/EventLoop.h" 11 | // #include "../../libs/muduo/include/muduo/net/TcpConnection.h" 12 | // #include "../../libs/muduo/include/muduo/net/TcpServer.h" 13 | #include "../log.hpp" 14 | #include "muduo/net/EventLoop.h" 15 | #include "muduo/net/TcpConnection.h" 16 | #include "muduo/net/TcpServer.h" 17 | #include 18 | 19 | class translate_server { 20 | private: 21 | muduo::net::EventLoop __base_loop; // 基本的事件循环(这个要传给server, 所以要放前面) 22 | muduo::net::TcpServer __server; // 服务器对象 23 | private: 24 | // 新连接建立成功时的回调函数 25 | // 会在一个连接建立成功,以及关闭的时候被调用 26 | void onConnection(const muduo::net::TcpConnectionPtr& conn) { 27 | if (conn->connected() == true) 28 | LOG(INFO) << "new connection!" << std::endl; 29 | else 30 | LOG(INFO) << "connection close" << std::endl; 31 | } 32 | // 通信连接收到请求时的回调函数 33 | void onMessage(const muduo::net::TcpConnectionPtr& conn, muduo::net::Buffer* buf, muduo::Timestamp) { 34 | std::string str = buf->retrieveAllAsString(); 35 | LOG(INFO) << "recv a mesg: " << str << std::endl; 36 | std::string resp = translate(str); 37 | conn->send(resp); // 向客户端进行发送 38 | } 39 | std::string translate(const std::string& str) { 40 | // 用个简单例子就行 41 | static std::unordered_map __dict_map = { 42 | { "hello", "nihao" }, { "nihao", "hello" } 43 | }; 44 | auto it = __dict_map.find(str); // 这里的str包含了\n,需要额外处理,不过这里只是为了学习使用服务器,不处理了 45 | if (it == __dict_map.end()) 46 | return "unknown\n"; 47 | return it->second; 48 | } 49 | 50 | public: 51 | translate_server(int port) 52 | : __server(&__base_loop, 53 | muduo::net::InetAddress("0.0.0.0", port), 54 | "translate_server", 55 | muduo::net::TcpServer::kReusePort) { 56 | __server.setConnectionCallback(std::bind(&translate_server::onConnection, 57 | this, std::placeholders::_1)); // 设置回调 58 | __server.setMessageCallback(std::bind(&translate_server::onMessage, 59 | this, std::placeholders::_1, 60 | std::placeholders::_2, 61 | std::placeholders::_3)); // 设置回调 62 | } 63 | void start() { 64 | __server.start(); // 开始事件监听 65 | __base_loop.loop(); // 开始事件监控,这是一个死循环阻塞接口 66 | } 67 | }; 68 | 69 | #endif 70 | 71 | int main() { 72 | translate_server server(8085); 73 | server.start(); 74 | return 0; 75 | } -------------------------------------------------------------------------------- /HareMQ/demo/muduo/makefile: -------------------------------------------------------------------------------- 1 | .PHONY:all 2 | all: server client 3 | 4 | # server: dict_server.cc 5 | # g++ -o $@ $^ -std=c++11 -I../../libs/muduo/include -L../../libs/muduo/lib -lmuduo_net -lmuduo_base -lpthread 6 | # client: dict_client.cc 7 | # g++ -o $@ $^ -std=c++11 -I../../libs/muduo/include -L../../libs/muduo/lib -lmuduo_net -lmuduo_base -lpthread 8 | # .PHONY:clean 9 | # clean: 10 | # rm -f server client 11 | 12 | server: proto_server.cc request.pb.cc /home/parallels/Project/HareMQ/HareMQ/libs/muduo/include/muduo/protoc/codec.cc 13 | g++ -o $@ $^ -std=c++11 -I../../libs/muduo/include -L../../libs/muduo/lib -lmuduo_net -lmuduo_base -lpthread -lprotobuf -lz 14 | client: proto_client.cc request.pb.cc /home/parallels/Project/HareMQ/HareMQ/libs/muduo/include/muduo/protoc/codec.cc 15 | g++ -o $@ $^ -std=c++11 -I../../libs/muduo/include -L../../libs/muduo/lib -lmuduo_net -lmuduo_base -lpthread -lprotobuf -lz 16 | .PHONY:clean 17 | clean: 18 | rm -f server client -------------------------------------------------------------------------------- /HareMQ/demo/muduo/proto_client.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | 8 | #ifndef __YUFC_DEMO_PROTOC_CLIENT_USE_MUDUO__ 9 | #define __YUFC_DEMO_PROTOC_CLIENT_USE_MUDUO__ 10 | 11 | #include "muduo/base/CountDownLatch.h" 12 | #include "muduo/base/Logging.h" 13 | #include "muduo/base/Mutex.h" 14 | #include "muduo/net/EventLoopThread.h" 15 | #include "muduo/net/TcpClient.h" 16 | #include "muduo/protoc/codec.h" 17 | #include "muduo/protoc/dispatcher.h" 18 | 19 | #include "../log.hpp" 20 | #include "request.pb.h" 21 | 22 | class client { 23 | public: 24 | typedef std::shared_ptr message_ptr; 25 | typedef std::shared_ptr add_response_ptr; 26 | typedef std::shared_ptr translate_response_ptr; 27 | 28 | private: 29 | muduo::CountDownLatch __latch; // 实现同步的 30 | muduo::net::EventLoopThread __loop_thread; // 异步循环处理线程 31 | muduo::net::TcpConnectionPtr __conn; // 客户端对应的连接 32 | muduo::net::TcpClient __client; // 客户端 33 | ProtobufDispatcher __dispatcher; // 请求分发器 34 | ProtobufCodec __codec; // 协议处理器 35 | public: 36 | client(const std::string& sip, int sport) 37 | : __latch(1) 38 | , __client(__loop_thread.startLoop(), muduo::net::InetAddress(sip, sport), "client") 39 | , __dispatcher(std::bind(&client::onUnknownMessage, this, 40 | std::placeholders::_1, 41 | std::placeholders::_2, 42 | std::placeholders::_3)) 43 | , __codec(std::bind(&ProtobufDispatcher::onProtobufMessage, &__dispatcher, 44 | std::placeholders::_1, 45 | std::placeholders::_2, 46 | std::placeholders::_3)) { 47 | // 注册 48 | __dispatcher.registerMessageCallback(std::bind(&client::onTranslate, 49 | this, std::placeholders::_1, 50 | std::placeholders::_2, 51 | std::placeholders::_3)); 52 | __dispatcher.registerMessageCallback(std::bind(&client::onAdd, 53 | this, std::placeholders::_1, 54 | std::placeholders::_2, 55 | std::placeholders::_3)); 56 | // 设置消息回调 57 | __client.setMessageCallback(std::bind(&ProtobufCodec::onMessage, &__codec, 58 | std::placeholders::_1, 59 | std::placeholders::_2, 60 | std::placeholders::_3)); 61 | __client.setConnectionCallback(std::bind(&client::onConnection, this, std::placeholders::_1)); 62 | } 63 | void connect() { 64 | __client.connect(); 65 | __latch.wait(); // 阻塞等待,直到建立成功 66 | } 67 | void translate(const std::string& mesg) { 68 | yufc::translateRequest req; // 请求对象 69 | req.set_msg(mesg); 70 | send(&req); 71 | } 72 | void add(int num1, int num2) { 73 | yufc::addRequest req; // 请求对象 74 | req.set_num1(num1); 75 | req.set_num2(num2); 76 | send(&req); 77 | } 78 | 79 | private: 80 | bool send(const google::protobuf::Message* message) { 81 | /** 82 | * 这里需要好好理解: google::protobuf::Message* 是一个父类指针 83 | * 传递过来的是: yufc::translateRequest* 或者是 yufc::addRequest* 是子类的指针 84 | * 然后send()里面就会调用父类指针指向的子类对象的虚函数 85 | * 这样就能同时让 translate() 和 add() 都调用 send() 了,不用写两个send 86 | */ 87 | if (__conn->connected()) { 88 | __codec.send(__conn, *message); // 改成利用__codec来发送 89 | return true; 90 | } 91 | return false; 92 | } 93 | void onUnknownMessage(const muduo::net::TcpConnectionPtr& conn, const MessagePtr& message, muduo::Timestamp ts) { 94 | LOG(INFO) << "unknown result: " << message->GetTypeName() << std::endl; 95 | conn->shutdown(); 96 | } 97 | void onTranslate(const muduo::net::TcpConnectionPtr& conn, const translate_response_ptr& message, muduo::Timestamp ts) { 98 | LOG(INFO) << "translate result: " << message->msg() << std::endl; 99 | } 100 | void onAdd(const muduo::net::TcpConnectionPtr& conn, const add_response_ptr& message, muduo::Timestamp ts) { 101 | LOG(INFO) << "add result: " << message->result() << std::endl; 102 | } 103 | void onConnection(const muduo::net::TcpConnectionPtr& conn) { 104 | if (conn->connected()) { 105 | __latch.countDown(); 106 | __conn = conn; 107 | LOG(INFO) << "connected" << std::endl; 108 | } 109 | else 110 | LOG(INFO) << "disconnected" << std::endl; 111 | } 112 | }; 113 | 114 | #endif 115 | 116 | int main() { 117 | client clt("127.0.0.1", 8085); 118 | clt.connect(); 119 | clt.translate("hello"); 120 | clt.add(11, 22); 121 | sleep(1); 122 | return 0; 123 | } -------------------------------------------------------------------------------- /HareMQ/demo/muduo/proto_server.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | 8 | #ifndef __YUFC_DEMO_PROTOC_SERVER_USE_MUDUO__ 9 | #define __YUFC_DEMO_PROTOC_SERVER_USE_MUDUO__ 10 | 11 | #include "muduo/base/Logging.h" 12 | #include "muduo/base/Mutex.h" 13 | #include "muduo/net/EventLoop.h" 14 | #include "muduo/net/TcpServer.h" 15 | #include "muduo/protoc/codec.h" 16 | #include "muduo/protoc/dispatcher.h" 17 | 18 | #include "../log.hpp" 19 | #include "request.pb.h" 20 | #include 21 | 22 | class server { 23 | public: 24 | typedef std::shared_ptr translate_request_ptr; 25 | typedef std::shared_ptr translate_response_ptr; 26 | typedef std::shared_ptr add_request_ptr; 27 | typedef std::shared_ptr add_response_ptr; 28 | 29 | private: 30 | muduo::net::EventLoop __base_loop; // 回调 31 | muduo::net::TcpServer __server; // 服务器对象 32 | ProtobufDispatcher __dispatcher; // 请求分发器对象 -- 要向其中注册请求处理函数 33 | ProtobufCodec __codec; // protobuf协议处理器 -- 针对收到的请求数据进行protobuf协议处理 34 | public: 35 | server(int port) 36 | : __server(&__base_loop, muduo::net::InetAddress("0.0.0.0", port), "server", muduo::net::TcpServer::kReusePort) 37 | , __dispatcher(std::bind(&server::onUnknownMessage, 38 | this, std::placeholders::_1, 39 | std::placeholders::_2, 40 | std::placeholders::_3)) 41 | , __codec(std::bind(&ProtobufDispatcher::onProtobufMessage, 42 | &__dispatcher, std::placeholders::_1, 43 | std::placeholders::_2, 44 | std::placeholders::_3)) { 45 | // 注册业务请求处理函数 46 | __dispatcher.registerMessageCallback(std::bind(&server::onTranslate, 47 | this, std::placeholders::_1, 48 | std::placeholders::_2, 49 | std::placeholders::_3)); 50 | __dispatcher.registerMessageCallback(std::bind(&server::onAdd, 51 | this, std::placeholders::_1, 52 | std::placeholders::_2, 53 | std::placeholders::_3)); 54 | // 设置消息回调 55 | __server.setMessageCallback(std::bind(&ProtobufCodec::onMessage, &__codec, 56 | std::placeholders::_1, 57 | std::placeholders::_2, 58 | std::placeholders::_3)); 59 | __server.setConnectionCallback(std::bind(&server::onConnection, this, std::placeholders::_1)); 60 | } 61 | void start() { 62 | __server.start(); 63 | __base_loop.loop(); 64 | } 65 | 66 | private: 67 | std::string translate(const std::string& str) { 68 | // 用个简单例子就行 69 | static std::unordered_map __dict_map = { 70 | { "hello", "nihao" }, { "nihao", "hello" } 71 | }; 72 | auto it = __dict_map.find(str); // 这里的str包含了\n,需要额外处理,不过这里只是为了学习使用服务器,不处理了 73 | if (it == __dict_map.end()) 74 | return "unknown\n"; 75 | return it->second; 76 | } 77 | void onTranslate(const muduo::net::TcpConnectionPtr& conn, const translate_request_ptr& message, muduo::Timestamp ts) { 78 | // 1. 提取message中的有效信息,也就是需要翻译的内容 79 | std::string req_msg = message->msg(); 80 | // 2. 进行翻译,得到结果 81 | std::string rsp_msg = translate(req_msg); 82 | // 3. 组织protobuf的响应 83 | yufc::translateResponse resp; 84 | resp.set_msg(rsp_msg); 85 | // 4. 发送响应 86 | __codec.send(conn, resp); // 这里不是 conn->send(), 因为resp是需要序列化的,所以调用 __codec.send() 87 | // __codec.send() == 序列化 + conn->send() 88 | } 89 | void onAdd(const muduo::net::TcpConnectionPtr& conn, const add_request_ptr& message, muduo::Timestamp ts) { 90 | int num1 = message->num1(); 91 | int num2 = message->num2(); 92 | int result = num1 + num2; 93 | yufc::addResponse resp; 94 | resp.set_result(result); 95 | __codec.send(conn, resp); 96 | } 97 | void onUnknownMessage(const muduo::net::TcpConnectionPtr& conn, const MessagePtr& message, muduo::Timestamp ts) { 98 | LOG(INFO) << "onUnknownMessage: " << message->GetTypeName() << std::endl; 99 | conn->shutdown(); 100 | } 101 | void onConnection(const muduo::net::TcpConnectionPtr& conn) { 102 | if (conn->connected()) 103 | LOG(INFO) << "connected" << std::endl; 104 | else 105 | LOG(INFO) << "disconnected" << std::endl; 106 | } 107 | }; 108 | 109 | #endif 110 | 111 | int main() { 112 | server svr(8085); 113 | svr.start(); 114 | return 0; 115 | } -------------------------------------------------------------------------------- /HareMQ/demo/muduo/request.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package yufc; 4 | 5 | message translateRequest { 6 | string msg = 1; 7 | }; 8 | 9 | message translateResponse { 10 | string msg = 1; 11 | }; 12 | 13 | message addRequest { 14 | int32 num1 = 1; 15 | int32 num2 = 2; 16 | }; 17 | 18 | message addResponse { 19 | int32 result = 1; 20 | }; -------------------------------------------------------------------------------- /HareMQ/demo/protobuf/contacts.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; // 声明语法版本 2 | package contacts; // 声明命名空间 3 | 4 | // 结构化对象的描述 5 | message contact { 6 | // 各个字段描述 7 | /* 字段类型 字段名 = 字段唯一编号 */ 8 | uint64 sn = 1; 9 | string name = 2; 10 | float score = 3; 11 | }; -------------------------------------------------------------------------------- /HareMQ/demo/protobuf/main.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | #include "contacts.pb.h" 8 | 9 | int main() { 10 | contacts::contact conn; 11 | conn.set_sn(10001); 12 | conn.set_name("Sam"); 13 | conn.set_score(60.5); 14 | 15 | std::string str = conn.SerializeAsString(); 16 | // conn.SerializeToString(&str); // 一样的 17 | contacts::contact stu; 18 | bool ret = stu.ParseFromString(str); 19 | if (ret == false) // 反序列化失败 20 | assert(false); 21 | std::cout << stu.sn() << " " << stu.name() << " " << stu.score() << std::endl; 22 | return 0; 23 | } -------------------------------------------------------------------------------- /HareMQ/demo/protobuf/makefile: -------------------------------------------------------------------------------- 1 | test: main.cc contacts.pb.cc 2 | g++ -o $@ $^ -lprotobuf -std=c++11 3 | .PHONY:clean 4 | clean: 5 | rm -f test -------------------------------------------------------------------------------- /HareMQ/demo/sqlite/db.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | 8 | #ifndef __YUFC_SQLITE_HELPER__ 9 | #define __YUFC_SQLITE_HELPER__ 10 | 11 | #include "../log.hpp" 12 | #include 13 | #include 14 | #include 15 | 16 | class sqlite_helper { 17 | public: 18 | typedef int (*sqlite_callback)(void*, int, char**, char**); 19 | 20 | private: 21 | sqlite3* __handler; 22 | std::string __db_file; 23 | 24 | public: 25 | sqlite_helper(const std::string& db_file) 26 | : __db_file(db_file) 27 | , __handler(nullptr) { } 28 | bool open(int safe_lavel = SQLITE_OPEN_FULLMUTEX) { 29 | // 打开数据库(文件) 30 | // int sqlite3_open_v2(const char* filename, sqlite3 **ppDb, int flags, const char* zVfs); 31 | int ret = sqlite3_open_v2(__db_file.c_str(), &__handler, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | safe_lavel, nullptr); 32 | if (ret != SQLITE_OK) { 33 | LOG(ERROR) << "create database failed: " << sqlite3_errmsg(__handler) << std::endl; 34 | return false; 35 | } 36 | return true; 37 | } 38 | bool exec(const std::string& sql, sqlite_callback cb, void* arg) { 39 | // 执行语句 40 | // int sqlite3_exec(sqlite3*, char* sql, int (*callback)(void*, int, char**, char**), void* arg, char**err); 41 | int ret = sqlite3_exec(__handler, sql.c_str(), cb, arg, nullptr); 42 | if (ret != SQLITE_OK) { 43 | LOG(ERROR) << "run exec: [" << sql << "] failed: " << sqlite3_errmsg(__handler) << std::endl; 44 | return false; 45 | } 46 | return true; 47 | } 48 | bool close() { 49 | // 关闭数据库(文件) 50 | if (__handler) { 51 | if (sqlite3_close_v2(__handler) == SQLITE_OK) 52 | return true; 53 | LOG(ERROR) << "close error" << std::endl; 54 | return false; 55 | } 56 | LOG(ERROR) << "null sql handler" << std::endl; 57 | return false; 58 | } 59 | }; 60 | 61 | #endif -------------------------------------------------------------------------------- /HareMQ/demo/sqlite/makefile: -------------------------------------------------------------------------------- 1 | test:test_sql.cc 2 | g++ -o $@ $^ -std=c++11 -lsqlite3 3 | .PHONY:clean 4 | clean: 5 | rm -f test *.db -------------------------------------------------------------------------------- /HareMQ/demo/sqlite/test_sql.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | #include "db.hpp" 8 | #include 9 | #include 10 | #include 11 | 12 | int select_cb(void* arg, int col_count, char** result, char** fields_name) { 13 | std::vector* arr = (std::vector*)arg; // 拿到传进来的数组 14 | arr->push_back(result[0]); // 因为查询结果只有一个字段,所以push一个就行了 15 | return 0; // 这里一定要返回0表示正常,否则可能会触发abort 16 | } 17 | 18 | int main() { 19 | // 1. 创建/打开库文件 20 | sqlite_helper helper("./test.db"); 21 | assert(helper.open()); 22 | // 2. 创建表(不存在则创建) 23 | const char* create_sql = "create table if not exists student(sn int primary key, name varchar(32), age int);"; 24 | assert(helper.exec(create_sql, nullptr, nullptr)); 25 | // 3. 新增数据(增删查改) 26 | const char* insert_sql = "insert into student values(1, 'Sam', 18), (2, 'Jack', 19), (3, 'Lucy', 18);"; 27 | assert(helper.exec(insert_sql, nullptr, nullptr)); 28 | const char* select_sql = "select name from student;"; 29 | std::vector arr; 30 | assert(helper.exec(select_sql, select_cb, &arr)); 31 | for (const auto& name : arr) 32 | std::cout << name << " "; 33 | std::cout << std::endl; 34 | // 4. 关闭数据库 35 | helper.close(); 36 | return 0; 37 | } -------------------------------------------------------------------------------- /HareMQ/demo/thread_pool/makefile: -------------------------------------------------------------------------------- 1 | test:test.cc 2 | g++ -o $@ $^ -std=c++11 3 | .PHONY:clean 4 | clean: 5 | rm -f test -------------------------------------------------------------------------------- /HareMQ/demo/thread_pool/test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | #include "thread_pool.hpp" 8 | 9 | int add(int a, int b) { return a + b; } 10 | 11 | int main() { 12 | thread_pool pool; 13 | for (int i = 0; i < 11; i++) { 14 | std::future fu = pool.push(add, i, 22); 15 | LOG(INFO) << "add res: " << fu.get() << std::endl; 16 | } 17 | pool.stop(); 18 | return 0; 19 | } -------------------------------------------------------------------------------- /HareMQ/demo/thread_pool/thread_pool.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | #ifndef __YUFC_DEMO_THREAD_POOL__ 8 | #define __YUFC_DEMO_THREAD_POOL__ 9 | 10 | #include "../log.hpp" 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | class thread_pool { 22 | public: 23 | using func_t = std::function; 24 | 25 | private: 26 | std::atomic __stop_signal; // 线程池停止信号 27 | std::mutex __mtx_lock; // 互斥锁 28 | std::condition_variable __cond; // 同步变量 29 | std::vector __threads; // 线程池的线程 30 | std::deque __task_queue; // 任务队列 31 | 32 | public: 33 | thread_pool(int thread_count = 1) 34 | : __stop_signal(false) { 35 | // 创建线程 36 | for (int i = 0; i < thread_count; i++) { 37 | __threads.emplace_back(&thread_pool::entry, this); 38 | } 39 | } 40 | ~thread_pool() { 41 | stop(); 42 | } 43 | void stop() { 44 | if (__stop_signal == true) 45 | return; // 如果已经退出了就不能重复退出了 46 | __stop_signal = true; 47 | __cond.notify_all(); 48 | for (auto& e : __threads) // 等待所有线程退出 49 | e.join(); 50 | } 51 | 52 | template 53 | auto push(func&& f, Args&&... args) -> std::future { 54 | // 1. 对传入的函数封装成 packaged_task 任务包 55 | using return_type = decltype(f(args...)); // 返回值类型 56 | auto bind_func = std::bind(std::forward(f), std::forward(args)...); // 函数+参数类型 57 | auto task = std::make_shared>(bind_func); 58 | std::future fu = task->get_future(); 59 | // 2. 构造 lambda 匿名函数(捕获任务对象,函数内执行任务对象) 60 | { 61 | std::unique_lock lock(__mtx_lock); 62 | // 3. 将构造出来的匿名函数对象,抛入到任务队列中 63 | __task_queue.push_back([task]() { 64 | (*task)(); 65 | }); 66 | // 4. 唤醒消费者 67 | __cond.notify_one(); 68 | } 69 | return fu; 70 | } 71 | 72 | private: 73 | // 线程入口函数 -- 不断从任务队列中取出任务进行执行 74 | void entry() { 75 | while (!__stop_signal) { 76 | std::deque tmp; 77 | { 78 | // 1. 加锁 79 | std::unique_lock lock(__mtx_lock); 80 | // 2. 任务队列不为空,或者 __stop_signal 被置位, 否则就一直等着 81 | __cond.wait(lock, [this]() { return __stop_signal || !__task_queue.empty(); }); 82 | // 3. 因为现在是加锁状态,一次取一个太亏了,如果就绪多个,可以一次取出来的} 83 | tmp.swap(__task_queue); 84 | assert(__task_queue.size() == 0); // 此时任务队列应该为空了 85 | } 86 | for (auto& t : tmp) // 逐个执行即可(无锁状态) 87 | t(); 88 | } 89 | } 90 | }; 91 | #endif -------------------------------------------------------------------------------- /HareMQ/mqclient/async_worker.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | #ifndef __YUFC_CLIENT_LOOP_WORKER__ 8 | #define __YUFC_CLIENT_LOOP_WORKER__ 9 | 10 | #include "../mqcommon/helper.hpp" 11 | #include "../mqcommon/logger.hpp" 12 | #include "../mqcommon/thread_pool.hpp" 13 | #include "muduo/net/EventLoopThread.h" 14 | 15 | namespace hare_mq { 16 | class async_worker { 17 | public: 18 | using ptr = std::shared_ptr; 19 | muduo::net::EventLoopThread loop_thread; 20 | thread_pool pool; 21 | }; 22 | } // namespace hare_mq 23 | 24 | #endif -------------------------------------------------------------------------------- /HareMQ/mqclient/config/for_test.cfg: -------------------------------------------------------------------------------- 1 | declare_exchange exchange1 FANOUT true false 2 | declare_queue queue1 true false false k1=v1&k2=v2 3 | declare_queue queue2 true false false 4 | bind exchange1 queue1 news.pop.# 5 | bind exchange1 queue2 queue2 -------------------------------------------------------------------------------- /HareMQ/mqclient/connection.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | #ifndef __YUFC_CLIENT_CONNECTION__ 8 | #define __YUFC_CLIENT_CONNECTION__ 9 | 10 | #include "../mqcommon/logger.hpp" 11 | #include "../mqcommon/msg.pb.h" 12 | #include "../mqcommon/protocol.pb.h" 13 | #include "async_worker.hpp" 14 | #include "channel.hpp" 15 | #include "muduo/base/CountDownLatch.h" 16 | #include "muduo/base/Mutex.h" 17 | #include "muduo/net/EventLoopThread.h" 18 | #include "muduo/net/TcpClient.h" 19 | #include "muduo/protoc/codec.h" 20 | #include "muduo/protoc/dispatcher.h" 21 | 22 | namespace hare_mq { 23 | class connection { 24 | public: 25 | using ptr = std::shared_ptr; 26 | using message_ptr = std::shared_ptr; 27 | using basicCommonResponsePtr = std::shared_ptr; 28 | using basicConsumeResponsePtr = std::shared_ptr; 29 | using basicQueryResponsePtr = std::shared_ptr; // 30 | private: 31 | muduo::CountDownLatch __latch; // 实现同步的 32 | muduo::net::TcpConnectionPtr __conn; // 客户端对应的连接 33 | muduo::net::TcpClient __client; // 客户端 34 | ProtobufDispatcher __dispatcher; // 请求分发器 35 | ProtobufCodecPtr __codec; // 协议处理器 36 | async_worker::ptr __worker; // 异步工作控制 37 | private: 38 | channel_manager::ptr __channel_manager; // 39 | public: 40 | connection(const std::string& sip, int sport, const async_worker::ptr& worker) 41 | : __latch(1) 42 | , __worker(worker) 43 | , __channel_manager(std::make_shared()) 44 | , __client(worker->loop_thread.startLoop(), muduo::net::InetAddress(sip, sport), "Client") 45 | , __dispatcher(std::bind(&connection::onUnknownMessage, this, 46 | std::placeholders::_1, 47 | std::placeholders::_2, 48 | std::placeholders::_3)) 49 | , __codec(std::make_shared(std::bind(&ProtobufDispatcher::onProtobufMessage, &__dispatcher, 50 | std::placeholders::_1, 51 | std::placeholders::_2, 52 | std::placeholders::_3))) { 53 | __dispatcher.registerMessageCallback(std::bind(&connection::commonResponse, 54 | this, std::placeholders::_1, 55 | std::placeholders::_2, 56 | std::placeholders::_3)); 57 | __dispatcher.registerMessageCallback(std::bind(&connection::consumeResponse, 58 | this, std::placeholders::_1, 59 | std::placeholders::_2, 60 | std::placeholders::_3)); 61 | __dispatcher.registerMessageCallback(std::bind(&connection::queryRespone, 62 | this, std::placeholders::_1, 63 | std::placeholders::_2, 64 | std::placeholders::_3)); 65 | __client.setMessageCallback(std::bind(&ProtobufCodec::onMessage, __codec, 66 | std::placeholders::_1, 67 | std::placeholders::_2, 68 | std::placeholders::_3)); 69 | __client.setConnectionCallback(std::bind(&connection::onConnection, this, std::placeholders::_1)); 70 | // 连接服务器 71 | __client.connect(); 72 | __latch.wait(); 73 | } 74 | channel::ptr openChannel() { 75 | channel::ptr ch = __channel_manager->create_channel(__conn, __codec); 76 | // 给服务器发送信道创建请求 77 | if (!ch->open_server_channel()) { 78 | LOG(ERROR) << "open channel failed!" << std::endl; 79 | return channel::ptr(); 80 | } 81 | return ch; 82 | } 83 | void closeChannel(const channel::ptr& ch) { 84 | ch->close_server_channel(); 85 | __channel_manager->remove_channel(ch->cid()); 86 | } // 87 | private: 88 | void commonResponse(const muduo::net::TcpConnectionPtr& conn, const basicCommonResponsePtr& message, muduo::Timestamp ts) { 89 | // 1. 找到信道 90 | channel::ptr ch = __channel_manager->select_channel(message->cid()); 91 | if (ch == nullptr) { 92 | LOG(ERROR) << "cannot find channel info" << std::endl; 93 | return; 94 | } 95 | // 2. 将得到的响应对象添加到信道的基础响应map中 96 | ch->push_basic_response(message); 97 | } 98 | void consumeResponse(const muduo::net::TcpConnectionPtr& conn, const basicConsumeResponsePtr& message, muduo::Timestamp ts) { 99 | // 1. 找到信道 100 | channel::ptr ch = __channel_manager->select_channel(message->cid()); 101 | // 2. 封装异步任务,抛入到线程池 102 | __worker->pool.push([ch, message]() { 103 | ch->consume(message); 104 | }); 105 | } 106 | void queryRespone(const muduo::net::TcpConnectionPtr& conn, const basicQueryResponsePtr& message, muduo::Timestamp ts) { 107 | channel::ptr ch = __channel_manager->select_channel(message->cid()); 108 | if (ch == nullptr) { 109 | LOG(ERROR) << "cannot find channel info" << std::endl; 110 | return; 111 | } 112 | ch->push_basic_response(message); 113 | } 114 | void onUnknownMessage(const muduo::net::TcpConnectionPtr& conn, const MessagePtr& message, muduo::Timestamp ts) { 115 | LOG(INFO) << "unknown result: " << message->GetTypeName() << std::endl; 116 | conn->shutdown(); 117 | } 118 | void onConnection(const muduo::net::TcpConnectionPtr& conn) { 119 | if (conn->connected()) { 120 | __latch.countDown(); 121 | __conn = conn; 122 | LOG(INFO) << "connected" << std::endl; 123 | } else 124 | LOG(INFO) << "disconnected" << std::endl; 125 | } 126 | }; 127 | 128 | } // namespace hare_mq 129 | 130 | #endif -------------------------------------------------------------------------------- /HareMQ/mqclient/consume_client.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | #include "connection.hpp" 8 | 9 | void cb(hare_mq::channel::ptr& ch, const std::string& tag, const hare_mq::BasicProperties* bp, const std::string& body) { 10 | hare_mq::LOG(INFO) << "[" << tag << "] consumed a mesg: " << body << std::endl; 11 | ch->basic_ack(bp->id()); 12 | } 13 | 14 | void consume_client(const std::string& qname) { 15 | // 1. 实例化异步工作线程 16 | hare_mq::async_worker::ptr awp = std::make_shared(); 17 | // 2. 实例化连接对象 18 | hare_mq::connection::ptr conn = std::make_shared("127.0.0.1", 8085, awp); 19 | // 3. 通过连接创建信道 20 | hare_mq::channel::ptr ch = conn->openChannel(); 21 | // // 4. 通过信道提供的服务完成所需 22 | // // 4.1 声明一个交换机 exchange1, 类型为广播类型 23 | // auto empty_map = std::unordered_map(); 24 | // ch->declare_exchange("exchange1", hare_mq::ExchangeType::TOPIC, true, false, empty_map); 25 | // // 4.2 声明一个队列 queue1 26 | // ch->declare_queue("queue1", true, false, false, empty_map); 27 | // // 4.3 声明一个队列 queue2 28 | // ch->declare_queue("queue2", true, false, false, empty_map); 29 | // // 4.4 绑定 queue1-exchange1, 且 binding_key 设置为 queue1 30 | // ch->bind("exchange1", "queue1", "queue1"); 31 | // // 4.5 绑定 queue2-exchange1, 且 binding_key 设置为 news.music.# 32 | // ch->bind("exchange1", "queue2", "news.music.#"); 33 | auto fn = std::bind(&cb, ch, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); 34 | ch->basic_consume("consumer1", qname, false, fn); 35 | while (true) { 36 | std::this_thread::sleep_for(std::chrono::seconds(1)); 37 | } 38 | // 6. 关闭信道 39 | conn->closeChannel(ch); 40 | } 41 | 42 | int main(int argc, char** argv) { 43 | if (argc != 2) { 44 | hare_mq::LOG(FATAL) << "usage: \n\r ./consume_client queue1\n"; 45 | return 1; 46 | } 47 | consume_client(argv[1]); 48 | return 0; 49 | } -------------------------------------------------------------------------------- /HareMQ/mqclient/consumer.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | #ifndef __YUFC_CLIENT_CONSUMER__ 8 | #define __YUFC_CLIENT_CONSUMER__ 9 | 10 | #include "../mqcommon/helper.hpp" 11 | #include "../mqcommon/logger.hpp" 12 | #include "../mqcommon/msg.pb.h" 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | namespace hare_mq { 22 | using consumer_callback = std::function; 23 | struct consumer { 24 | using ptr = std::shared_ptr; 25 | std::string tag; // 消费者标识 26 | std::string qname; // 订阅的队列名称 27 | bool auto_ack; // 自动确认标志 28 | consumer_callback callback; // 回调 29 | consumer() { } 30 | ~consumer() = default; 31 | consumer(const std::string& ctag, const std::string& queue_name, bool ack_flag, const consumer_callback& cb) 32 | : tag(ctag) 33 | , qname(queue_name) 34 | , auto_ack(ack_flag) 35 | , callback(cb) { } 36 | }; 37 | } // namespace hare_mq 38 | #endif -------------------------------------------------------------------------------- /HareMQ/mqclient/makefile: -------------------------------------------------------------------------------- 1 | # CFLAG= -I../tools/muduo/muduo/ 2 | CFLAG= -I../tools/muduo/include/ 3 | LFLAG= -L../tools/muduo/lib -lgtest -lprotobuf -lsqlite3 -lpthread -lmuduo_net -lmuduo_base -lz 4 | .PHONY:all 5 | all: publish_client consume_client client 6 | publish_client: publish_client.cc ../mqcommon/*.cc ../tools/muduo/include/muduo/protoc/codec.cc 7 | g++ -g -std=c++11 $(CFLAG) $^ -o $@ $(LFLAG) 8 | consume_client: consume_client.cc ../mqcommon/*.cc ../tools/muduo/include/muduo/protoc/codec.cc 9 | g++ -g -std=c++11 $(CFLAG) $^ -o $@ $(LFLAG) 10 | client: client.cc ../mqcommon/*.cc ../tools/muduo/include/muduo/protoc/codec.cc 11 | g++ -g -std=c++11 $(CFLAG) $^ -o $@ $(LFLAG) 12 | .PHONY:clean 13 | clean: 14 | rm -f publish_client consume_client client; 15 | rm -rf ./log -------------------------------------------------------------------------------- /HareMQ/mqclient/publish_client.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | #include "connection.hpp" 7 | 8 | void publish_client() { 9 | // 1. 实例化异步工作线程 10 | hare_mq::async_worker::ptr awp = std::make_shared(); 11 | // 2. 实例化连接对象 12 | hare_mq::connection::ptr conn = std::make_shared("127.0.0.1", 8085, awp); 13 | // 3. 通过连接创建信道 14 | hare_mq::channel::ptr ch = conn->openChannel(); 15 | // 4. 通过信道提供的服务完成所需 16 | // 4.1 声明一个交换机 exchange1, 类型为广播类型 17 | auto empty_map = std::unordered_map(); 18 | ch->declare_exchange("exchange1", hare_mq::ExchangeType::FANOUT, true, false, empty_map); 19 | // 4.2 声明一个队列 queue1 20 | ch->declare_queue("queue1", true, false, false, empty_map); 21 | // 4.3 声明一个队列 queue2 22 | ch->declare_queue("queue2", true, false, false, empty_map); 23 | // 4.4 绑定 queue1-exchange1, 且 binding_key 设置为 queue1 24 | ch->bind("exchange1", "queue1", "queue1"); 25 | // 4.5 绑定 queue2-exchange1, 且 binding_key 设置为 news.music.# 26 | ch->bind("exchange1", "queue2", "news.music.#"); 27 | // 5. 循环向交换机发布消息 28 | for (int i = 0; i < 3; ++i) { 29 | hare_mq::BasicProperties bp; 30 | bp.set_id(hare_mq::uuid_helper::uuid()); 31 | bp.set_delivery_mode(hare_mq::DeliveryMode::DURABLE); 32 | bp.set_routing_key("news.music.pop"); 33 | ch->basic_publish("exchange1", &bp, "Hello world-" + std::to_string(i)); // queue2 可以收到消息 34 | std::this_thread::sleep_for(std::chrono::seconds(1)); 35 | } 36 | #if false 37 | hare_mq::BasicProperties bp2; 38 | bp2.set_id(hare_mq::uuid_helper::uuid()); 39 | bp2.set_delivery_mode(hare_mq::DeliveryMode::DURABLE); 40 | bp2.set_routing_key("news.music.sport"); 41 | ch->basic_publish("exchange1", &bp2, "Hello Linux"); // queue2 可以收到消息 42 | hare_mq::BasicProperties bp3; 43 | bp3.set_id(hare_mq::uuid_helper::uuid()); 44 | bp3.set_delivery_mode(hare_mq::DeliveryMode::DURABLE); 45 | bp3.set_routing_key("news.sport.#"); 46 | ch->basic_publish("exchange1", &bp3, "Hello Linux sport"); // queue1, queue2 都收不到这条消息 47 | #endif 48 | 49 | // 6. 关闭信道 50 | conn->closeChannel(ch); 51 | } 52 | 53 | int main() { 54 | publish_client(); 55 | return 0; 56 | } -------------------------------------------------------------------------------- /HareMQ/mqcommon/helper.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | #ifndef __YUFC_COMMON_HELPER__ 8 | #define __YUFC_COMMON_HELPER__ 9 | 10 | #include "./logger.hpp" 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | namespace hare_mq { 28 | class sqlite_helper { 29 | public: 30 | typedef int (*sqlite_callback)(void*, int, char**, char**); 31 | 32 | private: 33 | sqlite3* __handler; 34 | std::string __db_file; 35 | 36 | public: 37 | sqlite_helper(const std::string& db_file) 38 | : __db_file(db_file) 39 | , __handler(nullptr) { } 40 | bool open(int safe_lavel = SQLITE_OPEN_FULLMUTEX) { 41 | // 打开数据库(文件) 42 | // int sqlite3_open_v2(const char* filename, sqlite3 **ppDb, int flags, const char* zVfs); 43 | int ret = sqlite3_open_v2(__db_file.c_str(), &__handler, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | safe_lavel, nullptr); 44 | if (ret != SQLITE_OK) { 45 | LOG(ERROR) << "create database failed: " << sqlite3_errmsg(__handler) << std::endl; 46 | return false; 47 | } 48 | return true; 49 | } 50 | bool exec(const std::string& sql, sqlite_callback cb, void* arg) { 51 | // 执行语句 52 | // int sqlite3_exec(sqlite3*, char* sql, int (*callback)(void*, int, char**, char**), void* arg, char**err); 53 | int ret = sqlite3_exec(__handler, sql.c_str(), cb, arg, nullptr); 54 | if (ret != SQLITE_OK) { 55 | LOG(ERROR) << "run exec: [" << sql << "] failed: " << sqlite3_errmsg(__handler) << std::endl; 56 | return false; 57 | } 58 | return true; 59 | } 60 | bool close() { 61 | // 关闭数据库(文件) 62 | if (__handler) { 63 | if (sqlite3_close_v2(__handler) == SQLITE_OK) 64 | return true; 65 | LOG(ERROR) << "close error" << std::endl; 66 | return false; 67 | } 68 | LOG(ERROR) << "null sql handler" << std::endl; 69 | return false; 70 | } 71 | }; 72 | 73 | class string_helper { 74 | public: 75 | static size_t split(const std::string& str, const std::string& sep, std::vector* out, bool if_compress = true) { 76 | // boost split 77 | if (if_compress) { 78 | boost::split(*out, str, boost::is_any_of(sep), boost::token_compress_on); 79 | return out->size(); 80 | } else { 81 | boost::split(*out, str, boost::is_any_of(sep), boost::token_compress_off); 82 | return out->size(); 83 | } 84 | } 85 | }; 86 | 87 | class uuid_helper { 88 | public: 89 | static std::string uuid() { 90 | std::random_device rd; 91 | std::mt19937_64 generator(rd()); 92 | std::uniform_int_distribution distribution(0, 255); 93 | std::stringstream ss; 94 | for (int i = 0; i < 8; ++i) { 95 | ss << std::setw(2) << std::setfill('0') << std::hex << distribution(generator); 96 | if (i == 3 || i == 5 || i == 7) 97 | ss << "-"; 98 | static std::atomic seq(1); // 这里一定要静态,保证多次调用都是自增的 99 | size_t num = seq.fetch_add(1); 100 | for (int i = 7; i >= 0; i--) { 101 | ss << std::setw(2) << std::setfill('0') << std::hex << ((num >> (i * 8)) & 0xff); 102 | if (i == 6) 103 | ss << "-"; 104 | } 105 | } 106 | return ss.str(); 107 | } 108 | }; 109 | 110 | class file_helper { 111 | private: 112 | std::string __file_name; 113 | 114 | public: 115 | std::string path() { return __file_name; } 116 | 117 | public: 118 | file_helper(const std::string& file_name) 119 | : __file_name(file_name) { } 120 | ~file_helper() = default; 121 | 122 | public: 123 | bool exists() { 124 | struct stat st; 125 | return stat(__file_name.c_str(), &st) == 0; 126 | } 127 | size_t size() { 128 | struct stat st; 129 | int ret = stat(__file_name.c_str(), &st); 130 | if (ret < 0) 131 | return 0; // 文件不存在 132 | return st.st_size; // 这个是文件的大小 133 | } 134 | bool read(std::string& body) { 135 | // 获取文件大小,根据文件大小调整body的空间 136 | size_t fsize = this->size(); 137 | // 调整空间 138 | body.resize(fsize); 139 | return read(&body[0], 0, fsize); 140 | } 141 | bool read(char* body, size_t offset, size_t len) { 142 | // 1. 打开文件 143 | std::ifstream ifs(__file_name, std::ios::binary | std::ios::in); 144 | if (ifs.is_open() == false) { 145 | LOG(ERROR) << "file open error: " << __file_name << std::endl; 146 | return false; 147 | } 148 | // 2. 跳转读写位置 149 | ifs.seekg(offset, std::ios::beg); 150 | // 3. 读取文件数据 151 | ifs.read(body, len); 152 | if (ifs.good() == false) { 153 | LOG(ERROR) << "read data error: " << __file_name << std::endl; 154 | ifs.close(); 155 | return false; 156 | } 157 | // 4. 关闭文件 158 | ifs.close(); 159 | return true; 160 | } 161 | bool write(const std::string& body) { 162 | return write(body.c_str(), 0, body.size()); 163 | } 164 | bool write(const char* body, size_t offset, size_t len) { 165 | // 1. 打开文件 166 | std::fstream fs(__file_name, std::ios::binary | std::ios::out | std::ios::in); 167 | if (fs.is_open() == false) { 168 | LOG(ERROR) << "file open error: " << __file_name << std::endl; 169 | return false; 170 | } 171 | // 2. 跳转到文件指定位置 172 | fs.seekp(offset, std::ios::beg); 173 | // 3. 写入数据 174 | fs.write(body, len); 175 | if (fs.good() == false) { 176 | LOG(ERROR) << "write data error: " << __file_name << std::endl; 177 | fs.close(); 178 | return false; 179 | } 180 | // 4. 关闭文件 181 | fs.close(); 182 | return true; 183 | } 184 | static bool create(const std::string& file_name) { 185 | std::ofstream ofs(file_name, std::ios::binary | std::ios::out); 186 | if (ofs.is_open() == false) { 187 | LOG(ERROR) << "create file error: " << file_name << " # " << strerror(errno) << std::endl; 188 | return false; 189 | } 190 | ofs.close(); 191 | return true; 192 | } 193 | static bool remove(const std::string& file_name) { 194 | return ::remove(file_name.c_str()) == 0; 195 | } 196 | static bool create_dir(const std::string& path) { 197 | // aaa/bbb/ccc/ddd 198 | // 在多级路径创建中,需要从第一个父级目录开始创建 199 | // 第一个创建的是 aaa 200 | // 第二个是 aaa/bbb 而不是 bbb 201 | // ... 202 | size_t pos, idx = 0; 203 | while (idx < path.size()) { 204 | pos = path.find("/", idx); 205 | if (pos == std::string::npos) { 206 | return ::mkdir(path.c_str(), 0775) == 0; 207 | } 208 | std::string subpath = path.substr(0, pos); 209 | int ret = ::mkdir(subpath.c_str(), 0775); 210 | if (ret != 0 && errno != EEXIST) { 211 | LOG(ERROR) << "create dir: " << path << " failed" << std::endl; 212 | return false; 213 | } 214 | idx = pos + 1; 215 | } 216 | return true; 217 | } 218 | static bool remove_dir(const std::string& path) { 219 | // rm -rf 220 | // system() 可以运行命令 221 | std::string cmd = "rm -rf " + path; 222 | return (system(cmd.c_str()) != -1); 223 | } 224 | bool rename(const std::string& name) { 225 | // stdio.h 里面有库函数 226 | return (::rename(__file_name.c_str(), name.c_str()) == 0); // 一定要加:: 227 | } 228 | static std::string parent_dir(const std::string& file_name) { 229 | // aaa/bbb/ccc/test.txt 230 | // 获取父级目录 231 | size_t pos = file_name.find_last_of("/"); 232 | if (pos == std::string::npos) // 当前目录 233 | return "./"; 234 | std::string path = file_name.substr(0, pos); 235 | return path; 236 | } 237 | }; 238 | 239 | class map_helper { 240 | public: 241 | static std::unordered_map ConvertProtoMapToStdMap(const google::protobuf::Map& proto_map) { 242 | std::unordered_map std_map; 243 | for (const auto& kv : proto_map) { 244 | std_map[kv.first] = kv.second; 245 | } 246 | return std_map; 247 | } 248 | static google::protobuf::Map ConvertStdMapToProtoMap(const std::unordered_map& std_map) { 249 | google::protobuf::Map proto_map; 250 | for (const auto& kv : std_map) { 251 | proto_map[kv.first] = kv.second; 252 | } 253 | return proto_map; 254 | } 255 | }; 256 | } // namespace hare_mq 257 | 258 | #endif -------------------------------------------------------------------------------- /HareMQ/mqcommon/logger.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | #ifndef __YUFC_COMMON_LOGGER__ 8 | #define __YUFC_COMMON_LOGGER__ 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | namespace hare_mq { 22 | enum STATUES // 日志等级 23 | { 24 | INFO, 25 | DEBUG, 26 | WARNING, 27 | ERROR, 28 | REQUEST, 29 | FATAL 30 | }; 31 | 32 | // 定义颜色代码 33 | #define RESET "\033[0m" 34 | #define RED "\033[1;31m" // 加粗红色 35 | #define GREEN "\033[1;32m" // 加粗绿色 36 | #define YELLOW "\033[1;33m" // 加粗黄色 37 | #define BLUE "\033[1;34m" // 加粗蓝色 38 | #define MAGENTA "\033[1;35m" // 加粗洋红 39 | #define CYAN "\033[1;36m" // 加粗青色 40 | 41 | // 根据日志等级获取相应颜色 42 | inline const char* GetColor(const std::string& level) { 43 | std::map m = { { "INFO", 0 }, { "DEBUG", 1 }, { "WARNING", 2 }, { "ERROR", 3 }, { "FATAL", 5 }, { "REQUEST", 4 } }; 44 | switch (m[level]) { 45 | case INFO: 46 | return BLUE; 47 | case DEBUG: 48 | return GREEN; 49 | case WARNING: 50 | return YELLOW; 51 | case ERROR: 52 | return MAGENTA; 53 | case FATAL: 54 | return RED; 55 | case REQUEST: 56 | return CYAN; 57 | default: 58 | return RESET; 59 | } 60 | } 61 | 62 | #define LOG_DIR "./log/" 63 | 64 | class DualStream { 65 | public: 66 | DualStream() { 67 | auto path = std::string(LOG_DIR); 68 | std::string cmd; 69 | cmd += "mkdir "; 70 | cmd += LOG_DIR; 71 | struct stat info; 72 | stat(path.c_str(), &info); 73 | if (!(info.st_mode & S_IFDIR)) 74 | system(cmd.c_str()); 75 | auto now = std::chrono::system_clock::now(); 76 | auto in_time_t = std::chrono::system_clock::to_time_t(now); 77 | std::stringstream ss; 78 | ss << std::put_time(std::localtime(&in_time_t), "%Y%m%d%H%M"); 79 | int pid = getpid(); // 获取当前进程ID 80 | // 构造文件名 81 | std::string filename = path + ss.str() + "_" + std::to_string(pid) + ".log"; 82 | file.open(filename, std::ios::out | std::ios::binary); 83 | if (!file.is_open()) { 84 | std::cerr << "Failed to open log file: " << filename << std::endl; 85 | exit(EXIT_FAILURE); 86 | } 87 | } 88 | ~DualStream() { 89 | if (file.is_open()) 90 | file.close(); 91 | } 92 | // template 93 | DualStream& operator<<(const std::string& msg) { 94 | std::cout << msg; 95 | file << stripColorCodes(msg); 96 | return *this; 97 | } 98 | // 特别处理控制符比如 std::endl 99 | DualStream& operator<<(std::ostream& (*pf)(std::ostream&)) { 100 | std::cout << pf; 101 | file << pf; 102 | return *this; 103 | } // 104 | private: 105 | std::ofstream file; 106 | std::string stripColorCodes(const std::string& input) { 107 | std::string output; 108 | std::istringstream stream(input); 109 | char ch; 110 | bool escapeSeq = false; 111 | while (stream.get(ch)) { 112 | if (ch == '\033') { // ESC character 113 | escapeSeq = true; 114 | continue; 115 | } 116 | if (escapeSeq) { 117 | if (ch == 'm') { // End of ANSI escape sequence 118 | escapeSeq = false; 119 | } 120 | continue; 121 | } 122 | output.push_back(ch); 123 | } 124 | return output; 125 | } 126 | }; 127 | 128 | // 全局日志文件流 129 | 130 | DualStream logStream; 131 | 132 | // LOG() << "message" 133 | inline DualStream& Log(const std::string& level, const std::string& file_name, int line) { 134 | // 添加日志等级 135 | std::string levelTag = std::string("[") + level + "]"; 136 | std::string coloredLevelTag = std::string(GetColor(level)) + levelTag + RESET; 137 | std::string message = coloredLevelTag; 138 | // 添加报错文件名称 139 | message += "["; 140 | message += file_name; 141 | message += "]"; 142 | // 添加当前文件的行数 143 | message += "["; 144 | message += std::to_string(line); 145 | message += "]"; 146 | // cout 本质内部是包含缓冲区的 147 | logStream << message << " "; // 不要endl进行刷新 148 | return logStream; 149 | } 150 | 151 | // 这种就是开放式的日志 152 | #define LOG(level) Log(#level, __FILE__, __LINE__) 153 | } // namespace name 154 | 155 | #endif -------------------------------------------------------------------------------- /HareMQ/mqcommon/msg.proto: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | syntax = "proto3"; 7 | package hare_mq; 8 | enum ExchangeType { 9 | UNKNOWTYPE = 0; 10 | DIRECT = 1; 11 | FANOUT = 2; 12 | TOPIC = 3; 13 | }; 14 | enum DeliveryMode { 15 | UNKNOWMODE = 0; 16 | UNDURABLE = 1; 17 | DURABLE = 2; 18 | }; 19 | message BasicProperties { 20 | string id = 1; 21 | DeliveryMode delivery_mode = 2; 22 | string routing_key = 3; 23 | }; 24 | message Message { 25 | message Payload { 26 | BasicProperties properties = 1; 27 | string body = 2; 28 | string valid = 3; 29 | }; 30 | Payload payload = 1; 31 | uint32 offset = 2; 32 | uint32 length = 3; 33 | }; -------------------------------------------------------------------------------- /HareMQ/mqcommon/protocol.proto: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | syntax = "proto3"; 7 | package hare_mq; 8 | 9 | import "msg.proto"; 10 | 11 | /* 信道的打开与关闭 */ 12 | message openChannelRequest { 13 | string rid = 1; // 请求id 14 | string cid = 2; // 信道id 15 | }; 16 | message closeChannelRequest { 17 | string rid = 1; 18 | string cid = 2; 19 | }; 20 | /* 交换机的声明与删除 */ 21 | message declareExchangeRequest { 22 | string rid = 1; 23 | string cid = 2; 24 | string exchange_name = 3; 25 | ExchangeType exchange_type = 4; 26 | bool durable = 5; 27 | bool auto_delete = 6; 28 | map args = 7; 29 | }; 30 | message deleteExchangeRequest { 31 | string rid = 1; 32 | string cid = 2; 33 | string exchange_name = 3; 34 | }; 35 | /* 队列的声明与删除 */ 36 | message declareQueueRequest { 37 | string rid = 1; 38 | string cid = 2; 39 | string queue_name = 3; 40 | bool exclusive = 4; 41 | bool durable = 5; 42 | bool auto_delete = 6; 43 | map args = 7; 44 | }; 45 | message deleteQueueRequest { 46 | string rid = 1; 47 | string cid = 2; 48 | string queue_name = 3; 49 | }; 50 | /* 队列的绑定与解除绑定 */ 51 | message bindRequest { 52 | string rid = 1; 53 | string cid = 2; 54 | string exchange_name = 3; 55 | string queue_name = 4; 56 | string binding_key = 5; 57 | }; 58 | message unbindRequest { 59 | string rid = 1; 60 | string cid = 2; 61 | string exchange_name = 3; 62 | string queue_name = 4; 63 | }; 64 | /* 消息的发布 */ 65 | message basicPublishRequest { 66 | string rid = 1; 67 | string cid = 2; 68 | string exchange_name = 3; 69 | string body = 4; 70 | BasicProperties properties = 5; 71 | }; 72 | /* 消息的确认 */ 73 | message basicAckRequest { 74 | string rid = 1; 75 | string cid = 2; 76 | string queue_name = 3; 77 | string message_id = 4; 78 | }; 79 | /* 队列的订阅 */ 80 | message basicConsumeRequest { 81 | string rid = 1; 82 | string cid = 2; 83 | string consumer_tag = 3; 84 | string queue_name = 4; 85 | bool auto_ack = 5; 86 | }; 87 | /* 订阅的取消 */ 88 | message basicCancelRequest { 89 | string rid = 1; 90 | string cid = 2; 91 | string consumer_tag = 3; 92 | string queue_name = 4; 93 | } 94 | /* 消息的推送 */ 95 | message basicConsumeResponse { 96 | string cid = 1; // 不需要rid,这个是一个响应 97 | string consumer_tag = 2; 98 | string body = 3; 99 | BasicProperties properties = 4; 100 | } 101 | /* 通用响应 */ 102 | message basicCommonResponse { 103 | string rid = 1; // 针对rid请求的响应 104 | string cid = 2; 105 | bool ok = 3; 106 | }; 107 | /* 查询 */ 108 | message basicQueryRequest { 109 | string rid = 1; // 针对rid请求的响应 110 | string cid = 2; 111 | } 112 | message basicQueryResponse { 113 | string rid = 1; 114 | string cid = 2; 115 | string body = 3; 116 | } -------------------------------------------------------------------------------- /HareMQ/mqcommon/thread_pool.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | #ifndef __YUFC_THREAD_POOL__ 8 | #define __YUFC_THREAD_POOL__ 9 | 10 | #include "../mqcommon/logger.hpp" 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | class thread_pool { 22 | public: 23 | using func_t = std::function; 24 | using ptr = std::shared_ptr; // 25 | private: 26 | std::atomic __stop_signal; // 线程池停止信号 27 | std::mutex __mtx_lock; // 互斥锁 28 | std::condition_variable __cond; // 同步变量 29 | std::vector __threads; // 线程池的线程 30 | std::deque __task_queue; // 任务队列 31 | 32 | public: 33 | thread_pool(int thread_count = 1) 34 | : __stop_signal(false) { 35 | // 创建线程 36 | for (int i = 0; i < thread_count; i++) { 37 | __threads.emplace_back(&thread_pool::entry, this); 38 | } 39 | } 40 | ~thread_pool() { 41 | stop(); 42 | } 43 | void stop() { 44 | if (__stop_signal == true) 45 | return; // 如果已经退出了就不能重复退出了 46 | __stop_signal = true; 47 | __cond.notify_all(); 48 | for (auto& e : __threads) // 等待所有线程退出 49 | e.join(); 50 | } 51 | 52 | template 53 | auto push(func&& f, Args&&... args) -> std::future { 54 | // 1. 对传入的函数封装成 packaged_task 任务包 55 | using return_type = decltype(f(args...)); // 返回值类型 56 | auto bind_func = std::bind(std::forward(f), std::forward(args)...); // 函数+参数类型 57 | auto task = std::make_shared>(bind_func); 58 | std::future fu = task->get_future(); 59 | // 2. 构造 lambda 匿名函数(捕获任务对象,函数内执行任务对象) 60 | { 61 | std::unique_lock lock(__mtx_lock); 62 | // 3. 将构造出来的匿名函数对象,抛入到任务队列中 63 | __task_queue.push_back([task]() { 64 | (*task)(); 65 | }); 66 | // 4. 唤醒消费者 67 | __cond.notify_one(); 68 | } 69 | return fu; 70 | } 71 | 72 | private: 73 | // 线程入口函数 -- 不断从任务队列中取出任务进行执行 74 | void entry() { 75 | while (!__stop_signal) { 76 | std::deque tmp; 77 | { 78 | // 1. 加锁 79 | std::unique_lock lock(__mtx_lock); 80 | // 2. 任务队列不为空,或者 __stop_signal 被置位, 否则就一直等着 81 | __cond.wait(lock, [this]() { return __stop_signal || !__task_queue.empty(); }); 82 | // 3. 因为现在是加锁状态,一次取一个太亏了,如果就绪多个,可以一次取出来的} 83 | tmp.swap(__task_queue); 84 | assert(__task_queue.size() == 0); // 此时任务队列应该为空了 85 | } 86 | for (auto& t : tmp) // 逐个执行即可(无锁状态) 87 | t(); 88 | } 89 | } 90 | }; 91 | #endif -------------------------------------------------------------------------------- /HareMQ/mqserver/binding.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | #ifndef __YUFC_BINDING__ 8 | #define __YUFC_BINDING__ 9 | 10 | #include "../mqcommon/helper.hpp" 11 | #include "../mqcommon/logger.hpp" 12 | #include "../mqcommon/msg.pb.h" 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | namespace hare_mq { 20 | /* 绑定信息类 */ 21 | struct binding { 22 | using ptr = std::shared_ptr; 23 | std::string exchange_name; 24 | std::string msg_queue_name; 25 | std::string binding_key; 26 | binding() { } 27 | binding(const std::string& ename, const std::string& qname, const std::string& key) 28 | : exchange_name(ename) 29 | , msg_queue_name(qname) 30 | , binding_key(key) { } 31 | }; 32 | 33 | #define BINDING_CREATE_TABLE "create table if not exists binding_table(\ 34 | exchange_name varchar(32), \ 35 | msg_queue_name varchar(32), \ 36 | binding_key varchar(128));" 37 | #define BINDING_DROP_TABLE "drop table if exists binding_table;" 38 | #define BINDING_INSERT_SQL "insert into binding_table values('%s', '%s', '%s');" 39 | #define BINDING_DELETE_SQL "delete from binding_table where exchange_name='%s' and msg_queue_name='%s';" 40 | #define BINDING_DELETE_EXCHANGE_SQL "delete from binding_table where exchange_name='%s';" 41 | #define BINDING_DELETE_QUEUE_SQL "delete from binding_table where msg_queue_name='%s';" 42 | #define BINDING_SELECT_SQL "select * from binding_table;" 43 | using msg_queue_binding_map = std::unordered_map; // tips in doc 44 | using binding_map = std::unordered_map; 45 | /* 绑定信息数据持久化类 */ 46 | class binding_mapper { 47 | private: 48 | sqlite_helper __sql_helper; // sql操作句柄 49 | public: 50 | binding_mapper(const std::string& dbfile) 51 | : __sql_helper(dbfile) { 52 | std::string path = file_helper::parent_dir(dbfile); 53 | file_helper::create_dir(path); 54 | assert(__sql_helper.open()); 55 | create_table(); 56 | } // constructor 57 | public: 58 | void create_table() { 59 | // same to queue and exchange 60 | bool ret = __sql_helper.exec(BINDING_CREATE_TABLE, nullptr, nullptr); 61 | if (ret == false) 62 | abort(); 63 | } 64 | void remove_table() { 65 | // same to queue and exchange 66 | bool ret = __sql_helper.exec(BINDING_DROP_TABLE, nullptr, nullptr); 67 | if (ret == false) 68 | abort(); 69 | } 70 | bool insert(binding::ptr& obj) { 71 | // same to queue and exchange 72 | char sql_str[4096] = { 0 }; 73 | sprintf(sql_str, BINDING_INSERT_SQL, 74 | obj->exchange_name.c_str(), 75 | obj->msg_queue_name.c_str(), 76 | obj->binding_key.c_str()); 77 | return __sql_helper.exec(sql_str, nullptr, nullptr); 78 | } 79 | void remove(const std::string& ename, const std::string& qname) { 80 | char sql_str[4096] = { 0 }; 81 | sprintf(sql_str, BINDING_DELETE_SQL, ename.c_str(), qname.c_str()); 82 | bool ret = __sql_helper.exec(sql_str, nullptr, nullptr); 83 | } // 移除特定的联系 84 | void remove_exchange(const std::string& ename) { 85 | char sql_str[4096] = { 0 }; 86 | sprintf(sql_str, BINDING_DELETE_EXCHANGE_SQL, ename.c_str()); 87 | bool ret = __sql_helper.exec(sql_str, nullptr, nullptr); 88 | } // 移除特定交换机的联系 89 | void remove_queue(const std::string& qname) { 90 | char sql_str[4096] = { 0 }; 91 | sprintf(sql_str, BINDING_DELETE_QUEUE_SQL, qname.c_str()); 92 | bool ret = __sql_helper.exec(sql_str, nullptr, nullptr); 93 | } // 移除特定队列的联系 94 | binding_map all() { 95 | binding_map res; 96 | __sql_helper.exec(BINDING_SELECT_SQL, select_callback, &res); // 这个是需要回调来组织查询回来的结果的 97 | return res; 98 | } // recovery 99 | private: 100 | static int select_callback(void* arg, int numcol, char** row, char** fields) { 101 | binding_map* result = (binding_map*)arg; 102 | binding::ptr bp = std::make_shared(row[0], row[1], row[2]); 103 | msg_queue_binding_map& qmap = (*result)[bp->exchange_name]; // 这里比较巧妙 104 | qmap.insert({ bp->msg_queue_name, bp }); 105 | return 0; 106 | } 107 | }; 108 | 109 | /* 绑定信息数据管理类 */ 110 | class binding_manager { 111 | private: 112 | std::mutex __mtx; 113 | binding_mapper __mapper; 114 | binding_map __bindings; // all same as queue.hpp and exchange.hpp 115 | public: 116 | using ptr = std::shared_ptr; 117 | binding_manager(const std::string& dbfile) 118 | : __mapper(dbfile) { 119 | __bindings = __mapper.all(); // recovery 120 | } // contructor 121 | public: 122 | bool bind(const std::string& ename, const std::string& qname, const std::string& key, bool durable) { 123 | std::unique_lock lock(__mtx); 124 | auto it = __bindings.find(ename); 125 | if (it != __bindings.end() && it->second.find(qname) != it->second.end()) // 这样才表示绑定信息存在 126 | return true; 127 | // 绑定信息是否需要持久化取决于: 交换机持久化 && 队列也是持久化的 128 | binding::ptr bp = std::make_shared(ename, qname, key); 129 | if (durable) { 130 | bool ret = __mapper.insert(bp); 131 | if (ret == false) 132 | return false; 133 | } 134 | auto& qbmap = __bindings[ename]; // 先获取,不存在则会创建 135 | qbmap.insert({ qname, bp }); 136 | return true; 137 | } // add a bind 138 | void unbind(const std::string& ename, const std::string& qname) { 139 | std::unique_lock lock(__mtx); 140 | auto it = __bindings.find(ename); 141 | if (it == __bindings.end()) // 交换机的数据都无 142 | return; 143 | if (it->second.find(qname) == it->second.end()) // 有交换机,但是没有绑定到qname上,也是无 144 | return; 145 | __mapper.remove(ename, qname); 146 | __bindings[ename].erase(qname); 147 | } // remove a bind 148 | void unbind_exchange(const std::string& ename) { 149 | std::unique_lock lock(__mtx); 150 | __mapper.remove_exchange(ename); 151 | __bindings.erase(ename); 152 | } // remove a exchange's binds 153 | void unbind_queue(const std::string& qname) { 154 | std::unique_lock lock(__mtx); 155 | __mapper.remove_queue(qname); 156 | // 一个queue可能和多个exchange都有绑定信息,如何都删除掉? 所以要遍历 157 | for (auto start = __bindings.begin(); start != __bindings.end(); ++start) 158 | start->second.erase(qname); 159 | } // remove a queue's binds 160 | msg_queue_binding_map get_exchange_bindings(const std::string& ename) { 161 | std::unique_lock lock(__mtx); 162 | auto it = __bindings.find(ename); 163 | if (it == __bindings.end()) // 交换机的数据无 164 | return msg_queue_binding_map(); // return null; 165 | return it->second; 166 | } // get exchange binding info 167 | binding::ptr get_binding(const std::string& ename, const std::string& qname) { 168 | std::unique_lock lock(__mtx); 169 | auto it = __bindings.find(ename); 170 | if (it == __bindings.end()) // 交换机的数据无 171 | return binding::ptr(); // return null; 172 | auto qit = it->second.find(qname); 173 | if (qit == it->second.end()) 174 | return binding::ptr(); 175 | return qit->second; 176 | } // get one binding info 177 | bool exists(const std::string& ename, const std::string& qname) { 178 | std::unique_lock lock(__mtx); 179 | auto it = __bindings.find(ename); 180 | if (it == __bindings.end()) 181 | return false; 182 | auto qit = it->second.find(qname); 183 | if (qit == it->second.end()) 184 | return false; 185 | return true; 186 | } 187 | size_t size() { 188 | size_t total_size = 0; 189 | std::unique_lock lock(__mtx); 190 | for (auto start = __bindings.begin(); start != __bindings.end(); ++start) 191 | total_size += start->second.size(); // 遍历交换机,看看每个交换机有多少个,都要加起来 192 | return total_size; 193 | } 194 | void clear_bindings() { 195 | std::unique_lock lock(__mtx); 196 | __mapper.remove_table(); 197 | __bindings.clear(); 198 | } 199 | }; 200 | 201 | } // namespace hare_mq 202 | 203 | #endif -------------------------------------------------------------------------------- /HareMQ/mqserver/connection.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | #ifndef __YUFC_CONNECTION__ 8 | #define __YUFC_CONNECTION__ 9 | 10 | #include "channel.hpp" 11 | 12 | namespace hare_mq { 13 | class connection { 14 | private: 15 | muduo::net::TcpConnectionPtr __conn; 16 | ProtobufCodecPtr __codec; 17 | consumer_manager::ptr __cmp; 18 | virtual_host::ptr __host; 19 | thread_pool::ptr __pool; 20 | channel_manager::ptr __channels; // 21 | public: 22 | using ptr = std::shared_ptr; 23 | connection(const virtual_host::ptr& host, 24 | const consumer_manager::ptr& cmp, 25 | const ProtobufCodecPtr& codec, 26 | const muduo::net::TcpConnectionPtr& conn, 27 | const thread_pool::ptr& pool) 28 | : __conn(conn) 29 | , __codec(codec) 30 | , __cmp(cmp) 31 | , __host(host) 32 | , __pool(pool) 33 | , __channels(std::make_shared()) { } 34 | ~connection() = default; 35 | void open_channel(const openChannelRequestPtr& req) { 36 | // 1. 判断信道ID是否重复 2. 创建信道 37 | bool ret = __channels->open_channel(req->cid(), __host, __cmp, __codec, __conn, __pool); // bug found! 38 | if (ret == false) 39 | return basic_response(false, req->rid(), req->cid()); 40 | // 3. 给客户端回复 41 | return basic_response(true, req->rid(), req->cid()); 42 | } 43 | void close_channel(const closeChannelRequestPtr& req) { 44 | __channels->close_channel(req->cid()); 45 | return basic_response(true, req->rid(), req->cid()); 46 | } // 47 | channel::ptr select_channel(const std::string& cid) { 48 | return __channels->select_channel(cid); 49 | } // 50 | private: 51 | void basic_response(bool ok, const std::string& rid, const std::string& cid) { 52 | basicCommonResponse resp; 53 | resp.set_rid(rid); 54 | resp.set_cid(cid); 55 | resp.set_ok(ok); 56 | __codec->send(__conn, resp); // 发送响应给客户端 57 | } // 58 | }; 59 | 60 | class connection_manager { 61 | private: 62 | std::mutex __mtx; 63 | std::unordered_map __conns; // 64 | public: 65 | using ptr = std::shared_ptr; 66 | connection_manager() = default; 67 | ~connection_manager() = default; 68 | void new_connection(const virtual_host::ptr& host, 69 | const consumer_manager::ptr& cmp, 70 | const ProtobufCodecPtr& codec, 71 | const muduo::net::TcpConnectionPtr& conn, 72 | const thread_pool::ptr& pool) { 73 | std::unique_lock lock(__mtx); 74 | auto it = __conns.find(conn); 75 | if (it != __conns.end()) // 已经有了 76 | return; 77 | auto self_conn = std::make_shared(host, cmp, codec, conn, pool); 78 | __conns.insert({ conn, self_conn }); 79 | } 80 | void delete_connection(const muduo::net::TcpConnectionPtr& conn) { 81 | std::unique_lock lock(__mtx); 82 | __conns.erase(conn); 83 | } 84 | connection::ptr select_connection(const muduo::net::TcpConnectionPtr& conn) { 85 | std::unique_lock lock(__mtx); 86 | auto it = __conns.find(conn); 87 | if (it == __conns.end()) // 没找到 88 | return connection::ptr(); 89 | return it->second; 90 | } 91 | }; 92 | 93 | } // namespace hare_mq 94 | 95 | #endif -------------------------------------------------------------------------------- /HareMQ/mqserver/consumer.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | #ifndef __YUFC_CONSUMER__ 8 | #define __YUFC_CONSUMER__ 9 | 10 | #include "../mqcommon/helper.hpp" 11 | #include "../mqcommon/logger.hpp" 12 | #include "../mqcommon/msg.pb.h" 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | namespace hare_mq { 22 | using consumer_callback = std::function; 23 | struct consumer { 24 | using ptr = std::shared_ptr; 25 | std::string tag; // 消费者标识 26 | std::string qname; // 订阅的队列名称 27 | bool auto_ack; // 自动确认标志 28 | consumer_callback callback; // 回调 29 | consumer() { } 30 | consumer(const std::string& ctag, const std::string& queue_name, bool ack_flag, const consumer_callback& cb) 31 | : tag(ctag) 32 | , qname(queue_name) 33 | , auto_ack(ack_flag) 34 | , callback(cb) { } 35 | }; 36 | 37 | /* 以队列为单元的消费者结构 */ 38 | class queue_consumer { 39 | private: 40 | std::string __qname; 41 | std::mutex __mtx; 42 | uint64_t __rr_seq; // 轮转序号 43 | std::vector __consumers; // 管理的所有消费者对象 44 | public: 45 | using ptr = std::shared_ptr; 46 | queue_consumer(const std::string& qname) 47 | : __qname(qname) 48 | , __rr_seq(0) { } 49 | consumer::ptr create(const std::string& ctag, const std::string& queue_name, bool ack_flag, const consumer_callback& cb) { 50 | // 1. lock 51 | std::unique_lock lock(__mtx); 52 | // 2. 判断消费者是否重复 53 | for (const auto& e : __consumers) 54 | if (e->tag == ctag) { 55 | LOG(WARNING) << "consumer duplicate tag, create consumer failed" << std::endl; 56 | return consumer::ptr(); // 创建失败 57 | } 58 | // 3. 没有重复则新增,构造对象 59 | auto new_consumer = std::make_shared(ctag, queue_name, ack_flag, cb); 60 | // 4. 田间管理后返回对象 61 | __consumers.push_back(new_consumer); 62 | return new_consumer; 63 | } // 创建消费者 64 | void remove(const std::string& ctag) { 65 | std::unique_lock lock(__mtx); 66 | for (auto it = __consumers.begin(); it != __consumers.end(); ++it) { 67 | if ((*it)->tag == ctag) { 68 | __consumers.erase(it); 69 | return; 70 | } 71 | } 72 | LOG(WARNING) << "consumer not founded, remove failed" << std::endl; 73 | return; 74 | } // 删除一个消费者 75 | consumer::ptr rr_choose() { 76 | std::unique_lock lock(__mtx); 77 | if (__consumers.size() == 0) 78 | return consumer::ptr(); 79 | int idx = __rr_seq % __consumers.size(); 80 | ++__rr_seq; 81 | return __consumers[idx]; 82 | } // rr 轮转获取一个消费者 83 | bool empty() { 84 | std::unique_lock lock(__mtx); 85 | return __consumers.size() == 0; 86 | } // 判空 87 | bool exists(const std::string& ctag) { 88 | std::unique_lock lock(__mtx); 89 | for (auto it = __consumers.begin(); it != __consumers.end(); ++it) 90 | if ((*it)->tag == ctag) 91 | return true; 92 | return false; 93 | } // 判断消费者是否存在 94 | void clear() { 95 | std::unique_lock lock(__mtx); 96 | __consumers.clear(); 97 | __rr_seq = 0; 98 | } // 清理所有消费者 99 | }; 100 | /* 对外提供的消费者管理 */ 101 | class consumer_manager { 102 | private: 103 | std::mutex __mtx; 104 | std::unordered_map __queue_consumers; // 映射关系 105 | public: 106 | using ptr = std::shared_ptr; 107 | consumer_manager() = default; 108 | void init_queue_consumer(const std::string& qname) { 109 | std::unique_lock lock(__mtx); 110 | auto it = __queue_consumers.find(qname); 111 | if (it != __queue_consumers.end()) 112 | return; 113 | auto new_qconsumer = std::make_shared(qname); 114 | __queue_consumers.insert({ qname, new_qconsumer }); 115 | } 116 | void destroy_queue_consumer(const std::string& qname) { 117 | std::unique_lock lock(__mtx); 118 | __queue_consumers.erase(qname); 119 | } 120 | consumer::ptr create(const std::string& ctag, 121 | const std::string& queue_name, 122 | bool ack_flag, 123 | const consumer_callback& cb) { 124 | queue_consumer::ptr qcp; 125 | { 126 | std::unique_lock lock(__mtx); // 这个锁是保护查找操作的 127 | auto it = __queue_consumers.find(queue_name); 128 | if (it == __queue_consumers.end()) { 129 | LOG(ERROR) << "cannot find this queue_consumer handler: [" << queue_name << "]" << std::endl; 130 | return consumer::ptr(); 131 | } 132 | qcp = it->second; 133 | } 134 | return qcp->create(ctag, queue_name, ack_flag, cb); // 这里面有自己的锁 135 | } 136 | void remove(const std::string& ctag, const std::string& queue_name) { 137 | queue_consumer::ptr qcp; 138 | { 139 | std::unique_lock lock(__mtx); // 这个锁是保护查找操作的 140 | auto it = __queue_consumers.find(queue_name); 141 | if (it == __queue_consumers.end()) { 142 | LOG(ERROR) << "cannot find this queue_consumer handler: [" << queue_name << "]" << std::endl; 143 | return; 144 | } 145 | qcp = it->second; 146 | } 147 | return qcp->remove(ctag); 148 | } // 删除指定队列里面的指定消费者 149 | consumer::ptr choose(const std::string& queue_name) { 150 | queue_consumer::ptr qcp; 151 | { 152 | std::unique_lock lock(__mtx); // 这个锁是保护查找操作的 153 | auto it = __queue_consumers.find(queue_name); 154 | if (it == __queue_consumers.end()) { 155 | LOG(ERROR) << "cannot find this queue_consumer handler: [" << queue_name << "]" << std::endl; 156 | return consumer::ptr(); 157 | } 158 | qcp = it->second; 159 | } 160 | return qcp->rr_choose(); 161 | } 162 | bool empty(const std::string& queue_name) { 163 | queue_consumer::ptr qcp; 164 | { 165 | std::unique_lock lock(__mtx); // 这个锁是保护查找操作的 166 | auto it = __queue_consumers.find(queue_name); 167 | if (it == __queue_consumers.end()) { 168 | LOG(ERROR) << "cannot find this queue_consumer handler: [" << queue_name << "]" << std::endl; 169 | return false; 170 | } 171 | qcp = it->second; 172 | } 173 | return qcp->empty(); 174 | } 175 | bool exists(const std::string& ctag, const std::string& queue_name) { 176 | queue_consumer::ptr qcp; 177 | { 178 | std::unique_lock lock(__mtx); // 这个锁是保护查找操作的 179 | auto it = __queue_consumers.find(queue_name); 180 | if (it == __queue_consumers.end()) { 181 | LOG(ERROR) << "cannot find this queue_consumer handler: [" << queue_name << "]" << std::endl; 182 | return false; 183 | } 184 | qcp = it->second; 185 | } 186 | return qcp->exists(ctag); 187 | } 188 | void clear() { 189 | std::unique_lock lock(__mtx); 190 | __queue_consumers.clear(); 191 | } 192 | }; 193 | } // namespace hare_mq 194 | 195 | #endif -------------------------------------------------------------------------------- /HareMQ/mqserver/exchange.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | #ifndef __YUFC_EXCHANGE__ 8 | #define __YUFC_EXCHANGE__ 9 | 10 | #include "../mqcommon/helper.hpp" 11 | #include "../mqcommon/logger.hpp" 12 | #include "../mqcommon/msg.pb.h" 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | namespace hare_mq { 20 | /** 21 | * 1. 交换机类 22 | * 2. 交换机数据持久化管理类 23 | * 3. 交换机数据内存管理类 24 | */ 25 | struct exchange { 26 | /* 交换机类 */ 27 | public: 28 | using ptr = std::shared_ptr; 29 | std::string name; // 交换机名称 30 | ExchangeType type; // 交换机类型 31 | bool durable; // 持久化标志 32 | bool auto_delete; // 自动删除标志 33 | std::unordered_map args; // 其他参数 34 | public: 35 | exchange() { } 36 | exchange(const std::string ename, 37 | ExchangeType etype, 38 | bool edurable, 39 | bool eauto_delete, 40 | const std::unordered_map& eargs) 41 | : name(ename) 42 | , type(etype) 43 | , durable(edurable) 44 | , auto_delete(eauto_delete) 45 | , args(eargs) { } 46 | // args存储的格式是键值对,在存储数据库的时候,会组织一个字符串进行存储 key=value&key=value 47 | void set_args(const std::string& str_args) { 48 | /** 49 | * 解析 str_args 字符串: key=value&key=value... 存到 args 成员变量中去 50 | */ 51 | // 用字符串切割就行了 52 | std::vector sub_args; 53 | size_t ret = string_helper::split(str_args, "&", &sub_args); 54 | for (auto& single_arg : sub_args) { 55 | size_t pos = single_arg.find("="); 56 | std::string key = single_arg.substr(0, pos); 57 | std::string val = single_arg.substr(pos + 1); 58 | args.insert({ key, val }); 59 | } 60 | } 61 | std::string get_args() { 62 | /** 63 | * set_args()的反操作,把args里面的数据序列化成 key=value&key=value... 的格式 64 | */ 65 | std::string result; 66 | for (auto start = args.begin(); start != args.end(); ++start) 67 | result += start->first + "=" + start->second + "&"; 68 | return result; 69 | } 70 | }; 71 | 72 | // 创建表的sql语句 73 | #define EXCHANGE_CREATE_TABLE "create table if not exists exchange_table(\ 74 | name varchar(32) primary key, \ 75 | type int, \ 76 | durable int, \ 77 | auto_delete int, \ 78 | args varchar(128));" 79 | // 删除表的sql语句 80 | #define EXCHANGE_DROP_TABLE "drop table if exists exchange_table;" 81 | // 新增交换机的sql语句 82 | #define EXCHANGE_INSERT_SQL "insert into exchange_table values('%s', %d, %d, %d, '%s');" 83 | // 删除交换机的sql语句 84 | #define EXCHANGE_DELETE_SQL "delete from exchange_table where name='%s';" 85 | // 查询获取所有交换机的sql语句 86 | #define EXCHANGE_SELECT_SQL "select name, type, durable, auto_delete, args from exchange_table;" 87 | 88 | class exchange_mapper { 89 | /* 交换机数据持久化管理类 */ 90 | private: 91 | sqlite_helper __sql_helper; // sqlite操作句柄 92 | public: 93 | exchange_mapper(const std::string& dbfile) 94 | : __sql_helper(dbfile) { 95 | // 构造,需要传递数据库文件名称 96 | std::string path = file_helper::parent_dir(dbfile); 97 | file_helper::create_dir(path); 98 | assert(__sql_helper.open()); 99 | create_table(); 100 | } 101 | 102 | public: 103 | void create_table() { 104 | // 创建表 105 | bool ret = __sql_helper.exec(EXCHANGE_CREATE_TABLE, nullptr, nullptr); 106 | if (ret == false) 107 | abort(); 108 | } 109 | void remove_table() { 110 | // 删除表 111 | bool ret = __sql_helper.exec(EXCHANGE_DROP_TABLE, nullptr, nullptr); 112 | if (ret == false) 113 | abort(); 114 | } 115 | bool insert(exchange::ptr& e) { 116 | // 插入交换机 117 | char sql_str[4096] = { 0 }; 118 | sprintf(sql_str, EXCHANGE_INSERT_SQL, 119 | e->name.c_str(), e->type, 120 | e->durable == true ? 1 : 0, e->auto_delete == true ? 1 : 0, 121 | e->get_args().c_str()); 122 | return __sql_helper.exec(sql_str, nullptr, nullptr); 123 | } 124 | void remove(const std::string& name) { 125 | // 移除交换机 126 | char sql_str[4096] = { 0 }; 127 | sprintf(sql_str, EXCHANGE_DELETE_SQL, name.c_str()); 128 | bool ret = __sql_helper.exec(sql_str, nullptr, nullptr); 129 | } 130 | std::unordered_map all() { 131 | // recovery 132 | std::unordered_map res; 133 | __sql_helper.exec(EXCHANGE_SELECT_SQL, select_callback, &res); // 这个是需要回调来组织查询回来的结果的 134 | return res; 135 | } 136 | 137 | private: 138 | // typedef int (*sqlite_callback)(void*, int, char**, char**); 139 | static int select_callback(void* arg, int numcol, char** row, char** fields) { 140 | // 需要static, 避免this 141 | std::unordered_map* result 142 | = (std::unordered_map*)arg; 143 | auto exp = std::make_shared(); 144 | exp->name = row[0]; // row[0]是名字 145 | exp->type = (ExchangeType)std::stoi(row[1]); // row[1]是type, 都是按顺序的 row 就是一个C风格的字符串数组 146 | exp->durable = (bool)std::stoi(row[2]); 147 | exp->auto_delete = (bool)std::stoi(row[3]); 148 | if (row[4]) 149 | exp->set_args(row[4]); // 注意这个字段是不一定有的, 如果null就不要设置 150 | result->insert({ exp->name, exp }); 151 | return 0; // return 0; 必须有, 不然sqlite会出问题,这个demo里面也提到了 152 | } 153 | }; 154 | 155 | class exchange_manager { 156 | /* 交换机数据内存管理类 */ 157 | public: 158 | using ptr = std::shared_ptr; 159 | 160 | private: 161 | exchange_mapper __mapper; // 持久化管理 162 | std::unordered_map __exchanges; // 管理所有的交换机 163 | std::mutex __mtx; // exchange_manager 会被多线程调用,管理一个互斥锁 164 | public: 165 | exchange_manager(const std::string& dbfile) 166 | : __mapper(dbfile) { 167 | __exchanges = __mapper.all(); // 直接获取所有的交换机(恢复历史数据) 168 | } 169 | bool declare_exchange(const std::string& name, 170 | ExchangeType type, 171 | bool durable, 172 | bool auto_delete, 173 | std::unordered_map& args) { 174 | // 声明交换机 175 | std::unique_lock lock(__mtx); // 需要加锁保护 176 | auto it = __exchanges.find(name); 177 | if (it != __exchanges.end()) // 如果交换机已经存在,不需要重复新增 178 | return true; 179 | auto exp = std::make_shared(name, type, durable, auto_delete, args); 180 | if (durable == true) 181 | __mapper.insert(exp); 182 | __exchanges.insert({ name, exp }); 183 | return true; 184 | } 185 | void delete_exchange(const std::string& name) { 186 | // 删除交换机 187 | std::unique_lock lock(__mtx); // 需要加锁保护 188 | // 如果存在就删除,如果不存在就直接返回 189 | auto it = __exchanges.find(name); 190 | if (it == __exchanges.end()) // 如果交换机不存在,直接返回 191 | return; 192 | // 删除 193 | if (it->second->durable == true) 194 | __mapper.remove(name); // 如果是持久化的才会调用mapper的删除 195 | __exchanges.erase(name); 196 | } 197 | exchange::ptr select_exchange(const std::string& name) { 198 | // 返回一台交换机对象 199 | std::unique_lock lock(__mtx); // 需要加锁保护 200 | // 如果存在就删除,如果不存在就直接返回 201 | auto it = __exchanges.find(name); 202 | if (it == __exchanges.end()) 203 | return exchange::ptr(); // null 204 | return it->second; 205 | } 206 | std::unordered_map select_all_exchanges() { 207 | return this->__exchanges; 208 | } 209 | bool exists(const std::string& name) { 210 | // 判断交换机是否存在 211 | std::unique_lock lock(__mtx); // 需要加锁保护 212 | auto it = __exchanges.find(name); 213 | if (it == __exchanges.end()) 214 | return false; 215 | return true; 216 | } 217 | void clear_exchange() { 218 | std::unique_lock lock(__mtx); // 需要加锁保护 219 | __mapper.remove_table(); 220 | __exchanges.clear(); 221 | } 222 | size_t size() { 223 | std::unique_lock lock(__mtx); 224 | return __exchanges.size(); 225 | } 226 | }; 227 | } // namespace hare_mq 228 | 229 | #endif -------------------------------------------------------------------------------- /HareMQ/mqserver/makefile: -------------------------------------------------------------------------------- 1 | CFLAG= -I../tools/muduo/include 2 | LFLAG= -L../tools/muduo/lib -lgtest -lprotobuf -lsqlite3 -lpthread -lmuduo_net -lmuduo_base -lz 3 | server: server.cc ../mqcommon/*.cc ../tools/muduo/include/muduo/protoc/codec.cc 4 | g++ -g -std=c++11 $(CFLAG) $^ -o $@ $(LFLAG) 5 | .PHONY:clean 6 | clean: 7 | rm -f server; 8 | rm -rf data/ log/; -------------------------------------------------------------------------------- /HareMQ/mqserver/queue.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | #ifndef __YUFC_QUEUE__ 8 | #define __YUFC_QUEUE__ 9 | 10 | #include "../mqcommon/helper.hpp" 11 | #include "../mqcommon/logger.hpp" 12 | #include "../mqcommon/msg.pb.h" 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | namespace hare_mq { 20 | /* 这一部分和exchange的实现基本一样 */ 21 | struct msg_queue { 22 | using ptr = std::shared_ptr; 23 | std::string name; // 队列名称 24 | bool durable; // 持久化标志 25 | bool exclusive; // 是否独占 26 | bool auto_delete; 27 | std::unordered_map args; // 其他参数 28 | msg_queue() { } 29 | msg_queue(const std::string& qname, 30 | bool qdurable, 31 | bool qexclusive, 32 | bool qauto_delete, 33 | const std::unordered_map& qargs) 34 | : name(qname) 35 | , durable(qdurable) 36 | , exclusive(qexclusive) 37 | , auto_delete(qauto_delete) 38 | , args(qargs) { } 39 | // 下面两个接口和exchange的是完全一样的 40 | void set_args(const std::string& str_args) { 41 | std::vector sub_args; 42 | size_t ret = string_helper::split(str_args, "&", &sub_args); 43 | for (auto& single_arg : sub_args) { 44 | size_t pos = single_arg.find("="); 45 | std::string key = single_arg.substr(0, pos); 46 | std::string val = single_arg.substr(pos + 1); 47 | args.insert({ key, val }); 48 | } 49 | } 50 | std::string get_args() { 51 | std::string result; 52 | for (auto start = args.begin(); start != args.end(); ++start) 53 | result += start->first + "=" + start->second + "&"; 54 | return result; 55 | } 56 | }; 57 | using queue_map = std::unordered_map; 58 | #define QUEUE_CREATE_TABLE "create table if not exists queue_table(\ 59 | name varchar(32) primary key, \ 60 | durable int, \ 61 | exclusive int, \ 62 | auto_delete int, \ 63 | args varchar(128));" 64 | #define QUEUE_DROP_TABLE "drop table if exists queue_table;" 65 | #define QUEUE_INSERT_SQL "insert into queue_table values('%s', %d, %d, %d, '%s');" 66 | #define QUEUE_DELETE_SQL "delete from queue_table where name='%s';" 67 | #define QUEUE_SELECT_SQL "select * from queue_table;" 68 | 69 | class msg_queue_mapper { 70 | private: 71 | sqlite_helper __sql_helper; // sql操作句柄 72 | public: 73 | msg_queue_mapper(const std::string& dbfile) 74 | : __sql_helper(dbfile) { 75 | std::string path = file_helper::parent_dir(dbfile); 76 | file_helper::create_dir(path); 77 | assert(__sql_helper.open()); 78 | create_table(); 79 | } 80 | void create_table() { 81 | bool ret = __sql_helper.exec(QUEUE_CREATE_TABLE, nullptr, nullptr); 82 | if (ret == false) 83 | abort(); 84 | } 85 | void remove_table() { 86 | bool ret = __sql_helper.exec(QUEUE_DROP_TABLE, nullptr, nullptr); 87 | if (ret == false) 88 | abort(); 89 | } 90 | bool insert(msg_queue::ptr& q) { 91 | char sql_str[4096] = { 0 }; 92 | sprintf(sql_str, QUEUE_INSERT_SQL, 93 | q->name.c_str(), q->durable == true ? 1 : 0, 94 | q->exclusive == true ? 1 : 0, q->auto_delete == true ? 1 : 0, 95 | q->get_args().c_str()); 96 | return __sql_helper.exec(sql_str, nullptr, nullptr); 97 | } 98 | void remove(const std::string& name) { 99 | char sql_str[4096] = { 0 }; 100 | sprintf(sql_str, QUEUE_DELETE_SQL, name.c_str()); 101 | bool ret = __sql_helper.exec(sql_str, nullptr, nullptr); 102 | } 103 | queue_map all() { 104 | // recovery 105 | queue_map res; 106 | __sql_helper.exec(QUEUE_SELECT_SQL, select_callback, &res); // 这个是需要回调来组织查询回来的结果的 107 | return res; 108 | } 109 | 110 | private: 111 | // typedef int (*sqlite_callback)(void*, int, char**, char**); // same to exchange 112 | static int select_callback(void* arg, int numcol, char** row, char** fields) { 113 | queue_map* result = (queue_map*)arg; 114 | auto qptr = std::make_shared(); 115 | qptr->name = row[0]; // row[0]是名字 116 | qptr->durable = (bool)std::stoi(row[1]); 117 | qptr->exclusive = (bool)std::stoi(row[2]); 118 | qptr->auto_delete = (bool)std::stoi(row[3]); 119 | if (row[4]) 120 | qptr->set_args(row[4]); 121 | result->insert({ qptr->name, qptr }); 122 | return 0; 123 | } 124 | }; 125 | 126 | class msg_queue_manager { 127 | public: 128 | using ptr = std::shared_ptr; 129 | private: 130 | std::mutex __mtx; 131 | msg_queue_mapper __mapper; 132 | queue_map __msg_queues; // all same to exchange 133 | public: 134 | msg_queue_manager(const std::string& dbfile) 135 | : __mapper(dbfile) { 136 | __msg_queues = __mapper.all(); // recovery 137 | } 138 | bool declare_queue(const std::string& qname, 139 | bool qdurable, 140 | bool qexclusive, 141 | bool qauto_delete, 142 | const std::unordered_map& qargs) { 143 | // all same to exchange 144 | std::unique_lock lock(__mtx); 145 | auto it = __msg_queues.find(qname); 146 | if (it != __msg_queues.end()) 147 | return true; 148 | auto qptr = std::make_shared(qname, qdurable, qexclusive, qauto_delete, qargs); 149 | if (qdurable == true) { 150 | if (!__mapper.insert(qptr)) 151 | return false; 152 | } 153 | __msg_queues.insert({ qname, qptr }); 154 | return true; 155 | } 156 | void delete_queue(const std::string& name) { 157 | // all same to exchange 158 | std::unique_lock lock(__mtx); 159 | auto it = __msg_queues.find(name); 160 | if (it == __msg_queues.end()) 161 | return; 162 | if (it->second->durable == true) 163 | __mapper.remove(name); 164 | __msg_queues.erase(name); 165 | } 166 | msg_queue::ptr select_queue(const std::string& name) { 167 | std::unique_lock lock(__mtx); 168 | auto it = __msg_queues.find(name); 169 | if (it == __msg_queues.end()) 170 | return msg_queue::ptr(); 171 | return it->second; 172 | } 173 | queue_map all() { 174 | std::unique_lock lock(__mtx); 175 | return __msg_queues; 176 | } 177 | bool exists(const std::string& name) { 178 | std::unique_lock lock(__mtx); 179 | auto it = __msg_queues.find(name); 180 | return it == __msg_queues.end() ? false : true; 181 | } 182 | size_t size() { 183 | std::unique_lock lock(__mtx); 184 | return __msg_queues.size(); 185 | } 186 | void clear_queues() { 187 | std::unique_lock lock(__mtx); 188 | __mapper.remove_table(); 189 | __msg_queues.clear(); 190 | } 191 | }; 192 | } // namespace hare_mq 193 | 194 | #endif -------------------------------------------------------------------------------- /HareMQ/mqserver/route.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | #ifndef __YUFC_ROUTE__ 8 | #define __YUFC_ROUTE__ 9 | 10 | #include "../mqcommon/helper.hpp" 11 | #include "../mqcommon/logger.hpp" 12 | #include "../mqcommon/msg.pb.h" 13 | 14 | namespace hare_mq { 15 | class router { 16 | public: 17 | static bool is_legal_routing_key(const std::string& routing_key) { 18 | // 只需要判断是否包含非法字符即可 19 | for (const auto& ch : routing_key) { 20 | if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '_' || ch == '.') 21 | continue; 22 | return false; 23 | } 24 | return true; 25 | } 26 | static bool is_legal_binding_key(const std::string& binding_key) { 27 | // 1. 判断是否包含非法字符 28 | for (const auto& ch : binding_key) { 29 | if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '_' || ch == '.' || ch == '*' || ch == '#') 30 | continue; 31 | return false; 32 | } 33 | // 2. * # 必须独立存在 34 | std::vector sub; 35 | string_helper::split(binding_key, ".", &sub); 36 | for (const auto& e : sub) { 37 | if (e.size() > 1 && (e.find('*') != std::string::npos || e.find('#') != std::string::npos)) 38 | return false; 39 | } 40 | // 3. * # 不能连续出现 41 | for (size_t i = 1; i < sub.size(); ++i) { 42 | if ((sub[i] == "#" && sub[i - 1] == "*") || (sub[i] == "#" && sub[i - 1] == "#") || (sub[i] == "*" && sub[i - 1] == "#")) 43 | return false; 44 | } 45 | return true; 46 | } 47 | static bool route(ExchangeType type, const std::string& routing_key, const std::string& binding_key) { 48 | if (type == ExchangeType::DIRECT) { 49 | return routing_key == binding_key; 50 | } else if (type == ExchangeType::FANOUT) { 51 | return true; 52 | } else { 53 | // dp 模式匹配 54 | return __dp_matching(routing_key, binding_key); 55 | } 56 | } 57 | 58 | private: 59 | static bool __dp_matching(const std::string& routing_key, const std::string& binding_key) { 60 | // 1. 将binding_key与routing_key进行字符串分割,得到各个的单词数组 61 | std::vector bkeys, rkeys; 62 | int n_bkey = string_helper::split(binding_key, ".", &bkeys); 63 | int n_rkey = string_helper::split(routing_key, ".", &rkeys); 64 | // 2. 定义标记数组,并初始化[0][0]位置为true,其他位置为false 65 | std::vector> dp(n_bkey + 1, std::vector(n_rkey + 1, false)); 66 | dp[0][0] = true; 67 | // 3. 如果binding_key以#起始,则将#对应行的第0位置置为1 68 | for (int i = 1; i <= bkeys.size(); ++i) { 69 | if (bkeys[i - 1] == "#") { 70 | dp[i][0] = true; 71 | continue; 72 | } 73 | break; 74 | } 75 | // 4. 使用routing_key中的每个单词与binding_key中的每个单词进行匹配并标记数组 76 | for (int i = 1; i <= n_bkey; i++) { 77 | for (int j = 1; j <= n_rkey; j++) { 78 | // 如果当前 bkey 是单词或者 * ,或者两个单词相同,表示单词匹配成功,从左上方继承结果 79 | if (bkeys[i - 1] == rkeys[j - 1] || bkeys[i - 1] == "*") 80 | dp[i][j] = dp[i - 1][j - 1]; 81 | // 如果当前 bkey 是 # , 则需要从左上,左边,上边继承结果 82 | else if (bkeys[i - 1] == "#") 83 | dp[i][j] = dp[i - 1][j - 1] | dp[i][j - 1] | dp[i - 1][j]; 84 | } 85 | } 86 | return dp[n_bkey][n_rkey]; 87 | } 88 | }; 89 | } // namespace hare_mq 90 | 91 | #endif -------------------------------------------------------------------------------- /HareMQ/mqserver/server.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | #include "broker_server.hpp" 8 | 9 | int main() { 10 | hare_mq::BrokerServer svr(8085, "./data"); 11 | svr.start(); 12 | return 0; 13 | } -------------------------------------------------------------------------------- /HareMQ/mqserver/virtual_host.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | #ifndef __YUFC_VIRTUAL_HOST__ 8 | #define __YUFC_VIRTUAL_HOST__ 9 | 10 | #include "../mqcommon/logger.hpp" 11 | #include "binding.hpp" 12 | #include "exchange.hpp" 13 | #include "message.hpp" 14 | #include "queue.hpp" 15 | #include 16 | 17 | namespace hare_mq { 18 | class virtual_host { 19 | public: 20 | using ptr = std::shared_ptr; // ptr 21 | private: 22 | std::string __host_name; 23 | exchange_manager::ptr __emp; 24 | msg_queue_manager::ptr __mqmp; 25 | binding_manager::ptr __bmp; 26 | message_manager::ptr __mmp; // 四个句柄 27 | public: 28 | virtual_host(const std::string& host_name, const std::string& basedir, const std::string& dbfile) 29 | : __host_name(host_name) 30 | , __emp(std::make_shared(dbfile)) 31 | , __mqmp(std::make_shared(dbfile)) 32 | , __bmp(std::make_shared(dbfile)) 33 | , __mmp(std::make_shared(basedir)) { 34 | // 获取到所有的队列信息,通过队列名称恢复历史消息数据 35 | auto qm = __mqmp->all(); 36 | for (auto& q : qm) 37 | __mmp->init_queue_msg(q.first); // 恢复了历史数据 38 | } 39 | bool declare_exchange(const std::string& name, 40 | ExchangeType type, 41 | bool durable, 42 | bool auto_delete, 43 | std::unordered_map& args) { 44 | return __emp->declare_exchange(name, type, durable, auto_delete, args); 45 | } // 声明交换机 46 | void delete_exchange(const std::string& name) { 47 | // 删除交换机的时候,和这台交换机相关的绑定信息也要删除 48 | __bmp->unbind_exchange(name); 49 | __emp->delete_exchange(name); 50 | } // 删除交换机 51 | bool declare_queue(const std::string& qname, 52 | bool qdurable, 53 | bool qexclusive, 54 | bool qauto_delete, 55 | const std::unordered_map& qargs) { 56 | // 初始化队列的消息句柄(消息的存储管理) 57 | // 队列的创建 58 | __mmp->init_queue_msg(qname); 59 | return __mqmp->declare_queue(qname, qdurable, qexclusive, qauto_delete, qargs); 60 | } // 声明队列 61 | void delete_queue(const std::string& name) { 62 | __mmp->destroy_queue_msg(name); 63 | __bmp->unbind_queue(name); 64 | __mqmp->delete_queue(name); 65 | } // 删除队列 66 | bool bind(const std::string& ename, const std::string& qname, const std::string& key) { 67 | exchange::ptr ep = __emp->select_exchange(ename); 68 | if (ep == nullptr) { 69 | LOG(ERROR) << "binding [exchange:" << ename << "] and [queue:" << qname << "] failed, exchange undefined" << std::endl; 70 | return false; 71 | } 72 | msg_queue::ptr mqp = __mqmp->select_queue(qname); 73 | if (mqp == nullptr) { 74 | LOG(ERROR) << "binding [exchange:" << ename << "] and [queue:" << qname << "] failed, queue undefined" << std::endl; 75 | return false; 76 | } 77 | return __bmp->bind(ename, qname, key, (ep->durable && mqp->durable)); // 需要两个都是持久化才能设置持久化 78 | } // 绑定交换机和队列 79 | void unbind(const std::string& ename, const std::string& qname) { 80 | __bmp->unbind(ename, qname); 81 | return; 82 | } // 解除绑定交换机和队列 83 | msg_queue_binding_map exchange_bindings(const std::string& ename) { 84 | return __bmp->get_exchange_bindings(ename); // 获取交换机的绑定信息 85 | } // 获取一台交换机的所有绑定信息 86 | queue_map all_queues() { 87 | return __mqmp->all(); 88 | } 89 | bool basic_publish(const std::string& qname, BasicProperties* bp, const std::string& body) { 90 | msg_queue::ptr mqp = __mqmp->select_queue(qname); // 先找到这个队列 91 | if (mqp == nullptr) { 92 | LOG(ERROR) << "public failed, queue:" << qname << " undefined" << std::endl; 93 | return false; 94 | } 95 | return __mmp->insert(qname, bp, body, mqp->durable); 96 | } // 发布一条消息 97 | message_ptr basic_consume(const std::string& qname) { 98 | return __mmp->front(qname); 99 | } // 消费一条消息 100 | void basic_ack(const std::string& qname, const std::string& msgid) { 101 | __mmp->ack(qname, msgid); 102 | } // 确认一条消息 103 | std::string basic_query() { 104 | std::string yellow_bold = "\033[1;33m"; // 1 表示加粗, 33 表示前景色为黄色 105 | std::string reset = "\033[0m"; // 重置样式 106 | std::vector> all_bindings; 107 | // 获取所有交换机 108 | auto all_ex = __emp->select_all_exchanges(); 109 | std::string res_str; 110 | res_str += yellow_bold + "------------------------ Query ------------------------\n" + reset; 111 | res_str += yellow_bold + "exists exchanges: " + reset + '\n'; 112 | res_str += " "; 113 | for (const auto& e : all_ex) { 114 | // 先获取这台交换机的所有绑定信息 115 | msg_queue_binding_map this_ex_bindings = exchange_bindings(e.first); 116 | for (const auto& q : this_ex_bindings) { 117 | assert(q.second->exchange_name == e.first); 118 | all_bindings.push_back(std::make_tuple(q.second->exchange_name, q.second->msg_queue_name, q.second->binding_key)); 119 | } 120 | res_str += e.first; 121 | res_str += ", "; 122 | } 123 | res_str += '\n'; 124 | // 获取所有队列 125 | auto all_q = all_queues(); 126 | res_str += yellow_bold + "exists queues: \n" + reset; 127 | res_str += " "; 128 | for (const auto& e : all_q) { 129 | res_str += e.first; 130 | res_str += ", "; 131 | } 132 | res_str += '\n'; 133 | // 整理所有binding 134 | res_str += yellow_bold + "exists bindings: \n" + reset; 135 | for (const auto& e : all_bindings) { 136 | std::string tmp_ename = std::get<0>(e); 137 | std::string tmp_qname = std::get<1>(e); 138 | std::string tmp_binding_key = std::get<2>(e); 139 | std::string line = " " + tmp_ename + "<-->" + tmp_qname + ", binding_key: " + tmp_binding_key + "\n"; 140 | res_str += line; 141 | } 142 | res_str += yellow_bold + "-------------------------------------------------------" + reset; 143 | return res_str; 144 | } 145 | void clear() { 146 | __emp->clear_exchange(); 147 | __mqmp->clear_queues(); 148 | __bmp->clear_bindings(); 149 | __mmp->clear(); 150 | } 151 | 152 | public: 153 | // for debugs 154 | bool exists_exchange(const std::string& ename) { return __emp->exists(ename); } 155 | bool exists_queue(const std::string& qname) { return __mqmp->exists(qname); } 156 | bool exists_binding(const std::string& ename, const std::string& qname) { return __bmp->exists(ename, qname); } 157 | exchange::ptr select_exchange(const std::string& ename) { return __emp->select_exchange(ename); } 158 | }; 159 | } // namespace hare_mq 160 | 161 | #endif -------------------------------------------------------------------------------- /HareMQ/mqtest/binding_test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | #include "../mqserver/binding.hpp" 8 | #include 9 | 10 | using namespace hare_mq; 11 | 12 | 13 | binding_manager::ptr bmptr = nullptr; 14 | 15 | class binding_test : public testing::Environment { 16 | public: 17 | virtual void SetUp() override { 18 | bmptr = std::make_shared("./data-binding/meta.db"); 19 | } 20 | virtual void TearDown() override { 21 | // bmptr->clear_bindings(); // 22 | } 23 | }; 24 | 25 | 26 | #define RECOVERY_TEST 1 // 是否测试recovery 27 | #if !RECOVERY_TEST 28 | TEST(binding_test, insert_test) { 29 | bmptr->bind("exchange1", "queue1", "news.music.#", true); 30 | bmptr->bind("exchange1", "queue2", "news.sport.#", true); 31 | bmptr->bind("exchange1", "queue3", "news.sport.basketball", true); 32 | bmptr->bind("exchange2", "queue1", "news.music.pop", true); 33 | bmptr->bind("exchange2", "queue2", "news.sport.football", true); 34 | bmptr->bind("exchange2", "queue3", "news.sport.swimming", true); 35 | /** 36 | * e1, q1 37 | * e1, q2 38 | * e1, q3 39 | * e2, q1 40 | * e2, q2 41 | * e2, q3 42 | */ 43 | ASSERT_EQ(bmptr->size(), 6); 44 | } 45 | TEST(binding_test, select_test) { 46 | /** 47 | * e1, q1 48 | * e1, q2 49 | * e1, q3 50 | * e2, q1 51 | * e2, q2 52 | * e2, q3 53 | */ 54 | ASSERT_EQ(bmptr->exists("exchange1", "queue1"), true); 55 | ASSERT_EQ(bmptr->exists("exchange1", "queue2"), true); 56 | ASSERT_EQ(bmptr->exists("exchange1", "queue3"), true); 57 | ASSERT_EQ(bmptr->exists("exchange2", "queue1"), true); 58 | ASSERT_EQ(bmptr->exists("exchange2", "queue2"), true); 59 | ASSERT_EQ(bmptr->exists("exchange2", "queue3"), true); 60 | 61 | binding::ptr bp = bmptr->get_binding("exchange1", "queue1"); 62 | ASSERT_NE(bp, nullptr); 63 | ASSERT_EQ(bp->exchange_name, std::string("exchange1")); 64 | ASSERT_EQ(bp->msg_queue_name, std::string("queue1")); 65 | ASSERT_EQ(bp->binding_key, std::string("news.music.#")); 66 | } 67 | 68 | TEST(binding_test, select_exchange_test) { 69 | /** 70 | * e1, q1 71 | * e1, q2 72 | * e1, q3 73 | * e2, q1 74 | * e2, q2 75 | * e2, q3 76 | */ 77 | msg_queue_binding_map mqbm = bmptr->get_exchange_bindings("exchange1"); 78 | ASSERT_EQ(mqbm.size(), 3); 79 | ASSERT_NE(mqbm.find("queue1"), mqbm.end()); 80 | ASSERT_NE(mqbm.find("queue2"), mqbm.end()); 81 | ASSERT_NE(mqbm.find("queue3"), mqbm.end()); 82 | } 83 | 84 | TEST(binding_test, remove_queue_test) { 85 | /** 86 | * e1, q1 87 | * e1, q2 88 | * e1, q3 89 | * e2, q1 90 | * e2, q2 91 | * e2, q3 92 | */ 93 | bmptr->unbind_queue("queue1"); 94 | ASSERT_EQ(bmptr->exists("exchange1", "queue1"), false); 95 | ASSERT_EQ(bmptr->exists("exchange2", "queue1"), false); 96 | /** 97 | * e1, q2 98 | * e1, q3 99 | * e2, q2 100 | * e2, q3 101 | */ 102 | } 103 | 104 | TEST(binding, remove_exchange_test) { 105 | /** 106 | * e1, q2 107 | * e1, q3 108 | * e2, q2 109 | * e2, q3 110 | */ 111 | bmptr->unbind_exchange("exchange1"); 112 | ASSERT_EQ(bmptr->exists("exchange1", "queue1"), false); 113 | ASSERT_EQ(bmptr->exists("exchange1", "queue2"), false); 114 | ASSERT_EQ(bmptr->exists("exchange1", "queue3"), false); 115 | /** 116 | * e2, q2 117 | * e2, q3 118 | */ 119 | } 120 | 121 | TEST(binding, remove_test) { 122 | /** 123 | * e2, q2 124 | * e2, q3 125 | */ 126 | bmptr->unbind("exchange2", "queue3"); 127 | ASSERT_EQ(bmptr->exists("exchange2", "queue3"), false); // 刚刚删掉的 128 | ASSERT_EQ(bmptr->exists("exchange2", "queue1"), false); // remove_queue_test 里删掉了 129 | ASSERT_EQ(bmptr->exists("exchange2", "queue2"), true); // 还在 130 | /** 131 | * e2, q2 132 | */ 133 | } 134 | #else 135 | TEST(binding, recovery_test) { 136 | /** 137 | * e2, q2 138 | */ 139 | ASSERT_EQ(bmptr->size(), 1); 140 | ASSERT_EQ(bmptr->exists("exchange1", "queue1"), false); 141 | ASSERT_EQ(bmptr->exists("exchange1", "queue2"), false); 142 | ASSERT_EQ(bmptr->exists("exchange1", "queue3"), false); 143 | ASSERT_EQ(bmptr->exists("exchange2", "queue1"), false); 144 | ASSERT_EQ(bmptr->exists("exchange2", "queue2"), true); 145 | ASSERT_EQ(bmptr->exists("exchange2", "queue3"), false); 146 | } 147 | #endif 148 | 149 | int main(int argc, char** argv) { 150 | testing::InitGoogleTest(&argc, argv); 151 | testing::AddGlobalTestEnvironment(new binding_test); 152 | auto res = RUN_ALL_TESTS(); 153 | LOG(INFO) << "res: " << res << std::endl; 154 | return 0; 155 | } -------------------------------------------------------------------------------- /HareMQ/mqtest/channel_simple_test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | #include "../mqserver/channel.hpp" 8 | 9 | int main() { 10 | hare_mq::channel_manager::ptr cmp = std::make_shared(); 11 | cmp->open_channel("c1", 12 | std::make_shared("host1", "./host1/message/", "./host1/host1.db"), 13 | std::make_shared(), 14 | hare_mq::ProtobufCodecPtr(), 15 | muduo::net::TcpConnectionPtr(), 16 | thread_pool::ptr()); 17 | return 0; 18 | } -------------------------------------------------------------------------------- /HareMQ/mqtest/connection_simple_test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | #include "../mqserver/connection.hpp" 8 | using namespace hare_mq; 9 | int main() { 10 | auto cm = std::make_shared(); 11 | cm->new_connection(std::make_shared("host1", "./host1/message/", "./host1/host1.db"), 12 | std::make_shared(), 13 | hare_mq::ProtobufCodecPtr(), 14 | muduo::net::TcpConnectionPtr(), 15 | thread_pool::ptr()); 16 | return 0; 17 | } -------------------------------------------------------------------------------- /HareMQ/mqtest/consumer_test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | #include "../mqserver/consumer.hpp" 8 | #include 9 | 10 | using namespace hare_mq; 11 | 12 | consumer_manager::ptr cmp = nullptr; 13 | 14 | class consumer_test : public testing::Environment { 15 | public: 16 | virtual void SetUp() override { 17 | cmp = std::make_shared(); 18 | cmp->init_queue_consumer("queue1"); 19 | } 20 | virtual void TearDown() override { 21 | // cmp->clear(); 22 | } 23 | }; 24 | 25 | void cb(const std::string& tag, const BasicProperties* bp, const std::string& body) { 26 | std::cout << tag << " consume a mesg: " << body << std::endl; 27 | } 28 | 29 | // 测试新增 30 | TEST(consumer_test, insert_test) { 31 | cmp->create("consumer1", "queue1", false, cb); 32 | cmp->create("consumer2", "queue1", false, cb); 33 | cmp->create("consumer3", "queue1", false, cb); 34 | ASSERT_EQ(cmp->exists("consumer1", "queue1"), true); 35 | ASSERT_EQ(cmp->exists("consumer2", "queue1"), true); 36 | ASSERT_EQ(cmp->exists("consumer3", "queue1"), true); 37 | ASSERT_EQ(cmp->exists("consumer3", "queue2"), false); 38 | ASSERT_EQ(cmp->exists("consumer4", "queue1"), false); 39 | } 40 | TEST(consumer_test, remove_test) { 41 | cmp->remove("consumer1", "queue1"); 42 | ASSERT_EQ(cmp->exists("consumer1", "queue1"), false); 43 | ASSERT_EQ(cmp->exists("consumer2", "queue1"), true); 44 | ASSERT_EQ(cmp->exists("consumer3", "queue1"), true); 45 | } 46 | 47 | TEST(consumer_test, choose_test) { 48 | consumer::ptr cp = cmp->choose("queue1"); 49 | ASSERT_NE(cp, nullptr); 50 | ASSERT_EQ(cp->tag, "consumer2"); 51 | 52 | cp = cmp->choose("queue1"); 53 | ASSERT_NE(cp, nullptr); 54 | ASSERT_EQ(cp->tag, "consumer3"); 55 | 56 | cp = cmp->choose("queue1"); 57 | ASSERT_NE(cp, nullptr); 58 | ASSERT_EQ(cp->tag, "consumer2"); 59 | } 60 | 61 | int main(int argc, char** argv) { 62 | testing::InitGoogleTest(&argc, argv); 63 | testing::AddGlobalTestEnvironment(new consumer_test); 64 | auto res = RUN_ALL_TESTS(); 65 | LOG(INFO) << "res: " << res << std::endl; 66 | return 0; 67 | } -------------------------------------------------------------------------------- /HareMQ/mqtest/data-binding/meta.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/HareMQ/mqtest/data-binding/meta.db -------------------------------------------------------------------------------- /HareMQ/mqtest/exchange_test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | #include "../mqserver/exchange.hpp" 8 | #include 9 | 10 | using namespace hare_mq; 11 | 12 | exchange_manager::ptr emp = nullptr; 13 | 14 | #define IF_GTEST 1 15 | 16 | #if IF_GTEST 17 | class exchange_test : public testing::Environment { 18 | public: 19 | virtual void SetUp() override { 20 | emp = std::make_shared("./data-exchange/meta.db"); 21 | } 22 | virtual void TearDown() override { 23 | // emp->clear_exchange(); // 24 | } 25 | }; 26 | 27 | TEST(exchange_test, insert_test) { 28 | std::unordered_map map = { { "k1", "v1" }, { "k2", "v2" } }; 29 | emp->declare_exchange("exchange1", ExchangeType::DIRECT, true, false, map); 30 | emp->declare_exchange("exchange2", ExchangeType::DIRECT, true, false, map); 31 | emp->declare_exchange("exchange3", ExchangeType::DIRECT, true, false, map); 32 | emp->declare_exchange("exchange4", ExchangeType::DIRECT, true, false, map); 33 | ASSERT_EQ(emp->size(), 4); 34 | } 35 | TEST(exchange_test, select_test) { 36 | exchange::ptr exp = emp->select_exchange("exchange3"); 37 | ASSERT_NE(exp, nullptr); 38 | ASSERT_EQ(exp->name, "exchange3"); 39 | ASSERT_EQ(exp->durable, true); 40 | ASSERT_EQ(exp->auto_delete, false); 41 | ASSERT_EQ(exp->type, ExchangeType::DIRECT); 42 | // ASSERT_EQ(exp->get_args(), std::string("k1=v1&k2=v2&")); 43 | } 44 | TEST(exchange_test, remove_test) { 45 | emp->delete_exchange("exchange3"); 46 | exchange::ptr exp = emp->select_exchange("exchange3"); 47 | ASSERT_EQ(exp, nullptr); // 这里应该是找不到的 48 | ASSERT_EQ(emp->exists("exchange3"), false); // 这里应该是找不到的 49 | } 50 | TEST(exchange_test, recovery) { 51 | // 测试加载历史数据 52 | // 跑一次不clear的, 然后屏蔽insert的测试,然后来测试recovery 53 | ASSERT_EQ(emp->exists("exchange1"), true); 54 | ASSERT_EQ(emp->exists("exchange2"), true); 55 | ASSERT_EQ(emp->exists("exchange3"), false); // 这里应该是找不到的, 在上面被删掉了 56 | ASSERT_EQ(emp->exists("exchange4"), true); 57 | } 58 | #endif 59 | 60 | 61 | #if !IF_GTEST 62 | void my_test() { 63 | emp = std::make_shared("./data/meta.db"); 64 | std::unordered_map map = { { "k1", "v1" }, { "k2", "v2" } }; 65 | emp->declare_exchange("exchange1", ExchangeType::DIRECT, true, false, map); 66 | emp->declare_exchange("exchange2", ExchangeType::DIRECT, true, false, map); 67 | emp->declare_exchange("exchange3", ExchangeType::DIRECT, true, false, map); 68 | emp->declare_exchange("exchange4", ExchangeType::DIRECT, true, false, map); 69 | } 70 | #endif 71 | 72 | int main(int argc, char** argv) { 73 | #if IF_GTEST 74 | testing::InitGoogleTest(&argc, argv); 75 | testing::AddGlobalTestEnvironment(new exchange_test); 76 | auto res = RUN_ALL_TESTS(); 77 | LOG(INFO) << "res: " << res << std::endl; 78 | #else 79 | my_test(); 80 | #endif 81 | return 0; 82 | } -------------------------------------------------------------------------------- /HareMQ/mqtest/file_test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | #include "../mqcommon/helper.hpp" 8 | 9 | void test1() { 10 | hare_mq::file_helper helper("../mqcommon/logger.hpp"); 11 | hare_mq::LOG(DEBUG) << "file if exists: " << helper.exists() << std::endl; 12 | hare_mq::LOG(DEBUG) << "file size: " << helper.size() << std::endl; 13 | } 14 | 15 | void test2() { 16 | hare_mq::file_helper helper("./aaa/bbb/ccc/tmp.hpp"); 17 | if (helper.exists() == false) { 18 | std::string p = hare_mq::file_helper::parent_dir(helper.path()); // 先获取父级目录 19 | // hare_mq::LOG(DEBUG) << p << std::endl; 20 | if (hare_mq::file_helper(p).exists() == false) { 21 | // 创建目录 22 | hare_mq::file_helper::create_dir(p); 23 | } 24 | hare_mq::file_helper::create(helper.path()); 25 | } 26 | } 27 | 28 | void test3() { 29 | hare_mq::file_helper helper1("../mqcommon/logger.hpp"); 30 | hare_mq::file_helper helper2("./aaa/bbb/ccc/tmp.hpp"); 31 | std::string body; 32 | helper1.read(body); 33 | // write to tmp.hpp 34 | helper2.write(body); 35 | } 36 | 37 | void test4() { 38 | hare_mq::file_helper helper("./aaa/bbb/ccc/tmp.hpp"); 39 | // 把6-19个字节读取出来 40 | char str[16] = { 0 }; 41 | helper.read(str, 6, 13); // 这里要读 (6, 19] 这些字符,应该传入 6, 13 42 | hare_mq::LOG(DEBUG) << std::string(str) << std::endl; 43 | helper.write("123456\n", 19, 7); 44 | } 45 | 46 | void test5() { 47 | hare_mq::file_helper helper("./aaa/bbb/ccc/tmp.hpp"); 48 | helper.rename(hare_mq::file_helper::parent_dir(helper.path()) + "/test.hpp"); 49 | } 50 | 51 | void test6() { 52 | hare_mq::LOG(DEBUG) << "before run" << std::endl; 53 | system("tree ."); 54 | hare_mq::file_helper::create("./aaa/bbb/ccc/tmp.hpp"); 55 | hare_mq::LOG(DEBUG) << "run: create(\"./aaa/bbb/ccc/tmp.hpp\");" << std::endl; 56 | system("tree ."); 57 | hare_mq::file_helper::remove("./aaa/bbb/ccc/tmp.hpp"); 58 | hare_mq::LOG(DEBUG) << "run: remove(\"./aaa/bbb/ccc/tmp.hpp\");" << std::endl; 59 | system("tree ."); 60 | hare_mq::file_helper::remove_dir("./aaa/bbb/ccc/"); 61 | hare_mq::LOG(DEBUG) << "run: remove_dir(\"./aaa/bbb/ccc/\");" << std::endl; 62 | system("tree ."); 63 | hare_mq::file_helper::remove_dir("./aaa"); 64 | hare_mq::LOG(DEBUG) << "run: remove_dir(\"./aaa\");" << std::endl; 65 | system("tree ."); 66 | } 67 | 68 | int main() { 69 | test6(); 70 | return 0; 71 | } -------------------------------------------------------------------------------- /HareMQ/mqtest/host1/host1.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/HareMQ/mqtest/host1/host1.db -------------------------------------------------------------------------------- /HareMQ/mqtest/host_test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | #include "../mqserver/virtual_host.hpp" 8 | #include 9 | 10 | using namespace hare_mq; 11 | 12 | class host_test : public testing::Test { 13 | public: 14 | void SetUp() override { 15 | std::unordered_map empty_map = std::unordered_map(); 16 | __host = std::make_shared("host1", "./host1/message/", "./host1/host1.db"); 17 | __host->declare_exchange("exchange1", ExchangeType::DIRECT, true, false, empty_map); 18 | __host->declare_exchange("exchange2", ExchangeType::DIRECT, true, false, empty_map); 19 | __host->declare_exchange("exchange3", ExchangeType::DIRECT, true, false, empty_map); 20 | 21 | __host->declare_queue("queue1", true, false, false, empty_map); 22 | __host->declare_queue("queue2", true, false, false, empty_map); 23 | __host->declare_queue("queue3", true, false, false, empty_map); 24 | 25 | __host->bind("exchange1", "queue1", "news.music.#"); 26 | __host->bind("exchange1", "queue2", "news.music.#"); 27 | __host->bind("exchange1", "queue3", "news.music.#"); 28 | 29 | __host->bind("exchange2", "queue1", "news.music.#"); 30 | __host->bind("exchange2", "queue2", "news.music.#"); 31 | __host->bind("exchange2", "queue3", "news.music.#"); 32 | 33 | __host->bind("exchange3", "queue1", "news.music.#"); 34 | __host->bind("exchange3", "queue2", "news.music.#"); 35 | __host->bind("exchange3", "queue3", "news.music.#"); 36 | 37 | __host->basic_publish("queue1", nullptr, "hello world-1"); 38 | __host->basic_publish("queue1", nullptr, "hello world-2"); 39 | __host->basic_publish("queue1", nullptr, "hello world-3"); 40 | 41 | __host->basic_publish("queue2", nullptr, "hello world-1"); 42 | __host->basic_publish("queue2", nullptr, "hello world-2"); 43 | __host->basic_publish("queue2", nullptr, "hello world-3"); 44 | 45 | __host->basic_publish("queue3", nullptr, "hello world-1"); 46 | __host->basic_publish("queue3", nullptr, "hello world-2"); 47 | __host->basic_publish("queue3", nullptr, "hello world-3"); 48 | } 49 | void TearDown() override { 50 | __host->clear(); 51 | } 52 | 53 | public: 54 | virtual_host::ptr __host; 55 | }; 56 | 57 | // 验证新增信息是否正常 58 | TEST_F(host_test, init_test) { 59 | ASSERT_EQ(__host->exists_exchange("exchange1"), true); 60 | ASSERT_EQ(__host->exists_exchange("exchange2"), true); 61 | ASSERT_EQ(__host->exists_exchange("exchange3"), true); 62 | 63 | ASSERT_EQ(__host->exists_queue("queue1"), true); 64 | ASSERT_EQ(__host->exists_queue("queue2"), true); 65 | ASSERT_EQ(__host->exists_queue("queue3"), true); 66 | 67 | ASSERT_EQ(__host->exists_binding("exchange1", "queue1"), true); 68 | ASSERT_EQ(__host->exists_binding("exchange1", "queue2"), true); 69 | ASSERT_EQ(__host->exists_binding("exchange1", "queue3"), true); 70 | 71 | ASSERT_EQ(__host->exists_binding("exchange2", "queue1"), true); 72 | ASSERT_EQ(__host->exists_binding("exchange2", "queue2"), true); 73 | ASSERT_EQ(__host->exists_binding("exchange2", "queue3"), true); 74 | 75 | ASSERT_EQ(__host->exists_binding("exchange3", "queue1"), true); 76 | ASSERT_EQ(__host->exists_binding("exchange3", "queue2"), true); 77 | ASSERT_EQ(__host->exists_binding("exchange3", "queue3"), true); 78 | 79 | message_ptr msg1 = __host->basic_consume("queue1"); 80 | ASSERT_EQ(msg1->payload().body(), "hello world-1"); 81 | msg1 = __host->basic_consume("queue1"); 82 | ASSERT_EQ(msg1->payload().body(), "hello world-2"); 83 | msg1 = __host->basic_consume("queue1"); 84 | ASSERT_EQ(msg1->payload().body(), "hello world-3"); 85 | msg1 = __host->basic_consume("queue1"); 86 | ASSERT_EQ(msg1, nullptr); 87 | } 88 | 89 | // 验证删除是否正常 90 | TEST_F(host_test, remove_exchange_test) { 91 | ASSERT_EQ(__host->exists_exchange("exchange1"), true); 92 | __host->delete_exchange("exchange1"); 93 | ASSERT_EQ(__host->exists_binding("exchange1", "queue1"), false); 94 | ASSERT_EQ(__host->exists_binding("exchange1", "queue2"), false); 95 | ASSERT_EQ(__host->exists_binding("exchange1", "queue3"), false); 96 | } 97 | TEST_F(host_test, remove_queue_test) { 98 | ASSERT_EQ(__host->exists_queue("queue1"), true); 99 | __host->delete_queue("queue1"); 100 | ASSERT_EQ(__host->exists_binding("exchange1", "queue1"), false); 101 | ASSERT_EQ(__host->exists_binding("exchange2", "queue1"), false); 102 | ASSERT_EQ(__host->exists_binding("exchange3", "queue1"), false); 103 | message_ptr msg1 = __host->basic_consume("queue1"); 104 | ASSERT_EQ(msg1, nullptr); 105 | } 106 | 107 | // 验证ack 108 | TEST_F(host_test, ack_test) { 109 | message_ptr msg1 = __host->basic_consume("queue1"); 110 | ASSERT_EQ(msg1->payload().body(), "hello world-1"); 111 | __host->basic_ack("queue1", msg1->payload().properties().id()); 112 | msg1 = __host->basic_consume("queue1"); 113 | ASSERT_EQ(msg1->payload().body(), "hello world-2"); 114 | __host->basic_ack("queue1", msg1->payload().properties().id()); 115 | msg1 = __host->basic_consume("queue1"); 116 | ASSERT_EQ(msg1->payload().body(), "hello world-3"); 117 | __host->basic_ack("queue1", msg1->payload().properties().id()); 118 | msg1 = __host->basic_consume("queue1"); 119 | ASSERT_EQ(msg1, nullptr); 120 | } 121 | 122 | int main(int argc, char** argv) { 123 | testing::InitGoogleTest(&argc, argv); 124 | auto res = RUN_ALL_TESTS(); 125 | LOG(INFO) << "res: " << res << std::endl; 126 | return 0; 127 | } -------------------------------------------------------------------------------- /HareMQ/mqtest/makefile: -------------------------------------------------------------------------------- 1 | test: test_logger.cc ../mqcommon/*.cc 2 | g++ -o $@ $^ -std=c++11 -g -lgtest -lprotobuf -lsqlite3 -I../libs/muduo/include 3 | .PHONY:clean 4 | clean: 5 | rm -f test -------------------------------------------------------------------------------- /HareMQ/mqtest/map_helper_test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | #include "../mqcommon/helper.hpp" 8 | #include 9 | using namespace hare_mq; 10 | 11 | class map_helper_test : public testing::Environment { 12 | public: 13 | virtual void SetUp() override { 14 | } 15 | virtual void TearDown() override { 16 | } 17 | }; 18 | 19 | TEST(map_helper_test, test) { 20 | google::protobuf::Map proto_map; 21 | proto_map["one"] = "1"; 22 | proto_map["two"] = "2"; 23 | std::unordered_map std_map = map_helper::ConvertProtoMapToStdMap(proto_map); 24 | ASSERT_EQ(std_map.size(), 2); 25 | ASSERT_EQ(std_map["one"], "1"); 26 | ASSERT_EQ(std_map["two"], "2"); 27 | google::protobuf::Map converted_proto_map = map_helper::ConvertStdMapToProtoMap(std_map); 28 | ASSERT_EQ(converted_proto_map.size(), 2); 29 | ASSERT_EQ(converted_proto_map["one"], "1"); 30 | ASSERT_EQ(converted_proto_map["two"], "2"); 31 | } 32 | 33 | int main(int argc, char** argv) { 34 | testing::InitGoogleTest(&argc, argv); 35 | testing::AddGlobalTestEnvironment(new map_helper_test); 36 | auto res = RUN_ALL_TESTS(); 37 | LOG(INFO) << "res: " << res << std::endl; 38 | return 0; 39 | } -------------------------------------------------------------------------------- /HareMQ/mqtest/msg_test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | #include "../mqserver/message.hpp" 8 | #include 9 | 10 | using namespace hare_mq; 11 | 12 | hare_mq::message_manager::ptr mmp; 13 | 14 | class msg_test : public testing::Environment { 15 | public: 16 | virtual void SetUp() override { 17 | mmp = std::make_shared("./data-mesg"); 18 | mmp->init_queue_msg("queue1"); 19 | } 20 | virtual void TearDown() override { 21 | mmp->clear(); 22 | } 23 | }; 24 | 25 | #define ONLY_INSERT 1 26 | #define RECOVERY_TEST 0 27 | #if !RECOVERY_TEST 28 | // 新增消息测试:新增消息,观察可获取消息数量,以及持久化消息数量 29 | TEST(msg_test, insert_test) { 30 | BasicProperties properties; 31 | properties.set_id(uuid_helper::uuid()); 32 | properties.set_delivery_mode(DeliveryMode::DURABLE); 33 | properties.set_routing_key("news.music.pop"); 34 | mmp->insert("queue1", &properties, "hello world-1", DeliveryMode::DURABLE); 35 | mmp->insert("queue1", nullptr, "hello world-2", DeliveryMode::DURABLE); 36 | mmp->insert("queue1", nullptr, "hello world-3", DeliveryMode::DURABLE); 37 | mmp->insert("queue1", nullptr, "hello world-4", DeliveryMode::DURABLE); 38 | mmp->insert("queue1", nullptr, "hello world-5", DeliveryMode::UNDURABLE); 39 | ASSERT_EQ(mmp->getable_count("queue1"), 5); 40 | ASSERT_EQ(mmp->total_count("queue1"), 4); 41 | ASSERT_EQ(mmp->durable_count("queue1"), 4); 42 | ASSERT_EQ(mmp->wait_ack_count("queue1"), 0); 43 | } 44 | // 获取消息测试:获取一条消息,在不进行确认,以及进行确认后,查看可获取消息数量,待确认消息数量,以及测试消息获取的顺序 45 | #if !ONLY_INSERT 46 | TEST(msg_test, select_test) { 47 | ASSERT_EQ(mmp->getable_count("queue1"), 5); 48 | ASSERT_EQ(mmp->total_count("queue1"), 4); 49 | ASSERT_EQ(mmp->durable_count("queue1"), 4); 50 | ASSERT_EQ(mmp->wait_ack_count("queue1"), 0); 51 | message_ptr msg = mmp->front("queue1"); 52 | ASSERT_EQ(msg->payload().body(), std::string("hello world-1")); 53 | ASSERT_EQ(mmp->getable_count("queue1"), 4); 54 | ASSERT_EQ(mmp->wait_ack_count("queue1"), 1); 55 | msg = mmp->front("queue1"); 56 | ASSERT_EQ(msg->payload().body(), std::string("hello world-2")); 57 | ASSERT_EQ(mmp->getable_count("queue1"), 3); 58 | ASSERT_EQ(mmp->wait_ack_count("queue1"), 2); 59 | msg = mmp->front("queue1"); 60 | ASSERT_EQ(msg->payload().body(), std::string("hello world-3")); 61 | ASSERT_EQ(mmp->getable_count("queue1"), 2); 62 | ASSERT_EQ(mmp->wait_ack_count("queue1"), 3); 63 | msg = mmp->front("queue1"); 64 | ASSERT_EQ(msg->payload().body(), std::string("hello world-4")); 65 | ASSERT_EQ(mmp->getable_count("queue1"), 1); 66 | ASSERT_EQ(mmp->wait_ack_count("queue1"), 4); 67 | msg = mmp->front("queue1"); 68 | // ASSERT_EQ(msg, nullptr); 69 | } 70 | #endif 71 | #else 72 | // 恢复历史消息数据 73 | TEST(msg_test, recovery_test) { 74 | ASSERT_EQ(mmp->getable_count("queue1"), 4); 75 | message_ptr msg = mmp->front("queue1"); 76 | ASSERT_EQ(msg->payload().body(), "hello world-1"); 77 | ASSERT_EQ(mmp->getable_count("queue1"), 3); 78 | ASSERT_EQ(mmp->wait_ack_count("queue1"), 1); 79 | msg = mmp->front("queue1"); 80 | ASSERT_EQ(msg->payload().body(), "hello world-2"); 81 | ASSERT_EQ(mmp->getable_count("queue1"), 2); 82 | ASSERT_EQ(mmp->wait_ack_count("queue1"), 2); 83 | msg = mmp->front("queue1"); 84 | ASSERT_EQ(msg->payload().body(), "hello world-3"); 85 | ASSERT_EQ(mmp->getable_count("queue1"), 1); 86 | ASSERT_EQ(mmp->wait_ack_count("queue1"), 3); 87 | msg = mmp->front("queue1"); 88 | ASSERT_EQ(msg->payload().body(), "hello world-4"); 89 | ASSERT_EQ(mmp->getable_count("queue1"), 0); 90 | ASSERT_EQ(mmp->wait_ack_count("queue1"), 4); 91 | msg = mmp->front("queue1"); 92 | ASSERT_EQ(msg, nullptr); 93 | } 94 | #endif 95 | // 删除消息测试:确认一条消息,查看持久化消息数量以及待确认消息数量 96 | TEST(msg_test, delete_ack_test) { 97 | ASSERT_EQ(mmp->getable_count("queue1"), 5); 98 | message_ptr msg = mmp->front("queue1"); 99 | ASSERT_NE(msg.get(), nullptr); 100 | ASSERT_EQ(msg->payload().body(), "hello world-1"); 101 | ASSERT_EQ(mmp->getable_count("queue1"), 4); 102 | ASSERT_EQ(mmp->wait_ack_count("queue1"), 1); 103 | mmp->ack("queue1", msg->payload().properties().id()); 104 | ASSERT_EQ(mmp->durable_count("queue1"), 3); 105 | ASSERT_EQ(mmp->getable_count("queue1"), 4); 106 | ASSERT_EQ(mmp->total_count("queue1"), 4); // 还是4,因为还没垃圾回收 107 | ASSERT_EQ(mmp->wait_ack_count("queue1"), 0); 108 | } 109 | // 销毁测试 110 | TEST(message_test, clear_test) { 111 | // 前面都clear了很多次了,不测了 112 | } 113 | 114 | int main(int argc, char** argv) { 115 | testing::InitGoogleTest(&argc, argv); 116 | testing::AddGlobalTestEnvironment(new msg_test); 117 | auto res = RUN_ALL_TESTS(); 118 | LOG(INFO) << "res: " << res << std::endl; 119 | return 0; 120 | } -------------------------------------------------------------------------------- /HareMQ/mqtest/queue_test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | #include "../mqserver/queue.hpp" 8 | #include 9 | 10 | using namespace hare_mq; 11 | 12 | msg_queue_manager::ptr qmptr = nullptr; 13 | 14 | #define IF_GTEST 1 15 | 16 | #if IF_GTEST 17 | class queue_test : public testing::Environment { 18 | public: 19 | virtual void SetUp() override { 20 | qmptr = std::make_shared("./data-queue/meta.db"); 21 | } 22 | virtual void TearDown() override { 23 | qmptr->clear_queues(); // 24 | } 25 | }; 26 | 27 | TEST(queue_test, insert_test) { 28 | std::unordered_map map = { { "k1", "v1" } }; 29 | qmptr->declare_queue("queue1", true, true, false, map); 30 | qmptr->declare_queue("queue2", true, true, false, map); 31 | qmptr->declare_queue("queue3", true, true, false, map); 32 | qmptr->declare_queue("queue4", true, true, false, map); 33 | ASSERT_EQ(qmptr->size(), 4); 34 | } 35 | TEST(queue_test, select_test) { 36 | msg_queue::ptr qptr = qmptr->select_queue("queue4"); 37 | ASSERT_NE(qptr, nullptr); 38 | ASSERT_EQ(qptr->name, "queue4"); 39 | ASSERT_EQ(qptr->durable, true); 40 | ASSERT_EQ(qptr->exclusive, true); 41 | ASSERT_EQ(qptr->auto_delete, false); 42 | ASSERT_EQ(qptr->get_args(), "k1=v1&"); 43 | } 44 | TEST(queue_test, remove_test) { 45 | qmptr->delete_queue("queue3"); 46 | msg_queue::ptr qptr = qmptr->select_queue("queue3"); 47 | ASSERT_EQ(qptr, nullptr); // 这里应该是找不到的 48 | ASSERT_EQ(qmptr->exists("queue3"), false); // 这里应该是找不到的 49 | } 50 | TEST(queue_test, recovery) { 51 | // 测试加载历史数据 52 | // 跑一次不clear的, 然后屏蔽insert的测试,然后来测试recovery 53 | ASSERT_EQ(qmptr->exists("queue1"), true); 54 | ASSERT_EQ(qmptr->exists("queue2"), true); 55 | ASSERT_EQ(qmptr->exists("queue3"), false); // 这里应该是找不到的, 在上面被删掉了 56 | ASSERT_EQ(qmptr->exists("queue4"), true); 57 | } 58 | #endif 59 | 60 | #if !IF_GTEST 61 | void my_test() { 62 | } 63 | #endif 64 | 65 | int main(int argc, char** argv) { 66 | #if IF_GTEST 67 | testing::InitGoogleTest(&argc, argv); 68 | testing::AddGlobalTestEnvironment(new queue_test); 69 | auto res = RUN_ALL_TESTS(); 70 | LOG(INFO) << "res: " << res << std::endl; 71 | #else 72 | my_test(); 73 | #endif 74 | return 0; 75 | } -------------------------------------------------------------------------------- /HareMQ/mqtest/route_test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | #include "../mqserver/route.hpp" 8 | #include 9 | 10 | using namespace hare_mq; 11 | 12 | class route_test : public testing::Environment { 13 | public: 14 | virtual void SetUp() override { } 15 | virtual void TearDown() override { } 16 | }; 17 | 18 | TEST(route_test, legal_routing_key) { 19 | std::string r1 = "news.music.pop"; 20 | std::string r2 = "news..music.pop"; 21 | std::string r3 = "news.,music.pop"; 22 | std::string r4 = "news.music_123.pop"; 23 | ASSERT_EQ(router::is_legal_routing_key(r1), true); 24 | ASSERT_EQ(router::is_legal_routing_key(r2), true); 25 | ASSERT_EQ(router::is_legal_routing_key(r3), false); 26 | ASSERT_EQ(router::is_legal_routing_key(r4), true); 27 | } 28 | 29 | TEST(route_test, legal_binding_key) { 30 | std::string b1 = "news.music.pop"; 31 | std::string b2 = "news.#.music.pop"; 32 | std::string b3 = "news.#.*.music.pop"; // 33 | std::string b4 = "news.*.#.music.pop"; // 34 | std::string b5 = "news.#.#.music.pop"; // 35 | std::string b6 = "news.*.*.music.pop"; 36 | std::string b7 = "news.music_123.pop"; 37 | std::string b8 = "news.,music_123.pop"; // 38 | std::string b9 = "#"; 39 | ASSERT_EQ(router::is_legal_binding_key(b1), true); 40 | ASSERT_EQ(router::is_legal_binding_key(b2), true); 41 | ASSERT_EQ(router::is_legal_binding_key(b3), false); 42 | ASSERT_EQ(router::is_legal_binding_key(b4), false); 43 | ASSERT_EQ(router::is_legal_binding_key(b5), false); 44 | ASSERT_EQ(router::is_legal_binding_key(b6), true); 45 | ASSERT_EQ(router::is_legal_binding_key(b7), true); 46 | ASSERT_EQ(router::is_legal_binding_key(b8), false); 47 | ASSERT_EQ(router::is_legal_binding_key(b9), true); 48 | } 49 | 50 | TEST(route_test, route) { 51 | std::vector binding_keys = { 52 | "aaa", 53 | "aaa.bbb", 54 | "aaa.bbb", 55 | "aaa.bbb", 56 | "aaa.#.bbb", 57 | "aaa.bbb.#", 58 | "#.bbb.ccc", 59 | "aaa.bbb.ccc", 60 | "aaa.*", 61 | "aaa.*.bbb", 62 | "*.aaa.bbb", 63 | "#", 64 | "aaa.#", 65 | "aaa.#", 66 | "aaa.#.ccc", 67 | "aaa.#.ccc", 68 | "aaa.#.ccc", 69 | "#.ccc", 70 | "#.ccc", 71 | "aaa.#.ccc.ccc", 72 | "aaa.#.bbb.*.bbb" 73 | }; 74 | std::vector routing_keys = { 75 | "aaa", 76 | "aaa.bbb", 77 | "aaa.bbb.ccc", 78 | "aaa.ccc", 79 | "aaa.bbb.ccc", 80 | "aaa.ccc.bbb", 81 | "aaa.bbb.ccc.ddd", 82 | "aaa.bbb.ccc", 83 | "aaa.bbb", 84 | "aaa.bbb.ccc", 85 | "aaa.bbb", 86 | "aaa.bbb.ccc", 87 | "aaa.bbb", 88 | "aaa.bbb.ccc", 89 | "aaa.ccc", 90 | "aaa.bbb.ccc", 91 | "aaa.aaa.bbb.ccc", 92 | "ccc", 93 | "aaa.bbb.ccc", 94 | "aaa.bbb.ccc.ccc.ccc", 95 | "aaa.ddd.ccc.bbb.eee.bbb" 96 | }; 97 | std::vector results = { 98 | true, 99 | true, 100 | false, 101 | false, 102 | false, 103 | false, 104 | false, 105 | true, 106 | true, 107 | false, 108 | false, 109 | true, 110 | true, 111 | true, 112 | true, 113 | true, 114 | true, 115 | true, 116 | true, 117 | true, 118 | true 119 | }; 120 | ASSERT_EQ(binding_keys.size(), routing_keys.size()); 121 | ASSERT_EQ(binding_keys.size(), results.size()); 122 | for (size_t i = 0; i < binding_keys.size(); ++i) { 123 | LOG(DEBUG) << "i: " << i << "/21 " << routing_keys[i] << ":" << binding_keys[i] << std::endl; 124 | ASSERT_EQ(router::route(ExchangeType::TOPIC, routing_keys[i], binding_keys[i]), results[i]); 125 | } 126 | } 127 | 128 | int main(int argc, char** argv) { 129 | testing::InitGoogleTest(&argc, argv); 130 | testing::AddGlobalTestEnvironment(new route_test); 131 | auto res = RUN_ALL_TESTS(); 132 | LOG(INFO) << "res: " << res << std::endl; 133 | return 0; 134 | } -------------------------------------------------------------------------------- /HareMQ/mqtest/server_simple_test.cc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/HareMQ/mqtest/server_simple_test.cc -------------------------------------------------------------------------------- /HareMQ/mqtest/test_logger.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | */ 6 | 7 | #include "../mqcommon/logger.hpp" 8 | using namespace hare_mq; 9 | int main() { 10 | LOG(DEBUG) << "hello world" << std::endl; 11 | LOG(INFO) << "hello world" << std::endl; 12 | return 0; 13 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Fengcheng Yu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /assets/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/assets/1.png -------------------------------------------------------------------------------- /assets/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/assets/2.png -------------------------------------------------------------------------------- /assets/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/assets/3.png -------------------------------------------------------------------------------- /assets/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/assets/4.png -------------------------------------------------------------------------------- /assets/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/assets/5.png -------------------------------------------------------------------------------- /assets/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/assets/6.png -------------------------------------------------------------------------------- /assets/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/assets/7.png -------------------------------------------------------------------------------- /docs/assets/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/assets/1.png -------------------------------------------------------------------------------- /docs/assets/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/assets/10.png -------------------------------------------------------------------------------- /docs/assets/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/assets/11.png -------------------------------------------------------------------------------- /docs/assets/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/assets/12.png -------------------------------------------------------------------------------- /docs/assets/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/assets/13.png -------------------------------------------------------------------------------- /docs/assets/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/assets/14.png -------------------------------------------------------------------------------- /docs/assets/15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/assets/15.png -------------------------------------------------------------------------------- /docs/assets/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/assets/16.png -------------------------------------------------------------------------------- /docs/assets/17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/assets/17.png -------------------------------------------------------------------------------- /docs/assets/18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/assets/18.png -------------------------------------------------------------------------------- /docs/assets/19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/assets/19.png -------------------------------------------------------------------------------- /docs/assets/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/assets/2.png -------------------------------------------------------------------------------- /docs/assets/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/assets/20.png -------------------------------------------------------------------------------- /docs/assets/21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/assets/21.png -------------------------------------------------------------------------------- /docs/assets/22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/assets/22.png -------------------------------------------------------------------------------- /docs/assets/23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/assets/23.png -------------------------------------------------------------------------------- /docs/assets/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/assets/3.png -------------------------------------------------------------------------------- /docs/assets/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/assets/4.png -------------------------------------------------------------------------------- /docs/assets/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/assets/5.png -------------------------------------------------------------------------------- /docs/assets/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/assets/6.png -------------------------------------------------------------------------------- /docs/assets/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/assets/7.png -------------------------------------------------------------------------------- /docs/assets/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/assets/8.png -------------------------------------------------------------------------------- /docs/assets/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/assets/9.png -------------------------------------------------------------------------------- /docs/asynchronous-en.md: -------------------------------------------------------------------------------- 1 | # Asynchronous operations in C++11 2 | 3 | - [简体中文](./asynchronous.md) 4 | - [English](./asynchronous-en.md) 5 | 6 | *** 7 | - [Asynchronous operations in C++11](#asynchronous-operations-in-c11) 8 | - [What is `std::future`](#what-is-stdfuture) 9 | - [Use `std::async` to associate asynchronous tasks](#use-stdasync-to-associate-asynchronous-tasks) 10 | - [Example](#example) 11 | - [Use `std::promise` and `std::future` together](#use-stdpromise-and-stdfuture-together) 12 | - [Using `std::packaged_task` with `std::future`](#using-stdpackaged_task-with-stdfuture) 13 | 14 | 15 | The main purpose of learning this part is to implement a thread pool. If a thread pool does not care about the running results of the threads, and only needs to throw tasks out, then this thread pool is very simple and easy to implement. However, if we care about the results of the thread pool, asynchronous operations are required to implement it. 16 | 17 | ## What is `std::future` 18 | 19 | **Documentation: [https://legacy.cplusplus.com/reference/future](https://legacy.cplusplus.com/reference/future/future/?kw=future)** 20 | 21 | `std::future` is a template class in the C++11 standard library that represents the result of an asynchronous operation. When we use asynchronous tasks in multithreaded programming, `std:future` can help us get the execution results of the task when we need it. An important feature of `std:future` is that it can block the current thread until the asynchronous operation is completed, thus ensuring that we will not encounter unfinished operations when getting the results. 22 | 23 | **Application scenarios** 24 | 25 | - Asynchronous tasks: When we need to perform some time-consuming operations in the background, such as network requests or computationally intensive tasks, `std:future` can be used to represent the results of these asynchronous tasks. By separating tasks from the main thread, we can achieve parallel processing of tasks, thereby improving the execution efficiency of the program. 26 | - Concurrency control: In multi-threaded programming, we may need to wait for certain tasks to complete before continuing to perform other operations. By using `std:future`, we can synchronize threads to ensure that the results are obtained after the task is completed and continue to perform subsequent operations 27 | - Result acquisition: `std:future` provides a safe way to obtain the results of asynchronous tasks. We can use the `std::future:get()` function to obtain the results of the task, which blocks the current thread until the asynchronous operation is completed. In this way, when calling the `get()` function, we can ensure that the required results have been obtained. 28 | 29 | 30 | ## Use `std::async` to associate asynchronous tasks 31 | 32 | `std::async` is a simple method that associates a task with a `std::future` task and returns a task associated with it. It creates and runs a `std::future` object associated with the output task result. By default, `std:async` starts a new thread or waits for the parameter in `future`. When this parameter is of `std::launch` type, whether the task runs synchronously depends on the parameter you give, which is of `std::launch` type. 33 | - `std::launch::deferred` indicates that the coefficient will be called delayed until `get()` or `wait()` is called on `future`. 34 | - `std::launch::async` indicates that the function will run on the thread created by itself. 35 | - `std::launch::deferred`, `std::launch::async` automatically selects the strategy based on system and other conditions. 36 | 37 | 38 | ## Example 39 | 40 | ```cpp 41 | #include "../log.hpp" 42 | #include 43 | #include 44 | #include 45 | 46 | int add(int num1, int num2) { 47 | LOG(INFO) << "called: add(int, int)" << std::endl; 48 | return num1 + num2; 49 | } 50 | int main() { 51 | // std::async(func, ...), std::async(policy, func, ...) 52 | std::future res = std::async(std::launch::deferred, add, 11, 12); 53 | LOG(INFO) << "get res: " << res.get() << std::endl; 54 | return 0; 55 | } 56 | ``` 57 | 58 | ![](./assets/19.png) 59 | 60 | It is obviously asynchronous. 61 | 62 | Obviously, if we don't call `get()`, it will not be executed. 63 | 64 | If it is replaced with `std::future res = std::async(std::launch::async, add, 11, 12);`, the effect will be different. As long as `async` is called, it will start to execute. When it will be executed, it is unknown! But it will definitely not wait until `get()` is called. 65 | 66 | ## Use `std::promise` and `std::future` together 67 | 68 | **Document: [https://legacy.cplusplus.com/reference/future/promise/](https://legacy.cplusplus.com/reference/future/promise/?kw=promise)** 69 | 70 | `std::promise` provides a way to set a value, which can be read through the associated `std::future` object after setting. In other words, as mentioned before, `std::future` can read the return value of an asynchronous function, but it has to wait for it to be ready, and `std::promise` provides a way to manually make `std:future` ready. 71 | 72 | 73 | ```cpp 74 | #include "../log.hpp" 75 | #include 76 | #include 77 | #include 78 | 79 | int add(int num1, int num2, std::promise& prom) { 80 | LOG(INFO) << "called: add(int, int)" << std::endl; 81 | prom.set_value(num1 + num2); 82 | return num1 + num2; 83 | } 84 | 85 | int main() { 86 | // Use get_future to establish the association between prom and fu 87 | std::promise prom; 88 | std::future fu = prom.get_future(); 89 | std::thread thr(add, 11, 22, std::ref(prom)); 90 | int res = fu.get(); 91 | LOG(INFO) << "sum: " << res << std::endl; 92 | thr.join(); 93 | return 0; 94 | } 95 | ``` 96 | 97 | It is unknown when the task executed by the thread will be executed, but we can use `get_future` to obtain the amount in the asynchronous task. 98 | 99 | ![](./assets/20.png) 100 | 101 | > [!NOTE] 102 | > Why do we take this approach? Can't we get the value by directly passing an integer address? If we do this, because the thread execution is asynchronous, we don't know when it will be set. If we use `promise`, we can ensure that the asynchronous result must be assigned before it can be obtained outside. This is a synchronous control. Other methods do not have synchronous control! For example, the following code uses output parameters to set. 103 | 104 | ```cpp 105 | int add(int num1, int num2, int* result) { 106 | std::this_thread::sleep_for(std::chrono::seconds(3)); 107 | LOG(INFO) << "called: add(int, int)" << std::endl; 108 | *result = num1 + num2; 109 | return num1 + num2; 110 | } 111 | int main() { 112 | int result = 0; 113 | std::thread thr(add, 11, 22, &result); 114 | int res = result; 115 | LOG(INFO) << "sum: " << res << std::endl; 116 | thr.join(); 117 | return 0; 118 | } 119 | ``` 120 | 121 | ![](./assets/21.png) 122 | 123 | There is no synchronization, so the result obtained from the outside will not wait for the calculation to end, but will directly get the result that has not been set. In essence, there is no synchronization! In fact, there are other ways to synchronize, such as the synchronization variables in the `pthread` library, which is the same principle. 124 | 125 | ## Using `std::packaged_task` with `std::future` 126 | 127 | **Documentation: [https://legacy.cplusplus.com/reference/future/packaged_task/](https://legacy.cplusplus.com/reference/future/packaged_task/)** 128 | 129 | ```cpp 130 | int add(int num1, int num2) { 131 | std::this_thread::sleep_for(std::chrono::seconds(3)); 132 | LOG(INFO) << "called: add(int, int)" << std::endl; 133 | return num1 + num2; 134 | } 135 | 136 | int main() { 137 | // std::packaged_task task(add); 138 | // std::future fu = task.get_future(); 139 | 140 | // task(11, 22) can be called like this 141 | // But it cannot be used as a function object, that is, it cannot be called like this std::thread thr(task) This is not allowed 142 | // std::thread thr(task) and std::async(std::launch::async, task) are essentially the same, both are asynchronous operations, and async also starts a thread 143 | // std::async(std::launch::async, task, 11, 22); // Cannot be called like this 144 | 145 | // But we can define task as a pointer, pass it to the thread, and then dereference it for execution 146 | // If you use ordinary pointers, it is easy to have pointer life cycle problems, so use smart pointers 147 | 148 | auto ptask = std::make_shared>(add); 149 | std::thread thr([ptask]() { 150 | (*ptask); // call this function 151 | }); 152 | std::future fu = ptask->get_future(); 153 | LOG(INFO) << "result: " << fu.get() << std::endl; 154 | thr.join(); 155 | return 0; 156 | } 157 | ``` 158 | 159 | ![](./assets/22.png) -------------------------------------------------------------------------------- /docs/asynchronous.md: -------------------------------------------------------------------------------- 1 | # C++11中的异步操作 2 | 3 | - [简体中文](./asynchronous.md) 4 | - [English](./asynchronous-en.md) 5 | 6 | *** 7 | - [C++11中的异步操作](#c11中的异步操作) 8 | - [`std::future`是什么](#stdfuture是什么) 9 | - [使用`std::async`关联异步任务](#使用stdasync关联异步任务) 10 | - [使用例子](#使用例子) 11 | - [使用`std::promise`和`std::future`配合](#使用stdpromise和stdfuture配合) 12 | - [`std::packaged_task`结合`std::future`的使用](#stdpackaged_task结合stdfuture的使用) 13 | 14 | 15 | 学习这一部分主要是为了实现一个线程池。如果一个线程池中,不关系线程的运行结果,只需要把任务丢出去,那么这个线程池是非常简单的,很容易实现的。但是如果我们关心线程池的结果,就需要异步操作才能实现了。 16 | 17 | ## `std::future`是什么 18 | 19 | **文档: [https://legacy.cplusplus.com/reference/future](https://legacy.cplusplus.com/reference/future/future/?kw=future)** 20 | 21 | `std::future` 是C++11标准库中的一个模板类,它表示一个异步操作的结果。当我们在多线程编程中使用异步任务时,`std:future`可以帮助我们在需要的时候获取任务的执行结果。`std:future`的一个重要特性是能够阻塞当前线程,直到异步操作完成,从而确保我们在获取结果时不会遇到未完成的操作。 22 | 23 | **应用场景** 24 | 25 | - 异步任务: 当我们需要在后台执行一些耗时操作时,如网络请求或计算密集型任务等,`std:future`可以用来表示这些异步任务的结果。通过将任务与主线程分离,我们可以实现任务的并行处理,从而提高程序的执行效率。 26 | - 并发控制: 在多线程编程中,我们可能需要等待某些任务完成后才能继续执行其他操作。通过使用`std:future`,我们可以实现线程之间的同步,确保任务完成后再获取结果并继续执行后续操作 27 | - 结果获取: `std:future`提供了一种安全的方式来获取异步任务的结果。我们可以使用`std::future:get()`函数来获取任务的结果,此函数会阻塞当前线程,直到异步操作完成。这样,在调用`get()`函数时,我们可以确保已经获取到了所需的结果。 28 | 29 | 30 | ## 使用`std::async`关联异步任务 31 | 32 | `std::async`是一种将任务与`std::future`任务,并返回一个与该关联的简单方法。它创建并运行一个异出任务结果关联的`std::future`对象。默认情况下,`std:async`是否启动一个新线程或者在等待`future`内参数。这个参数为`std::launch`类型时,任务是否同步运行都取决于你给的参数,这个参数的类型为`std::launch`类型。 33 | - `std::launch::deferred`表明该系数会被延迟调用,直到在`future`上调用`get()`或者`wait()`才会开始执行任务。 34 | - `std::launch::async`表明函数会在自己创建的线程上运行。 35 | - `std::launch::deferred`, `std::launch::async`内部通过系统等条件自动选择策略。 36 | 37 | 38 | ## 使用例子 39 | 40 | ```cpp 41 | #include "../log.hpp" 42 | #include 43 | #include 44 | #include 45 | 46 | int add(int num1, int num2) { 47 | LOG(INFO) << "called: add(int, int)" << std::endl; 48 | return num1 + num2; 49 | } 50 | int main() { 51 | // std::async(func, ...), std::async(policy, func, ...) 52 | std::future res = std::async(std::launch::deferred, add, 11, 12); 53 | LOG(INFO) << "get res: " << res.get() << std::endl; 54 | return 0; 55 | } 56 | ``` 57 | 58 | ![](./assets/19.png) 59 | 60 | 很明显就是一个异步。 61 | 62 | 很明显,如果我们不去`get()`调用,他是不会执行的。 63 | 64 | 如果换成`std::future res = std::async(std::launch::async, add, 11, 12);`,效果就会不同了,只要调用了`async`就会开始执行了,什么时候执行,不知道!但是一定不会等到调用`get()`的时候才执行。 65 | 66 | ## 使用`std::promise`和`std::future`配合 67 | 68 | **文档: [https://legacy.cplusplus.com/reference/future/promise/](https://legacy.cplusplus.com/reference/future/promise/?kw=promise)** 69 | 70 | `std::promise` 提供了一种设置值的方式,它可以在设置之后通过相关联的`std::future`对象进行读取。换种说法就是之前说过`std::future`可以读取一个异步函数的返回值了,但是要等待就绪,而`std::promise`就提供一种方式手动让`std:future`就绪。 71 | 72 | 73 | ```cpp 74 | #include "../log.hpp" 75 | #include 76 | #include 77 | #include 78 | 79 | int add(int num1, int num2, std::promise& prom) { 80 | LOG(INFO) << "called: add(int, int)" << std::endl; 81 | prom.set_value(num1 + num2); 82 | return num1 + num2; 83 | } 84 | 85 | int main() { 86 | // 通过 get_future 来建立 prom和fu 的关联 87 | std::promise prom; 88 | std::future fu = prom.get_future(); 89 | std::thread thr(add, 11, 22, std::ref(prom)); 90 | int res = fu.get(); 91 | LOG(INFO) << "sum: " << res << std::endl; 92 | thr.join(); 93 | return 0; 94 | } 95 | ``` 96 | 97 | 线程执行的任务什么时候执行是不知道的,但是我们可以通过 `get_future` 来获取异步任务中的量。 98 | 99 | ![](./assets/20.png) 100 | 101 | > [!NOTE] 102 | > 为什么要采取这种方式,直接传递一个整型的地址不是也可以拿到这个值吗?如果这样操作,因为线程执行是异步的,什么时候设置的是不知道的,如果使用`promise`,才能保证:异步结果一定赋值了,外面才会拿到,这是一种同步控制,其他方式是没有同步控制的!比如下面这段代码,使用输出参数去设置。 103 | 104 | ```cpp 105 | int add(int num1, int num2, int* result) { 106 | std::this_thread::sleep_for(std::chrono::seconds(3)); 107 | LOG(INFO) << "called: add(int, int)" << std::endl; 108 | *result = num1 + num2; 109 | return num1 + num2; 110 | } 111 | int main() { 112 | int result = 0; 113 | std::thread thr(add, 11, 22, &result); 114 | int res = result; 115 | LOG(INFO) << "sum: " << res << std::endl; 116 | thr.join(); 117 | return 0; 118 | } 119 | ``` 120 | 121 | ![](./assets/21.png) 122 | 123 | 没有进行同步,所以外面获取结果不会等待计算结束,而是会直接没有被设置过的结果,本质就是没有进行同步!其实同步还可以用其他方法,比如`pthread`库里面的同步变量,也是同一个道理。 124 | 125 | ## `std::packaged_task`结合`std::future`的使用 126 | 127 | **文档: [https://legacy.cplusplus.com/reference/future/packaged_task/](https://legacy.cplusplus.com/reference/future/packaged_task/)** 128 | 129 | ```cpp 130 | int add(int num1, int num2) { 131 | std::this_thread::sleep_for(std::chrono::seconds(3)); 132 | LOG(INFO) << "called: add(int, int)" << std::endl; 133 | return num1 + num2; 134 | } 135 | 136 | int main() { 137 | // std::packaged_task task(add); 138 | // std::future fu = task.get_future(); 139 | 140 | // task(11, 22) 可以这样调用 141 | // 但是不能当作函数对象,也就是说,不能这样调用 std::thread thr(task) 这样是不行的 142 | // std::thread thr(task) 和 std::async(std::launch::async, task) 本质是一样的,都是异步操作,async里面也是起线程 143 | // std::async(std::launch::async, task, 11, 22); // 不能这样调用 144 | 145 | // 但是我们可以把task定义成一个指针,传递到线程中,然后进行解引用执行 146 | // 如果用普通指针,容易出现指针的生命周期问题,所以使用智能指针 147 | 148 | auto ptask = std::make_shared>(add); 149 | std::thread thr([ptask]() { 150 | (*ptask); // 调用这个函数 151 | }); 152 | std::future fu = ptask->get_future(); 153 | LOG(INFO) << "result: " << fu.get() << std::endl; 154 | thr.join(); 155 | return 0; 156 | } 157 | ``` 158 | 159 | ![](./assets/22.png) -------------------------------------------------------------------------------- /docs/documentation.md: -------------------------------------------------------------------------------- 1 | # HareMQ使用文档 2 | 3 | - [简体中文](./documentation.md) 4 | - [English](./documentation-en.md) 5 | 6 | *** 7 | - [HareMQ使用文档](#haremq使用文档) 8 | - [编译运行项目](#编译运行项目) 9 | - [命令手册](#命令手册) 10 | - [所有合法命令](#所有合法命令) 11 | - [`cmds`](#cmds) 12 | - [`clear`](#clear) 13 | - [`logo`](#logo) 14 | - [`exit`](#exit) 15 | - [`cid`](#cid) 16 | - [`svrstat`](#svrstat) 17 | - [`declare_exchange`](#declare_exchange) 18 | - [`delete_exchange`](#delete_exchange) 19 | - [`declare_queue`](#declare_queue) 20 | - [`delete_queue`](#delete_queue) 21 | - [`publish/publish_mode`](#publishpublish_mode) 22 | - [`subscribe`](#subscribe) 23 | - [使用例子](#使用例子) 24 | - [使用配置文件启动客户端](#使用配置文件启动客户端) 25 | - [日志](#日志) 26 | 27 | 28 | ## 编译运行项目 29 | 30 | 关于项目的配置,请见 [README文件](../README-cn.md)。 31 | 32 | 项目服务端代码在 `HareMQ/mqserver` 目录中,项目客户端代码在 `HareMQ/mqclient` 目录中。 33 | 34 | 编译服务端,并运行服务端: 35 | ```sh 36 | cd HareMQ/mqserver 37 | make clean;make; 38 | ./server 39 | ``` 40 | 运行后效果如下所示,则表示服务端运行成功。 41 | 42 | ![](./use-assets/1.png) 43 | 44 | 打印的是服务端的基本信息,包括服务端监听的ip和端口,服务器启动的时间,启动服务的系统用户,程序的PID。 45 | 46 | 当然,你可以选择使用 `tmux` 或者其他方式,把服务部署到后台当中。 47 | 48 | 编译客户端,并运行客户端: 49 | ```sh 50 | cd HareMQ/mqserver 51 | make clean;make; 52 | ./client 53 | ``` 54 | 55 | 这里可能会遇到bug,暂时还没有解决,具体可见 [issue#30](https://github.com/ffengc/HareMQ/issues/30),**临时的解决方法:如果启动遇到段错误,则重复运行 `./client` 至成功。** 56 | 57 | 如图所示,则客户端运行成功: 58 | 59 | ![](./use-assets/2.png) 60 | 61 | 启动客户端之后,即进入了客户端的控制命令行。 62 | 63 | ## 命令手册 64 | 65 | ### 所有合法命令 66 | 67 | | 命令 | 简单描述 | 68 | | ------------------ | ------------------------------------------------------------------------------------------ | 69 | | `subscribe` | 订阅一个队列的消息,并持续在 `stdout` 上消费订阅队列上的消息(需要手动退出此模式) | 70 | | `publish` | 推送一条消息到指定交换机中 | 71 | | `publish_mode` | 连续推送消息模式 | 72 | | `svrstat` | 检查服务器当前交换机状态、队列状态和绑定状态,这是一个阻塞接口,需要阻塞等待服务器返回结果 | 73 | | `declare_exchange` | 声明一台交换机 | 74 | | `declare_queue` | 声明一个队列 | 75 | | `delete_exchange` | 删除一台交换机 | 76 | | `delete_queue` | 删除一个队列 | 77 | | `bind` | 绑定一题台交换机和队列 | 78 | | `unbind` | 解除绑定 | 79 | | `cid` | 查看当前客户端使用信道的id | 80 | | `cmds` | 查看所有合法命令 | 81 | | `exit` | 退出客户端 | 82 | | `clear` | 清理命令行 | 83 | | `logo` | 打项目印logo | 84 | 85 | ### `cmds` 86 | 87 | 功能: 查看所有合法命令。 88 | 89 | **Usage:** 90 | ```sh 91 | cmds 92 | ``` 93 | 94 | ![](./use-assets/3.png) 95 | 96 | ### `clear` 97 | 98 | 功能:清理命令行。 99 | 100 | **Usage:** 101 | ```sh 102 | clear 103 | ``` 104 | 105 | 运行后效果: 106 | ![](./use-assets/4.png) 107 | 108 | 109 | ### `logo` 110 | 111 | 功能:打印项目logo。 112 | 113 | **Usage:** 114 | ```sh 115 | logo 116 | ``` 117 | 118 | ![](./use-assets/5.png) 119 | 120 | ### `exit` 121 | 122 | 功能:退出客户端 123 | 124 | **Usage:** 125 | ```sh 126 | exit 127 | ``` 128 | 129 | ![](./use-assets/6.png) 130 | 131 | ### `cid` 132 | 133 | 功能:打印当前客户端信道id。 134 | 135 | **Usage:** 136 | ```sh 137 | cid 138 | ``` 139 | 140 | ![](./use-assets/7.png) 141 | 142 | ### `svrstat` 143 | 144 | 功能:检查服务器当前交换机状态、队列状态和绑定状态,这是一个阻塞接口,需要阻塞等待服务器返回结果。 145 | 146 | **Usage:** 147 | ```sh 148 | svrstat 149 | ``` 150 | 151 | ![](./use-assets/8.png) 152 | 153 | 可以观察到,目前服务器上有一台交换机,一个队列,和一个绑定信息。 154 | 155 | > [!NOTE] 156 | > 虽然服务器已经重启多次,但是服务器是能保留历史数据的,因此所有的信息都会从文件中加载出来,这个即为信息持久化,我在 README 中也有提到。 157 | 158 | 159 | ### `declare_exchange` 160 | 161 | 功能:声明一台交换机。 162 | 163 | **Usage:** 164 | ```sh 165 | declare_exchange exchange_name ExchangeType durable auto_delete other_args; 166 | @ExchangeType: TOPIC/FANOUT/DIRECT 167 | @durable, auto_delete: true/false 168 | @other_args format: k1=v1&k2=v2&k3=v3... 169 | ``` 170 | 171 | ### `delete_exchange` 172 | 173 | 功能:删除一台交换机。 174 | 175 | **Usage:** 176 | ```sh 177 | delete_exchange exchange_name 178 | ``` 179 | 180 | ### `declare_queue` 181 | 182 | 功能:声明一个队列。 183 | 184 | **Usage:** 185 | ```sh 186 | declare_queue queue_name durable exclusive auto_delete other_args 187 | @durable, exclusive, auto_delete: true/false 188 | @other_args format: k1=v1&k2=v2&k3=v3... 189 | ``` 190 | 191 | ### `delete_queue` 192 | 193 | 功能:删除一个队列。 194 | 195 | **Usage:** 196 | ```sh 197 | delete_queue queue_name 198 | ``` 199 | 200 | ### `publish/publish_mode` 201 | 202 | 功能:推送一条消息到指定交换机中/持续推送模式 203 | 204 | **Usage:** 205 | ```sh 206 | publish/publish_mode exchange_name DURABLE binding_key 207 | @DURABLE: hare_mq::DeliveryMode::DURABLE/UNDURABLE 208 | ``` 209 | 210 | ### `subscribe` 211 | 212 | 213 | 功能:订阅一个队列的消息,并持续在 `stdout` 上消费订阅队列上的消息,在屏幕上输入 `quit` + 回车,退出该模式,并自动取消订阅。 214 | 215 | **Usage:** 216 | ```sh 217 | publish/publish_mode exchange_name DURABLE binding_key 218 | @DURABLE: hare_mq::DeliveryMode::DURABLE/UNDURABLE 219 | ``` 220 | 221 | ## 使用例子 222 | 223 | 启动服务端,并启动两个客户端。 224 | 225 | ![](./use-assets/9.png) 226 | 227 | 定义一台交换机,定义一个队列,并进行绑定。如果命令格式有误,则会显示usage使用说明。 228 | 229 | ![](./use-assets/10.png) 230 | 231 | 一个终端利用 `publish` 和 `publish_mode` 向指定交换机推送消息。 232 | 一个终端利用 `subscribe` 订阅队列的消息。 233 | 234 | ![](./use-assets/11.png) 235 | 236 | 两个消费者终端都可以使用quit来退出当前的模式。 237 | 238 | ![](./use-assets/12.png) 239 | 240 | ## 使用配置文件启动客户端 241 | 242 | 为了启动客户端后可以自动化快速搭建交换机和队列以及绑定信息,因此可以提供配置文件启动方式。 243 | 244 | **Usage:** 245 | ```sh 246 | ./client file_name 247 | ``` 248 | 249 | 比如, 编写配置文件。 250 | 251 | ```conf 252 | declare_exchange exchange1 FANOUT true false 253 | declare_queue queue1 true false false k1=v1&k2=v2 254 | declare_queue queue2 true false false 255 | bind exchange1 queue1 news.pop.# 256 | bind exchange1 queue2 queue2 257 | ``` 258 | 259 | **配置文件格式:** 260 | 1. 每一行为一条命令 261 | 2. 每条命令格式和终端输入格式相同 262 | 3. 如果配置文件出现错误,则进程会被终止(但此时有可能已经对服务器配置了一部分命令,需要检查服务器状态。 263 | 4. 格式化服务器状态方法:删除 `mqserver` 目录下的 `data` 目录。 264 | 265 | 使用配置文件启动客户端: 266 | 267 | ![](./use-assets/13.png) 268 | 269 | 270 | ## 日志 271 | 272 | 每一次启动服务端或客户端,都会在可执行程序同级目录下生成该程序的日志,便于运维工作的开展。 273 | 274 | 比如服务端的日志: 275 | 276 | ```log 277 | [INFO][broker_server.hpp][98] 278 | ------------------- Server Start -------------------- 279 | IP Address and Port: 0.0.0.0:8085 280 | Start Time: Fri Aug 23 23:32:31 2024 281 | User: ffengc 282 | Process ID: 378907 283 | ------------------------------------------------------- 284 | [INFO][broker_server.hpp][324] connected 285 | [INFO][broker_server.hpp][316] 286 | New Connection: 287 | Connection Name: server-0.0.0.0:8085#1 288 | Local IP: 127.0.0.1:8085 289 | Peer IP: 127.0.0.1:52790 290 | [REQUEST][broker_server.hpp][116] Request: openChannelRequest 291 | [INFO][broker_server.hpp][324] connected 292 | [INFO][broker_server.hpp][316] 293 | New Connection: 294 | Connection Name: server-0.0.0.0:8085#2 295 | Local IP: 127.0.0.1:8085 296 | Peer IP: 127.0.0.1:52800 297 | [REQUEST][broker_server.hpp][116] Request: openChannelRequest 298 | [REQUEST][broker_server.hpp][303] Request: basicQueryRequest 299 | [REQUEST][broker_server.hpp][143] Request: declareExchangeRequest 300 | [REQUEST][broker_server.hpp][175] Request: declareQueueRequest 301 | [REQUEST][broker_server.hpp][207] Request: bindRequest 302 | ... 303 | ``` 304 | 305 | **日志文件名为: `year-month-day-hour-minute_pid.log`** 306 | 307 | -------------------------------------------------------------------------------- /docs/gtest-en.md: -------------------------------------------------------------------------------- 1 | # GTest 2 | 3 | - [简体中文](./gtest.md) 4 | - [English](./gtest-en.md) 5 | 6 | *** 7 | - [GTest](#gtest) 8 | - [What is GTest](#what-is-gtest) 9 | - [GTest functions we need to learn](#gtest-functions-we-need-to-learn) 10 | - [Macro Assertions](#macro-assertions) 11 | - [Event mechanism](#event-mechanism) 12 | - [Global test suite](#global-test-suite) 13 | - [Independent test suite](#independent-test-suite) 14 | 15 | ## What is GTest 16 | 17 | GTest is a cross-platform C++ unit testing framework released by Google. GTest is generated for writing C++ unit tests on different platforms. It provides rich assertions, fatal and non-fatal judgments, parameterization, etc. 18 | 19 | ## GTest functions we need to learn 20 | 21 | 1. Simple macro assertion mechanism 22 | 23 | 2. Event mechanism (global test, single case test) 24 | 25 | ## Macro Assertions 26 | 27 | Assertions in GTest can be divided into two categories: 28 | - `ASSERT_` series: if the current test fails, exit the current function 29 | - `EXPECT_` series: if the current test fails, continue to execute 30 | 31 | ```cpp 32 | // Boolean value check 33 | ASSERT_TRUE(res) // Expects that res is true 34 | ASSERT_FALSE(res) // Expects that res is false 35 | // Numeric data check 36 | ASSERT_EQ(arg1, arg2) // Returns true if arg1 == arg2 37 | ASSERT_NE(arg1, arg2) // Returns true if arg1 != arg2 38 | ASSERT_LT(arg1, arg2) // Returns true if arg1 < arg2 39 | ASSERT_GT(arg1, arg2) // Returns true if arg1 > arg2 40 | ASSERT_LE(arg1, arg2) // Returns true if arg1 <= arg2 41 | ASSERT_GE(arg1, arg2) // Returns true if arg1 >= arg2 42 | ``` 43 | 44 | ```cpp 45 | TEST(test1, MYTEST) { 46 | 47 | } 48 | ``` 49 | All tests must be placed under a unit test. The first parameter of the macro represents the "test suite name" and the second parameter represents the "test name". 50 | 51 | ```cpp 52 | testing::InitGoogleTest(&argc, argv); 53 | RUN_ALL_TESTS(); 54 | ``` 55 | This is how all tests are initialized and run. 56 | 57 | ## Event mechanism 58 | 59 | In a test, there can be multiple test suites (including a set of unit tests) 60 | 61 | Test suite: It can be understood as a test environment. The test environment can be initialized before the unit test and cleaned up after the test is completed 62 | 63 | - Global test suite: In the overall test, the environment will only be initialized once, and the environment will be cleaned up after all test cases are completed 64 | 65 | - Case test suite: In each unit test, the test environment will be reinitialized and cleaned up after completion 66 | 67 | ### Global test suite 68 | 69 | ```cpp 70 | #include "../log.hpp" 71 | #include 72 | #include 73 | 74 | class MyEnv : public testing::Environment { 75 | public: 76 | virtual void SetUp() override { 77 | LOG(INFO) << "before testing, init ..." << std::endl; 78 | } 79 | virtual void TearDown() override { 80 | LOG(INFO) << "after testing, clearing ..." << std::endl; 81 | } 82 | }; 83 | TEST(MyEnv, test1) { 84 | LOG(INFO) << "unit test1" << std::endl; 85 | } 86 | TEST(MyEnv, test2) { 87 | LOG(INFO) << "unit test2" << std::endl; 88 | } 89 | int main(int argc, char** argv) { 90 | testing::AddGlobalTestEnvironment(new MyEnv); 91 | testing::InitGoogleTest(&argc, argv); 92 | auto res = RUN_ALL_TESTS(); 93 | LOG(INFO) << "res: " << res << std::endl; 94 | return 0; 95 | } 96 | ``` 97 | 98 | Observe the location of each print message and observe when the global suite is initialized and cleaned up. 99 | 100 | ![](./assets/17.png) 101 | 102 | 103 | ### Independent test suite 104 | 105 | - Member variables can be defined in the test environment class. Member variables are independent and can only be accessed by unit tests related to the current test suite. 106 | - The unit test macro name has become `TEST_F()`, and the first parameter of this macro function (the total test name) must be consistent with the suite environment class name. (In the unit test macro function, class member variables can be directly accessed) 107 | 108 | ```cpp 109 | #include "../log.hpp" 110 | #include 111 | #include 112 | #include 113 | 114 | class MyTest : public testing::Test { 115 | public: 116 | static void SetUpTestCase() { 117 | LOG(INFO) << "before each suit test, init ..." << std::endl; 118 | } 119 | static void TearDownTestCase() { 120 | LOG(INFO) << "after each suit test, clearing ..." << std::endl; 121 | } 122 | virtual void SetUp() override { } 123 | virtual void TearDown() override { } 124 | public: 125 | std::unordered_map __map; 126 | }; 127 | 128 | TEST_F(MyTest, insert_test) { 129 | __map.insert({ "hello", "nihao" }); 130 | __map.insert({ "bye", "zaijian" }); 131 | } 132 | TEST_F(MyTest, size_test) { 133 | ASSERT_EQ(__map.size(), 2); 134 | } 135 | 136 | int main(int argc, char** argv) { 137 | testing::InitGoogleTest(&argc, argv); 138 | auto res = RUN_ALL_TESTS(); 139 | LOG(INFO) << "res: " << res << std::endl; 140 | return 0; 141 | } 142 | ``` 143 | 144 | If you run the program now, you will find that the test fails because `__map` is independent of each test unit. Therefore, the data added in `insert_test` will not appear in `size_test`. 145 | 146 | ![](./assets/18.png) 147 | 148 | 149 | If you want to have two data at the beginning, you can use: 150 | 151 | ```cpp 152 | virtual void SetUp() override { } 153 | virtual void TearDown() override { } 154 | ``` 155 | 156 | Perform all initialization, that is, public initialization. -------------------------------------------------------------------------------- /docs/gtest.md: -------------------------------------------------------------------------------- 1 | # GTest 2 | 3 | - [简体中文](./gtest.md) 4 | - [English](./gtest-en.md) 5 | 6 | *** 7 | - [GTest](#gtest) 8 | - [GTest是什么](#gtest是什么) 9 | - [我们需要学习的GTest功能](#我们需要学习的gtest功能) 10 | - [宏断言](#宏断言) 11 | - [事件机制](#事件机制) 12 | - [全局测试套件](#全局测试套件) 13 | - [独立测试套件](#独立测试套件) 14 | 15 | ## GTest是什么 16 | 17 | GTest是一个跨平台的 C++单元测试框架,由google公司发布。gtest是为了在不同平台上为编写C++单元测试而生成的。它提供了丰富的断言、致命和非致命判断、参数化等等。 18 | 19 | ## 我们需要学习的GTest功能 20 | 21 | 1. 简单的宏断言机制 22 | 2. 事件机制(全局测试,单独用例测试) 23 | 24 | ## 宏断言 25 | 26 | GTest中断言的宏可以分为两类: 27 | - `ASSERT_`系列:如果当前检测失败则推出当前函数 28 | - `EXPECT_`系列: 如果当前检测失败则继续向下执行 29 | 30 | ```cpp 31 | // bool值检查 32 | ASSERT_TRUE(res) // 期待res是true 33 | ASSERT_FALSE(res) // 期待res是false 34 | //数值型数据检查 35 | ASSERT_EQ(arg1, arg2) // arg1 == arg2 返回 true 36 | ASSERT_NE(arg1, arg2) // arg1 != arg2 返回 true 37 | ASSERT_LT(arg1, arg2) // arg1 < arg2 返回 true 38 | ASSERT_GT(arg1, arg2) // arg1 > arg2 返回 true 39 | ASSERT_LE(arg1, arg2) // arg1 <= arg2 返回 true 40 | ASSERT_GE(arg1, arg2) // arg1 >= arg2 返回 true 41 | ``` 42 | 43 | ```cpp 44 | TEST(test1, MYTEST) { 45 | 46 | } 47 | ``` 48 | 所有的测试都要放在一个单元测试下才行的。宏的第一个参数表示“测试套件名称”,第二个参数表示“测试名称”。 49 | 50 | ```cpp 51 | testing::InitGoogleTest(&argc, argv); 52 | RUN_ALL_TESTS(); 53 | ``` 54 | 通过这种方式来初始化和运行所有的测试。 55 | 56 | ## 事件机制 57 | 58 | 测试中,可以有多个测试套件(包含有一组单元测试) 59 | 60 | 测试套件: 可以理解成一个测试环境,可以在单元测试之前进行测试环境初始化,测试完毕后进行测试环境清理 61 | 62 | - 全局测试套件:在整体的测试中,只会初始化一次环境,在所有测试用例完毕后,才会清理环境 63 | - 用例测试套件:在每次的单元测试中,都会重新初始化测试环境,完毕后清理环境 64 | 65 | ### 全局测试套件 66 | 67 | ```cpp 68 | #include "../log.hpp" 69 | #include 70 | #include 71 | 72 | class MyEnv : public testing::Environment { 73 | public: 74 | virtual void SetUp() override { 75 | LOG(INFO) << "before testing, init ..." << std::endl; 76 | } 77 | virtual void TearDown() override { 78 | LOG(INFO) << "after testing, clearing ..." << std::endl; 79 | } 80 | }; 81 | TEST(MyEnv, test1) { 82 | LOG(INFO) << "unit test1" << std::endl; 83 | } 84 | TEST(MyEnv, test2) { 85 | LOG(INFO) << "unit test2" << std::endl; 86 | } 87 | int main(int argc, char** argv) { 88 | testing::AddGlobalTestEnvironment(new MyEnv); 89 | testing::InitGoogleTest(&argc, argv); 90 | auto res = RUN_ALL_TESTS(); 91 | LOG(INFO) << "res: " << res << std::endl; 92 | return 0; 93 | } 94 | ``` 95 | 96 | 观察每条打印信息的位置,观察全局套件是何时初始化和何时清理的。 97 | 98 | ![](./assets/17.png) 99 | 100 | 101 | ### 独立测试套件 102 | 103 | - 测试环境类中,可以定义成员变量,成员变量是独立的,是与当前测试套件相关的单元测试才能访问的。 104 | - 单元测试宏名称变成了`TEST_F()`,而且这个宏函数的第一个参数(总测试名称),一定要和套件环境类名一致。(在单元测试宏函数中,能够直接访问类成员变量) 105 | 106 | ```cpp 107 | #include "../log.hpp" 108 | #include 109 | #include 110 | #include 111 | 112 | class MyTest : public testing::Test { 113 | public: 114 | static void SetUpTestCase() { 115 | LOG(INFO) << "before each suit test, init ..." << std::endl; 116 | } 117 | static void TearDownTestCase() { 118 | LOG(INFO) << "after each suit test, clearing ..." << std::endl; 119 | } 120 | virtual void SetUp() override { } 121 | virtual void TearDown() override { } 122 | public: 123 | std::unordered_map __map; 124 | }; 125 | 126 | TEST_F(MyTest, insert_test) { 127 | __map.insert({ "hello", "nihao" }); 128 | __map.insert({ "bye", "zaijian" }); 129 | } 130 | TEST_F(MyTest, size_test) { 131 | ASSERT_EQ(__map.size(), 2); 132 | } 133 | 134 | int main(int argc, char** argv) { 135 | testing::InitGoogleTest(&argc, argv); 136 | auto res = RUN_ALL_TESTS(); 137 | LOG(INFO) << "res: " << res << std::endl; 138 | return 0; 139 | } 140 | ``` 141 | 142 | 此时运行可以发现,是通过不了测试的,因为 `__map` 是每个测试单元独立的。所以`insert_test`里添加的数据不会出现在`size_test`中。 143 | 144 | ![](./assets/18.png) 145 | 146 | 147 | 如果想让一开始就有两个数据,可以在: 148 | 149 | ```cpp 150 | virtual void SetUp() override { } 151 | virtual void TearDown() override { } 152 | ``` 153 | 154 | 进行全部的初始化,就是公共的初始化。 -------------------------------------------------------------------------------- /docs/proto-en.md: -------------------------------------------------------------------------------- 1 | # protobuf 2 | 3 | - [简体中文](./proto.md) 4 | - [English](./proto-en.md) 5 | 6 | *** 7 | - [protobuf](#protobuf) 8 | - [What is it](#what-is-it) 9 | - [Get started quickly](#get-started-quickly) 10 | 11 | ## What is it 12 | 13 | ProtoBuf (full name Protocol Buffer) is a data structure serialization and deserialization framework, which has the following features: 14 | - Language-independent, platform-independent: ProtoBuf supports multiple languages ​​such as Java, C++, Python, and supports multiple platforms 15 | - Efficient: It is smaller, faster, and simpler than XML 16 | - Good scalability and compatibility: You can update the data structure without affecting or destroying the original old program 17 | - Write a `.proto` file to define the structure object (message) and attribute content 18 | - Use the `protoc` compiler to compile the .proto file and generate a series of interface codes, which are stored in the newly generated header file and source file 19 | - Rely on the generated interface, include the compiled header file into our code, and implement the setting and getting of the fields defined in the `.proto` file, and serialization and deserialization of the `message` object. 20 | 21 | ## Get started quickly 22 | 23 | `contacts.proto` is defined as follows. 24 | ```proto 25 | syntax = "proto3"; // Declaration syntax version 26 | package contacts; // Declaring a namespace 27 | 28 | // Description of structured object 29 | message contact { 30 | // Description of each field 31 | /* Field type Field name = Field unique number */ 32 | uint64 sn = 1; 33 | string name = 2; 34 | float score = 3; 35 | }; 36 | ``` 37 | 38 | After defining the proto file, we need to let proto generate the corresponding `.h` and `.cc` for us. 39 | 40 | ```sh 41 | protoc --cpp_out=. contacts.proto 42 | ``` 43 | 44 | ![](./assets/2.png) 45 | 46 | This will generate successfully. 47 | 48 | 49 | ```cpp 50 | namespace contacts { 51 | PROTOBUF_CONSTEXPR contact::contact( 52 | ``` 53 | 54 | The namespace here is the name of the package we determined. 55 | 56 | So how to call and use it? 57 | 58 | Note that you need to link it when using it. 59 | 60 | makefile 61 | ```makefile 62 | test: main.cc contacts.pb.cc 63 | g++ -o $@ $^ -lprotobuf -std=c++11 64 | .PHONY:clean 65 | clean: 66 | rm -f test 67 | ``` 68 | 69 | main.cc 70 | ```cpp 71 | int main() { 72 | contacts::contact conn; 73 | conn.set_sn(10001); 74 | conn.set_name("Sam"); 75 | conn.set_score(60.5); 76 | 77 | std::string str = conn.SerializeAsString(); 78 | // conn.SerializeToString(&str); // same 79 | contacts::contact stu; 80 | bool ret = stu.ParseFromString(str); 81 | if (ret == false) // Deserialization failed 82 | assert(false); 83 | std::cout << stu.sn() << " " << stu.name() << " " << stu.score() << std::endl; 84 | return 0; 85 | } 86 | ``` 87 | 88 | This is a basic usage. -------------------------------------------------------------------------------- /docs/proto.md: -------------------------------------------------------------------------------- 1 | # protobuf 2 | 3 | - [简体中文](./proto.md) 4 | - [English](./proto-en.md) 5 | 6 | *** 7 | - [protobuf](#protobuf) 8 | - [是什么](#是什么) 9 | - [快速上手](#快速上手) 10 | 11 | ## 是什么 12 | 13 | ProtoBuf(全称Protocol Buffer)是数据结构序列化和反序列化框架,它具有以下特点: 14 | - 语言无关、平台无关:即 ProtoBuf支持 Java、C++、Python 等多种语言,支持多个平台 15 | - 高效:即比 XML 更小、更快、更为简单 16 | - 扩展性、兼容性好:你可以更新数据结构,而不影响和破坏原有的旧程序 17 | 18 | ![](./assets/1.png) 19 | 20 | - 编写`.proto`文件,目的是为了定义结构对象(message)及属性内容 21 | - 使用`protoc`编译器编译.proto 文件,生成一系列接口代码,存放在新生成头文件和源文件中 22 | - 依赖生成的接口,将编译生成的头文件包含进我们的代码中,实现对`.proto`文件中定义的字段进行设置和获取,和对 `message` 对象进行序列化和反序列化。 23 | 24 | ## 快速上手 25 | 26 | `contacts.proto`定义如下。 27 | ```proto 28 | syntax = "proto3"; // 声明语法版本 29 | package contacts; // 声明命名空间 30 | 31 | // 结构化对象的描述 32 | message contact { 33 | // 各个字段描述 34 | /* 字段类型 字段名 = 字段唯一编号 */ 35 | uint64 sn = 1; 36 | string name = 2; 37 | float score = 3; 38 | }; 39 | ``` 40 | 41 | 定义完成proto文件之后,就要让proto给我们生成对应的`.h`和`.cc`了。 42 | 43 | ```sh 44 | protoc --cpp_out=. contacts.proto 45 | ``` 46 | 47 | ![](./assets/2.png) 48 | 49 | 这样就生成成功了。 50 | 51 | ```cpp 52 | namespace contacts { 53 | PROTOBUF_CONSTEXPR contact::contact( 54 | ``` 55 | 这里的命名空间就是我们确定的package的名字。 56 | 57 | 那么如何调用和使用呢? 58 | 59 | 注意,使用的时候需要链接上去。 60 | 61 | makefile 62 | ```makefile 63 | test: main.cc contacts.pb.cc 64 | g++ -o $@ $^ -lprotobuf -std=c++11 65 | .PHONY:clean 66 | clean: 67 | rm -f test 68 | ``` 69 | 70 | main.cc 71 | ```cpp 72 | int main() { 73 | contacts::contact conn; 74 | conn.set_sn(10001); 75 | conn.set_name("Sam"); 76 | conn.set_score(60.5); 77 | 78 | std::string str = conn.SerializeAsString(); 79 | // conn.SerializeToString(&str); // 一样的 80 | contacts::contact stu; 81 | bool ret = stu.ParseFromString(str); 82 | if (ret == false) // 反序列化失败 83 | assert(false); 84 | std::cout << stu.sn() << " " << stu.name() << " " << stu.score() << std::endl; 85 | return 0; 86 | } 87 | ``` 88 | 这就是一个基本的使用。 -------------------------------------------------------------------------------- /docs/sqlite-en.md: -------------------------------------------------------------------------------- 1 | # SQLite3 2 | 3 | - [简体中文](./sqlite.md) 4 | - [English](./sqlite-en.md) 5 | 6 | *** 7 | - [SQLite3](#sqlite3) 8 | - [What is SQLite](#what-is-sqlite) 9 | - [Why do you need SQLite](#why-do-you-need-sqlite) 10 | - [Official Documentation](#official-documentation) 11 | - [Encapsulating Helper](#encapsulating-helper) 12 | - [Conduct some experiments](#conduct-some-experiments) 13 | 14 | ## What is SQLite 15 | 16 | SQLite is an in-process lightweight database that implements a self-sufficient, serverless, zero-configuration, transactional SQL database engine. It is a zero-configuration database, which means that unlike other databases, we do not need to configure it in the system. Like other databases, the SQLite engine is not an independent process and can be statically or dynamically connected as required by the application. SQLite directly accesses its storage files. 17 | 18 | ## Why do you need SQLite 19 | 20 | > [!NOTE] 21 | > - No need for a separate server process or operating system (serverless). 22 | > - SQLite does not require configuration. 23 | > - A complete SQLite database is stored in a single cross-platform disk file. 24 | > - SQLite is very small and lightweight, less than 400KiB when fully configured, less than 250KiB when optional features are omitted, SQLite is self-sufficient, which means that no external dependencies are required. 25 | > - SQLite transactions are fully ACID-compliant, allowing safe access from multiple processes or threads. 26 | > - SQLite supports most query language features of the SQL92 (SQL2) standard. 27 | > - SQLite is written in ANSI-C and provides a simple and easy-to-use API. 28 | > - SQLite runs on UNlX (Linux, MacOs-X, Android, iOS) and Windows (Win32, WinCE, WinRT). 29 | 30 | ## Official Documentation 31 | 32 | - **[https://www.sqlite.org/c3ref/funclist.html](https://www.sqlite.org/c3ref/funclist.html)** 33 | 34 | ## Encapsulating Helper 35 | 36 | Because we will not use all functions, we first encapsulate some commonly used methods into a `.hpp` file for easy subsequent use. 37 | 38 | ```cpp 39 | /** 40 | * Encapsulate common methods of sqlite 41 | */ 42 | 43 | #ifndef __YUFC_SQLITE_HELPER__ 44 | #define __YUFC_SQLITE_HELPER__ 45 | 46 | #include "../log.hpp" 47 | #include 48 | #include 49 | #include 50 | 51 | class sqlite_helper { 52 | public: 53 | typedef int (*sqlite_callback)(void*, int, char**, char**); 54 | 55 | private: 56 | sqlite3* __handler; 57 | std::string __db_file; 58 | 59 | public: 60 | sqlite_helper(const std::string& db_file) 61 | : __db_file(db_file) 62 | , __handler(nullptr) { } 63 | bool open(int safe_lavel = SQLITE_OPEN_FULLMUTEX) { 64 | // int sqlite3_open_v2(const char* filename, sqlite3 **ppDb, int flags, const char* zVfs); 65 | int ret = sqlite3_open_v2(__db_file.c_str(), &__handler, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | safe_lavel, nullptr); 66 | if (ret != SQLITE_OK) { 67 | LOG(ERROR) << "create database failed: " << sqlite3_errmsg(__handler) << std::endl; 68 | return false; 69 | } 70 | return true; 71 | } 72 | bool exec(const std::string& sql, sqlite_callback& cb, void* arg) { 73 | // int sqlite3_exec(sqlite3*, char* sql, int (*callback)(void*, int, char**, char**), void* arg, char**err); 74 | int ret = sqlite3_exec(__handler, sql.c_str(), cb, arg, nullptr); 75 | if (ret != SQLITE_OK) { 76 | LOG(ERROR) << "run exec: [" << sql << "] failed: " << sqlite3_errmsg(__handler) << std::endl; 77 | return false; 78 | } 79 | return true; 80 | } 81 | bool close() { 82 | if (__handler) { 83 | if (sqlite3_close_v2(__handler)) 84 | return true; 85 | LOG(ERROR) << "close error" << std::endl; 86 | return false; 87 | } 88 | LOG(ERROR) << "null sql handler" << std::endl; 89 | return false; 90 | } 91 | }; 92 | 93 | #endif 94 | ``` 95 | 96 | ## Conduct some experiments 97 | 98 | Let’s try inserting some data:: 99 | 100 | ```cpp 101 | int main() { 102 | // 1. Create/Open db File 103 | sqlite_helper helper("./test.db"); 104 | assert(helper.open()); 105 | // 2. Create a table (if it does not exist) 106 | const char* create_sql = "create table if not exists student(sn int primary key, name varchar(32), age int);"; 107 | assert(helper.exec(create_sql, nullptr, nullptr)); 108 | // 3. Add new data (add, delete, check and modify) 109 | const char* insert_sql = "insert into student values(1, 'Sam', 18), (2, 'Jack', 19), (3, 'Lucy', 18);"; 110 | assert(helper.exec(insert_sql, nullptr, nullptr)); 111 | // 4. Close the database 112 | helper.close(); 113 | return 0; 114 | } 115 | ``` 116 | 117 | After running, there will be a database file called `test.db` 118 | 119 | ```sh 120 | sqlite3 test.db 121 | ``` 122 | 123 | You can see our data: 124 | 125 | ![](./assets/15.png) 126 | 127 | Test query: 128 | 129 | ```cpp 130 | int select_cb(void* arg, int col_count, char** result, char** fields_name) { 131 | std::vector* arr = (std::vector*)arg; // Get the passed in array 132 | arr->push_back(result[0]); // Because the query result has only one field, just push one. 133 | return 0; // Here, 0 must be returned to indicate normal operation, otherwise abort may be triggered. 134 | } 135 | 136 | int main() { 137 | // 1. Create/Open db File 138 | sqlite_helper helper("./test.db"); 139 | assert(helper.open()); 140 | // 2. Create a table (if it does not exist) 141 | const char* create_sql = "create table if not exists student(sn int primary key, name varchar(32), age int);"; 142 | assert(helper.exec(create_sql, nullptr, nullptr)); 143 | // 3. Add new data (add, delete, check and modify) 144 | const char* insert_sql = "insert into student values(1, 'Sam', 18), (2, 'Jack', 19), (3, 'Lucy', 18);"; 145 | assert(helper.exec(insert_sql, nullptr, nullptr)); 146 | const char* select_sql = "select name from student;"; 147 | std::vector arr; 148 | assert(helper.exec(select_sql, select_cb, &arr)); 149 | for (const auto& name : arr) 150 | std::cout << name << " "; 151 | std::cout << std::endl; 152 | // 4. Close the database 153 | helper.close(); 154 | return 0; 155 | } 156 | ``` 157 | 158 | ![](./assets/16.png) -------------------------------------------------------------------------------- /docs/sqlite.md: -------------------------------------------------------------------------------- 1 | # SQLite3 2 | 3 | - [简体中文](./sqlite.md) 4 | - [English](./sqlite-en.md) 5 | 6 | *** 7 | - [SQLite3](#sqlite3) 8 | - [什么是SQLite](#什么是sqlite) 9 | - [为什么需要用SQLite](#为什么需要用sqlite) 10 | - [官方文档](#官方文档) 11 | - [封装Helper](#封装helper) 12 | - [进行一些实验](#进行一些实验) 13 | 14 | ## 什么是SQLite 15 | 16 | SQLite是一个进程内的轻量级数据库,它实现了自给自足的、无服务器的、零配置的、事务性的 SQL数据库引擎。它是一个零配置的数据库,这意味着与其他数据库不一样,我们不需要在系统中配置。像其他数据库,SQLite引擎不是一个独立的进程,可以按应用程序需求进行静态或动态连接,SQLite直接访问其存储文件。 17 | 18 | ## 为什么需要用SQLite 19 | 20 | > [!NOTE] 21 | > - 不需要一个单独的服务器进程或操作的系统(无服务器的)。 22 | > - SQLite不需要配置。 23 | > - 一个完整的SQLite数据库是存储在一个单一的跨平台的磁盘文件。 24 | > - SQLite是非常小的,是轻量级的,完全配置时小于400KiB,省略可选功能配置时小于250KiB, SQLite是自给自足的,这意味着不需要任何外部的依赖。 25 | > - SQLite 事务是完全兼容 ACID 的,允许从多个进程或线程安全访问。 26 | > - SQLite支持SQL92(SQL2)标准的大多数查询语言的功能。 27 | > - SQLite使用 ANSI-C 编写的,并提供了简单和易于使用的 API。 28 | > - SQLite可在UNlX(Linux, MacOs-X, Android, iOS)和 Windows(Win32, WinCE, WinRT)中运行。 29 | 30 | ## 官方文档 31 | 32 | - **[https://www.sqlite.org/c3ref/funclist.html](https://www.sqlite.org/c3ref/funclist.html)** 33 | 34 | ## 封装Helper 35 | 36 | 因为不是所有的功能我们都会用到,因此我们先封装一些常用的方法到一个`.hpp`文件里面,方便后续使用即可。 37 | 38 | ```cpp 39 | /** 40 | * 封装sqlite常用方法 41 | */ 42 | 43 | #ifndef __YUFC_SQLITE_HELPER__ 44 | #define __YUFC_SQLITE_HELPER__ 45 | 46 | #include "../log.hpp" 47 | #include 48 | #include 49 | #include 50 | 51 | class sqlite_helper { 52 | public: 53 | typedef int (*sqlite_callback)(void*, int, char**, char**); 54 | 55 | private: 56 | sqlite3* __handler; 57 | std::string __db_file; 58 | 59 | public: 60 | sqlite_helper(const std::string& db_file) 61 | : __db_file(db_file) 62 | , __handler(nullptr) { } 63 | bool open(int safe_lavel = SQLITE_OPEN_FULLMUTEX) { 64 | // 打开数据库(文件) 65 | // int sqlite3_open_v2(const char* filename, sqlite3 **ppDb, int flags, const char* zVfs); 66 | int ret = sqlite3_open_v2(__db_file.c_str(), &__handler, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | safe_lavel, nullptr); 67 | if (ret != SQLITE_OK) { 68 | LOG(ERROR) << "create database failed: " << sqlite3_errmsg(__handler) << std::endl; 69 | return false; 70 | } 71 | return true; 72 | } 73 | bool exec(const std::string& sql, sqlite_callback& cb, void* arg) { 74 | // 执行语句 75 | // int sqlite3_exec(sqlite3*, char* sql, int (*callback)(void*, int, char**, char**), void* arg, char**err); 76 | int ret = sqlite3_exec(__handler, sql.c_str(), cb, arg, nullptr); 77 | if (ret != SQLITE_OK) { 78 | LOG(ERROR) << "run exec: [" << sql << "] failed: " << sqlite3_errmsg(__handler) << std::endl; 79 | return false; 80 | } 81 | return true; 82 | } 83 | bool close() { 84 | // 关闭数据库(文件) 85 | if (__handler) { 86 | if (sqlite3_close_v2(__handler)) 87 | return true; 88 | LOG(ERROR) << "close error" << std::endl; 89 | return false; 90 | } 91 | LOG(ERROR) << "null sql handler" << std::endl; 92 | return false; 93 | } 94 | }; 95 | 96 | #endif 97 | ``` 98 | 99 | ## 进行一些实验 100 | 101 | 插入一些数据试试: 102 | 103 | ```cpp 104 | int main() { 105 | // 1. 创建/打开库文件 106 | sqlite_helper helper("./test.db"); 107 | assert(helper.open()); 108 | // 2. 创建表(不存在则创建) 109 | const char* create_sql = "create table if not exists student(sn int primary key, name varchar(32), age int);"; 110 | assert(helper.exec(create_sql, nullptr, nullptr)); 111 | // 3. 新增数据(增删查改) 112 | const char* insert_sql = "insert into student values(1, 'Sam', 18), (2, 'Jack', 19), (3, 'Lucy', 18);"; 113 | assert(helper.exec(insert_sql, nullptr, nullptr)); 114 | // 4. 关闭数据库 115 | helper.close(); 116 | return 0; 117 | } 118 | ``` 119 | 120 | 运行之后就会有一个 `test.db` 的数据库文件 121 | 122 | ```sh 123 | sqlite3 test.db # 打开数据库 124 | ``` 125 | 126 | 就可以看到我们的数据了: 127 | 128 | ![](./assets/15.png) 129 | 130 | 测试查询: 131 | 132 | ```cpp 133 | int select_cb(void* arg, int col_count, char** result, char** fields_name) { 134 | std::vector* arr = (std::vector*)arg; // 拿到传进来的数组 135 | arr->push_back(result[0]); // 因为查询结果只有一个字段,所以push一个就行了 136 | return 0; // 这里一定要返回0表示正常,否则可能会触发abort 137 | } 138 | 139 | int main() { 140 | // 1. 创建/打开库文件 141 | sqlite_helper helper("./test.db"); 142 | assert(helper.open()); 143 | // 2. 创建表(不存在则创建) 144 | const char* create_sql = "create table if not exists student(sn int primary key, name varchar(32), age int);"; 145 | assert(helper.exec(create_sql, nullptr, nullptr)); 146 | // 3. 新增数据(增删查改) 147 | const char* insert_sql = "insert into student values(1, 'Sam', 18), (2, 'Jack', 19), (3, 'Lucy', 18);"; 148 | assert(helper.exec(insert_sql, nullptr, nullptr)); 149 | const char* select_sql = "select name from student;"; 150 | std::vector arr; 151 | assert(helper.exec(select_sql, select_cb, &arr)); 152 | for (const auto& name : arr) 153 | std::cout << name << " "; 154 | std::cout << std::endl; 155 | // 4. 关闭数据库 156 | helper.close(); 157 | return 0; 158 | } 159 | ``` 160 | 161 | ![](./assets/16.png) -------------------------------------------------------------------------------- /docs/thread_pool-en.md: -------------------------------------------------------------------------------- 1 | # Implementing a thread pool using C++11 asynchronous operations 2 | 3 | - [简体中文](./thread_pool.md) 4 | - [English](./thread_pool-en.md) 5 | 6 | *** 7 | - [Implementing a thread pool using C++11 asynchronous operations](#implementing-a-thread-pool-using-c11-asynchronous-operations) 8 | - [Introduction](#introduction) 9 | - [Explanation of some code details](#explanation-of-some-code-details) 10 | - [test](#test) 11 | 12 | 13 | ## Introduction 14 | 15 | When executing tasks based on the thread pool, the internal execution logic of the entry function is fixed, so the combination of `std::packaged_task` and `std::future` is selected to implement it. 16 | 17 | Working idea of ​​thread pool: 18 | - The user passes in the function to be executed and the data to be processed (function parameters), and the working thread in the thread pool executes the function to complete the task; 19 | 20 | **Code: [HareMQ/demo/thread_pool](../HareMQ/demo/thread_pool/thread_pool.hpp)** 21 | 22 | 23 | ## Explanation of some code details 24 | 25 | **Explanation of `push` function parameters** 26 | 27 | ```cpp 28 | template 29 | auto push(func&& f, Args&&... args) -> std::future; 30 | ``` 31 | The first thing pushed in is a function (the function that the user wants to execute), followed by an indefinite parameter, which represents the data to be processed, that is, the parameter to be inserted. Then, inside push, this function is packaged into an asynchronous operation (packaged_task) and thrown to the thread for execution! 32 | 33 | **Explanation of return value** 34 | 35 | Because we don't know what type of value the user's thread returns to us (because we don't know what the function the user wants to execute will look like), we can only use `auto` to indicate the type, but using `auto` directly will definitely not work, because the system doesn't know how much space to open up to push onto the stack, so C++ provides a type deduction: `decltype(func(args...))` indicates that the return type is the return type of `func(args...)`. 36 | 37 | **Some explanation inside the `push` function** 38 | 39 | ```cpp 40 | auto push(func&& f, Args&&... args) -> std::future { 41 | // 1. Package the passed function into a packaged_task task package 42 | using return_type = decltype(f(args...)); // return type 43 | auto bind_func = std::bind(std::forward(f), std::forward(args)...); // Function + parameter type 44 | auto task = std::make_shared>(bind_func); 45 | std::future fu = task->get_future(); 46 | // 2. Construct a lambda anonymous function (capture the task object and execute the task object within the function) 47 | { 48 | std::unique_lock lock(__mtx_lock); 49 | // 3. Throw the constructed anonymous function object into the task queue 50 | __task_queue.push_back([task]() { 51 | (*task)(); 52 | }); 53 | // 4. Awakening consumers 54 | __cond.notify_one(); 55 | } 56 | return fu; 57 | } 58 | ``` 59 | 60 | First, because the functions passed in by each user may be different, the parameters and return values ​​are different, so we must unify them in `push`. 61 | 62 | First, the return value type, we need to deduce: 63 | 64 | ```cpp 65 | using return_type = decltype(f(args...)); // return type 66 | ``` 67 | 68 | Then we need to bind the function and function parameters together, so we need to use `bind`. Because it is a variable parameter, we need to expand it with `...`. In order to maintain the properties of the parameters, we need to use a perfect forwarding `std::forward`. 69 | 70 | ```cpp 71 | auto bind_func = std::bind(std::forward(f), std::forward(args)...); // Function + parameter type 72 | ``` 73 | 74 | Then the rest is the same as the demo in [asynchronous.md](./asynchronous.md) , using a pointer to encapsulate `std::packaged_task`. 75 | 76 | 77 | ```cpp 78 | auto task = std::make_shared>(bind_func); 79 | std::future fu = task->get_future(); 80 | ``` 81 | 82 | ## test 83 | 84 | ```cpp 85 | #include "thread_pool.hpp" 86 | 87 | int add(int a, int b) { return a + b; } 88 | 89 | int main() { 90 | thread_pool pool; 91 | for (int i = 0; i < 11; i++) { 92 | std::future fu = pool.push(add, i, 22); 93 | LOG(INFO) << "add res: " << fu.get() << std::endl; 94 | } 95 | pool.stop(); 96 | return 0; 97 | } 98 | ``` 99 | 100 | ![](./assets/23.png) -------------------------------------------------------------------------------- /docs/thread_pool.md: -------------------------------------------------------------------------------- 1 | # 利用C++11的异步操作实现一个线程池 2 | 3 | - [简体中文](./thread_pool.md) 4 | - [English](./thread_pool-en.md) 5 | 6 | *** 7 | - [利用C++11的异步操作实现一个线程池](#利用c11的异步操作实现一个线程池) 8 | - [介绍](#介绍) 9 | - [关于一些代码细节的解释](#关于一些代码细节的解释) 10 | - [测试](#测试) 11 | 12 | 13 | 14 | ## 介绍 15 | 16 | 基于线程池执行任务的时候,入口函数内部执行逻辑是固定的,因此选择`std::packaged_task`加上`std::future`的组合来实现。 17 | 18 | 线程池的工作思想: 19 | - 用户传入要执行的函数,以及需要处理的数据(函数的参数),由线程池中的工作线程来执行函数完成任务; 20 | 21 | **代码: [HareMQ/demo/thread_pool](../HareMQ/demo/thread_pool/thread_pool.hpp)** 22 | 23 | 24 | ## 关于一些代码细节的解释 25 | 26 | **`push`函数参数的解释** 27 | 28 | ```cpp 29 | template 30 | auto push(func&& f, Args&&... args) -> std::future; 31 | ``` 32 | push 进来的首先是一个函数(用户要执行的函数),接下来是不定参数,表示要处理的数据,也就是要穿入的参数,然后在push内部,把这个函数封装成一个异步操作(packaged_task),丢给线程执行! 33 | 34 | **返回值的解释** 35 | 36 | 因为我们不知道用户的线程给我们返回的是什么类型的值(因为我们是不知道用户要执行的函数要长什么样的),所以只能用`auto`表示类型,但是直接用`auto`肯定是不行的,因为系统不知道要开辟多少空间压栈,所以C++中提供的一个类型推导: `decltype(func(args...))` 表示返回类型就是 `func(args...)` 的返回类型。 37 | 38 | **`push`函数内部的一些解释** 39 | 40 | ```cpp 41 | auto push(func&& f, Args&&... args) -> std::future { 42 | // 1. 对传入的函数封装成 packaged_task 任务包 43 | using return_type = decltype(f(args...)); // 返回值类型 44 | auto bind_func = std::bind(std::forward(f), std::forward(args)...); // 函数+参数类型 45 | auto task = std::make_shared>(bind_func); 46 | std::future fu = task->get_future(); 47 | // 2. 构造 lambda 匿名函数(捕获任务对象,函数内执行任务对象) 48 | { 49 | std::unique_lock lock(__mtx_lock); 50 | // 3. 将构造出来的匿名函数对象,抛入到任务队列中 51 | __task_queue.push_back([task]() { 52 | (*task)(); 53 | }); 54 | // 4. 唤醒消费者 55 | __cond.notify_one(); 56 | } 57 | return fu; 58 | } 59 | ``` 60 | 61 | 首先,因为每个用户传递进来的函数可能是不一样的,参数返回值都不一样,在`push`里一定要做一个统一。 62 | 63 | 首先是返回值类型,我们要做推导: 64 | 65 | ```cpp 66 | using return_type = decltype(f(args...)); // 返回值类型 67 | ``` 68 | 69 | 然后我们要把函数和函数参数绑定在一起,所以要用`bind`,因为是一个可变参数,所以要`...`展开,因为为了保持参数的性质,需要用一个完美转发`std::forward`。 70 | 71 | ```cpp 72 | auto bind_func = std::bind(std::forward(f), std::forward(args)...); // 函数+参数类型 73 | ``` 74 | 75 | 然后后面就和 [asynchronous.md](./asynchronous.md) 里面的 demo 一样了,要用一个指针,来封装 `std::packaged_task`。 76 | 77 | ```cpp 78 | auto task = std::make_shared>(bind_func); 79 | std::future fu = task->get_future(); 80 | ``` 81 | 82 | ## 测试 83 | 84 | ```cpp 85 | #include "thread_pool.hpp" 86 | 87 | int add(int a, int b) { return a + b; } 88 | 89 | int main() { 90 | thread_pool pool; 91 | for (int i = 0; i < 11; i++) { 92 | std::future fu = pool.push(add, i, 22); 93 | LOG(INFO) << "add res: " << fu.get() << std::endl; 94 | } 95 | pool.stop(); 96 | return 0; 97 | } 98 | ``` 99 | 100 | ![](./assets/23.png) -------------------------------------------------------------------------------- /docs/use-assets/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/use-assets/1.png -------------------------------------------------------------------------------- /docs/use-assets/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/use-assets/10.png -------------------------------------------------------------------------------- /docs/use-assets/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/use-assets/11.png -------------------------------------------------------------------------------- /docs/use-assets/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/use-assets/12.png -------------------------------------------------------------------------------- /docs/use-assets/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/use-assets/13.png -------------------------------------------------------------------------------- /docs/use-assets/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/use-assets/2.png -------------------------------------------------------------------------------- /docs/use-assets/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/use-assets/3.png -------------------------------------------------------------------------------- /docs/use-assets/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/use-assets/4.png -------------------------------------------------------------------------------- /docs/use-assets/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/use-assets/5.png -------------------------------------------------------------------------------- /docs/use-assets/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/use-assets/6.png -------------------------------------------------------------------------------- /docs/use-assets/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/use-assets/7.png -------------------------------------------------------------------------------- /docs/use-assets/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/use-assets/8.png -------------------------------------------------------------------------------- /docs/use-assets/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/use-assets/9.png -------------------------------------------------------------------------------- /docs/work-assets/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/work-assets/1.png -------------------------------------------------------------------------------- /docs/work-assets/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/work-assets/2.png -------------------------------------------------------------------------------- /docs/work-assets/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/work-assets/3.png -------------------------------------------------------------------------------- /docs/work-assets/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/work-assets/4.png -------------------------------------------------------------------------------- /docs/work-assets/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/work-assets/5.png -------------------------------------------------------------------------------- /docs/work-assets/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/work-assets/6.png -------------------------------------------------------------------------------- /docs/work-assets/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/work-assets/7.png -------------------------------------------------------------------------------- /docs/work-assets/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/work-assets/8.png -------------------------------------------------------------------------------- /docs/work-assets/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffengc/HareMQ/9486d167170766a3e2834ffb63d6474e9e064607/docs/work-assets/9.png -------------------------------------------------------------------------------- /env/test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Write by Yufc 3 | * See https://github.com/ffengc/HareMQ 4 | * please cite my project link: https://github.com/ffengc/HareMQ when you use this code 5 | * g++ test.cc -o gtest -lgtest 6 | */ 7 | #include 8 | int add(int a, int b) { 9 | return a + b; 10 | } 11 | TEST(testCase, test1) { 12 | EXPECT_EQ(add(2, 3), 5); 13 | } 14 | int main(int argc, char** argv) { 15 | testing::InitGoogleTest(&argc, argv); 16 | return RUN_ALL_TESTS(); 17 | } --------------------------------------------------------------------------------