├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── acid ├── acid.h ├── common │ ├── byte_array.cpp │ ├── byte_array.h │ ├── config.cpp │ ├── config.h │ ├── lexical_cast.cpp │ ├── lexical_cast.h │ ├── reflection.h │ ├── stream.cpp │ ├── stream.h │ ├── time_measure.h │ ├── traits.h │ ├── util.cpp │ └── util.h ├── http │ ├── http.cpp │ ├── http.h │ ├── http_connection.cpp │ ├── http_connection.h │ ├── http_server.cpp │ ├── http_server.h │ ├── http_session.cpp │ ├── http_session.h │ ├── parse.cpp │ ├── parse.h │ ├── servlet.cpp │ ├── servlet.h │ └── servlets │ │ ├── file_servlet.cpp │ │ ├── file_servlet.h │ │ ├── kvstore_servlet.cpp │ │ └── kvstore_servlet.h ├── kvraft │ ├── commom.h │ ├── kvclient.cpp │ ├── kvclient.h │ ├── kvserver.cpp │ └── kvserver.h ├── net │ ├── address.cpp │ ├── address.h │ ├── socket.cpp │ ├── socket.h │ ├── socket_stream.cpp │ ├── socket_stream.h │ ├── tcp_server.cpp │ ├── tcp_server.h │ ├── uri.cpp │ └── uri.h ├── raft │ ├── entry.h │ ├── persister.cpp │ ├── persister.h │ ├── raft_log.cpp │ ├── raft_log.h │ ├── raft_node.cpp │ ├── raft_node.h │ ├── raft_peer.cpp │ ├── raft_peer.h │ ├── snapshot.cpp │ └── snapshot.h └── rpc │ ├── protocol.h │ ├── pubsub.h │ ├── route_strategy.h │ ├── rpc.h │ ├── rpc_client.cpp │ ├── rpc_client.h │ ├── rpc_server.cpp │ ├── rpc_server.h │ ├── rpc_session.cpp │ ├── rpc_session.h │ └── serializer.h ├── build.sh ├── docs ├── go style协程设计.md ├── images │ ├── 多路分解.png │ └── 多路复用.png ├── rpc连接复用设计.md ├── 分布式kv存储.md ├── 反射实现无侵入式序列化.md ├── 无栈协程状态机解析HTTP.md ├── 旧版README.md ├── 服务订阅与通知.md └── 通用协程同步原语设计.md ├── example ├── CMakeLists.txt └── kvhttp │ ├── CMakeLists.txt │ ├── KVHttpServer接口文档.md │ ├── index.html │ └── kvhttp_server.cpp ├── tests ├── CMakeLists.txt ├── http │ ├── CMakeLists.txt │ ├── test_http.cpp │ ├── test_http_connection.cpp │ ├── test_http_parse.cpp │ └── test_http_server.cpp ├── kvraft │ ├── CMakeLists.txt │ ├── test_kvclient.cpp │ └── test_kvserver.cpp ├── raft │ ├── CMakeLists.txt │ ├── test_persister.cpp │ ├── test_raft_node_1.cpp │ ├── test_raft_node_2.cpp │ └── test_raft_node_3.cpp ├── rpc │ ├── CMakeLists.txt │ ├── test_route_strategy.cpp │ ├── test_rpc_client.cpp │ ├── test_rpc_server.cpp │ └── test_serializer.cpp └── test │ ├── CMakeLists.txt │ ├── test_address.cpp │ ├── test_bytearray.cpp │ ├── test_config.cpp │ ├── test_socket.cpp │ ├── test_tcp_server.cpp │ ├── test_timer.cpp │ └── test_uri.cpp └── third_party └── json └── single_include └── nlohmann ├── json.hpp └── json_fwd.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | cmake-build-debug/ 2 | cmake-build-release/ 3 | build/ 4 | third_party/ 5 | .idea/ 6 | 7 | # Prerequisites 8 | *.d 9 | 10 | # Compiled Object files 11 | *.slo 12 | *.lo 13 | *.o 14 | *.obj 15 | 16 | # Precompiled Headers 17 | *.gch 18 | *.pch 19 | 20 | # Compiled Dynamic libraries 21 | *.so 22 | *.dylib 23 | *.dll 24 | 25 | # Fortran module files 26 | *.mod 27 | *.smod 28 | 29 | # Compiled Static libraries 30 | *.lai 31 | *.la 32 | *.a 33 | *.lib 34 | 35 | # Executables 36 | *.exe 37 | *.out 38 | *.app -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "third_party/spdlog"] 2 | path = third_party/spdlog 3 | url = https://github.com/gabime/spdlog.git 4 | [submodule "third_party/yaml-cpp"] 5 | path = third_party/yaml-cpp 6 | url = https://github.com/jbeder/yaml-cpp.git 7 | [submodule "third_party/libgo"] 8 | path = third_party/libgo 9 | url = https://github.com/yyzybb537/libgo.git 10 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | project(acid) 3 | #add_subdirectory(example) 4 | #add_subdirectory(tests) 5 | if (CMAKE_BUILD_TYPE) 6 | else() 7 | #set(CMAKE_BUILD_TYPE RELEASE) 8 | set(CMAKE_BUILD_TYPE DEBUG) 9 | endif() 10 | message("------------ Options -------------") 11 | message(" CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}") 12 | message(" CMAKE_COMMAND: ${CMAKE_COMMAND}") 13 | 14 | option(ENABLE_DEBUGGER "enable debugger" ON) 15 | if (ENABLE_DEBUGGER) 16 | set(ENABLE_DEBUGGER 1) 17 | add_definitions(-DACID_ENABLE_DEBUGGER=1) 18 | message (" enable_debugger: yes") 19 | else() 20 | set(ENABLE_DEBUGGER 0) 21 | add_definitions(-DACID_ENABLE_DEBUGGER=0) 22 | message (" enable_debugger: no") 23 | endif() 24 | 25 | option(BUILD_DYNAMIC "build dynamic" OFF) 26 | if (BUILD_DYNAMIC) 27 | message (" build dynamic lib: yes") 28 | else() 29 | message (" build dynamic lib: no") 30 | endif() 31 | 32 | message("-------------- Env ---------------") 33 | message(" CMAKE_SOURCE_DIR: ${CMAKE_SOURCE_DIR}") 34 | message(" CMAKE_BINARY_DIR: ${CMAKE_BINARY_DIR}") 35 | message("----------------------------------") 36 | 37 | set(CMAKE_CXX_COMPILER "/usr/bin/g++-11") 38 | #set(CMAKE_VERBOSE_MAKEFILE ON) 39 | set(CMAKE_CXX_FLAGS "-std=c++20 -D__const__= -fPIC -fno-strict-aliasing -Wall ${CMAKE_CXX_FLAGS}") 40 | set(CMAKE_CXX_FLAGS_DEBUG "-g") 41 | set(CMAKE_CXX_FLAGS_RELEASE "-g -O3 -DNDEBUG") 42 | 43 | message("------------ Cxx flags -------------") 44 | message(" CMAKE_CXX_FLAGS_${CMAKE_BUILD_TYPE}: ${CMAKE_CXX_FLAGS_${CMAKE_BUILD_TYPE}}") 45 | message("------------------------------------") 46 | 47 | ################################################################################### 48 | # third party 49 | # json 50 | include_directories("${PROJECT_SOURCE_DIR}/third_party/json/single_include") 51 | # libgo 52 | include_directories("${PROJECT_SOURCE_DIR}/third_party/libgo") 53 | include_directories("${PROJECT_SOURCE_DIR}/third_party/libgo/libgo") 54 | include_directories("${PROJECT_SOURCE_DIR}/third_party/libgo/libgo/linux") 55 | link_directories(${PROJECT_SOURCE_DIR}/third_party/libgo/build) 56 | # spdlog 57 | include_directories("${PROJECT_SOURCE_DIR}/third_party/spdlog/include") 58 | link_directories(${PROJECT_SOURCE_DIR}/third_party/spdlog/build) 59 | # yaml-cpp 60 | include_directories("${PROJECT_SOURCE_DIR}/third_party/yaml-cpp/include") 61 | link_directories(${PROJECT_SOURCE_DIR}/third_party/yaml-cpp/build) 62 | ################################################################################### 63 | 64 | include_directories("${PROJECT_SOURCE_DIR}") 65 | #记得将目录加进来 66 | aux_source_directory(${PROJECT_SOURCE_DIR}/acid/common SRC_LIST) 67 | aux_source_directory(${PROJECT_SOURCE_DIR}/acid/http SRC_LIST) 68 | aux_source_directory(${PROJECT_SOURCE_DIR}/acid/http/servlets SRC_LIST) 69 | aux_source_directory(${PROJECT_SOURCE_DIR}/acid/net SRC_LIST) 70 | aux_source_directory(${PROJECT_SOURCE_DIR}/acid/raft SRC_LIST) 71 | aux_source_directory(${PROJECT_SOURCE_DIR}/acid/kvraft SRC_LIST) 72 | aux_source_directory(${PROJECT_SOURCE_DIR}/acid/rpc SRC_LIST) 73 | 74 | # 编译的库在 acid/lib 下生成 75 | set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib) 76 | message("------------ LIBRARY_OUTPUT_PATH -------------") 77 | message(${PROJECT_SOURCE_DIR}/lib) 78 | 79 | set(LINK_ARGS acid libgo pthread dl yaml-cpp) 80 | set(TARGET "acid") 81 | set(STATIC_T "acid_static") 82 | set(SHARED_T "acid_dynamic") 83 | 84 | # 默认编译静态库 85 | add_library("${STATIC_T}" STATIC ${SRC_LIST}) 86 | set_target_properties("${STATIC_T}" PROPERTIES OUTPUT_NAME "${TARGET}") 87 | target_link_libraries("${STATIC_T}" -Wl,--start-group ${LINK_ARGS} -Wl,--end-group) 88 | 89 | # 如果想编为译动态库记得把 libgo 也编译成动态库 90 | if (BUILD_DYNAMIC) 91 | add_library("${SHARED_T}" SHARED ${SRC_LIST}) 92 | set_target_properties("${SHARED_T}" PROPERTIES OUTPUT_NAME "${TARGET}") 93 | target_link_libraries("${SHARED_T}" -Wl,--start-group ${LINK_ARGS} -Wl,--end-group) 94 | install(TARGETS ${SHARED_T} LIBRARY DESTINATION "lib" ARCHIVE DESTINATION "lib") 95 | endif() -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ACID: 分布式服务治理框架 2 | 3 | > 学习本项目需要有一定的C++,RPC,分布式知识 4 | 5 | ### 注意 6 | 7 | 项目经过了重构,如果你是阅读文章而来的读者,可以前往 8 | [旧版本](https://github.com/zavier-wong/acid/tree/0f45acff75979d4636d217b78cd61fc2b1c01751) 9 | 10 | 11 | ### 构建项目 / Build 12 | 1. 项目用到了大量C++17/20新特性,如`constexpr if`的编译期代码生成,基于c++20 `coroutine`的无栈协程状态机解析 URI 和 HTTP 协议等。注意,必须安装g++-11,否则不支持编译。 13 | 14 | ubuntu可以采用以下方法升级g++,对于其他环境,可以下载源代码自行编译。 15 | ```shell 16 | sudo apt install software-properties-common 17 | sudo add-apt-repository ppa:ubuntu-toolchain-r/test 18 | sudo apt install gcc-11 g++-11 19 | ``` 20 | 2. 构建依赖库 21 | ```shell 22 | git clone --recursive https://github.com/zavier-wong/acid 23 | cd acid 24 | sudo bash build.sh 25 | ``` 26 | 3.构建项目 / Build 27 | ```shell 28 | mkdir build 29 | cd build 30 | cmake .. 31 | make 32 | cd .. 33 | ``` 34 | 现在你可以在 acid/lib 里面看见编译后的静态库 libacid.a 35 | 36 | example 和 tests 的 cmake 已经写好,只需要进入对应的目录 37 | ```shell 38 | mkdir build 39 | cd build 40 | cmake .. 41 | make 42 | ``` 43 | 44 | ## 反馈和参与 45 | 46 | * bug、疑惑、修改建议都欢迎提在[Github Issues](https://github.com/zavier-wong/acid/issues)中 47 | * 也可发送邮件 [acid@163.com](mailto:acid@163.com) 交流学习 48 | -------------------------------------------------------------------------------- /acid/acid.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2021/11/17. 3 | // 4 | 5 | #ifndef ACID_ACID_H 6 | #define ACID_ACID_H 7 | 8 | #include "acid/common/byte_array.h" 9 | #include "acid/common/config.h" 10 | #include "acid/common/stream.h" 11 | #include "acid/common/time_measure.h" 12 | #include "acid/common/util.h" 13 | 14 | #include "http/servlets/file_servlet.h" 15 | #include "http/http.h" 16 | #include "http/http_connection.h" 17 | #include "http/http_server.h" 18 | #include "http/http_session.h" 19 | #include "http/parse.h" 20 | #include "http/servlet.h" 21 | 22 | #include "net/address.h" 23 | #include "net/socket.h" 24 | #include "net/socket_stream.h" 25 | #include "net/tcp_server.h" 26 | #include "net/uri.h" 27 | 28 | #include "rpc/protocol.h" 29 | #include "rpc/route_strategy.h" 30 | #include "rpc/rpc.h" 31 | #include "rpc/rpc_client.h" 32 | #include "rpc/rpc_connection_pool.h" 33 | #include "rpc/rpc_server.h" 34 | #include "rpc/rpc_service_registry.h" 35 | #include "rpc/rpc_session.h" 36 | #include "rpc/serializer.h" 37 | 38 | #endif //ACID_ACID_H 39 | -------------------------------------------------------------------------------- /acid/common/config.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2021/10/26. 3 | // 4 | 5 | #include 6 | #include 7 | #include "config.h" 8 | 9 | namespace acid { 10 | static auto g_logger = GetLogInstance(); 11 | 12 | static void ListAllMenbers(const std::string& perfix,const YAML::Node& node, 13 | std::list>& output){ 14 | if(perfix.find_first_not_of("abcdefghijklmnopqrstuvwxyz0123456789._") != std::string::npos){ 15 | SPDLOG_LOGGER_ERROR(g_logger, "config invalid name : {} : {}", perfix, node.Scalar()); 16 | return; 17 | } 18 | output.emplace_back(perfix,node); 19 | if(node.IsMap()){ 20 | for(auto& item : node){ 21 | std::string name = perfix.empty()? item.first.Scalar() : perfix + "." + item.first.Scalar(); 22 | ListAllMenbers(name,item.second,output); 23 | } 24 | } 25 | } 26 | 27 | void Config::LoadFromYaml(const YAML::Node &root) { 28 | std::list> allNodes; 29 | ListAllMenbers("",root,allNodes); 30 | for(auto& item:allNodes){ 31 | std::string key = item.first; 32 | if(key.empty()) 33 | continue; 34 | std::transform(key.begin(),key.end(),key.begin(),::tolower); 35 | auto var = LookupBase(key); 36 | if(var){ 37 | if(item.second.IsScalar()){ 38 | var->fromString(item.second.Scalar()); 39 | } else{ 40 | std::stringstream ss; 41 | ss << item.second; 42 | var->fromString(ss.str()); 43 | } 44 | } 45 | } 46 | } 47 | 48 | void Config::LoadFromFile(const std::string &file) { 49 | SPDLOG_LOGGER_INFO(g_logger, "load config from file: {}", file); 50 | YAML::Node root = YAML::LoadFile(file); 51 | LoadFromYaml(root); 52 | } 53 | 54 | ConfigVarBase::ptr Config::LookupBase(const std::string &name) { 55 | std::unique_lock lock(GetMutex().Reader()); 56 | auto it = GetDatas().find(name); 57 | if(it == GetDatas().end()) 58 | return nullptr; 59 | return it->second; 60 | } 61 | 62 | void Config::Visit(std::function cb) { 63 | auto& config = GetDatas(); 64 | for(auto& item: config){ 65 | cb(item.second); 66 | } 67 | } 68 | 69 | } -------------------------------------------------------------------------------- /acid/common/lexical_cast.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2022/1/26. 3 | // 4 | 5 | #include "lexical_cast.h" 6 | 7 | namespace acid { 8 | namespace detail { 9 | const char* strue = "true"; 10 | const char* sfalse = "false"; 11 | 12 | bool checkbool(const char* from, const size_t len, const char* s) { 13 | for (size_t i = 0; i < len; i++) { 14 | if (from[i] != s[i]) { 15 | return false; 16 | } 17 | } 18 | 19 | return true; 20 | } 21 | 22 | bool convert(const char* from) { 23 | const unsigned int len = strlen(from); 24 | if (len != 4 && len != 5) 25 | throw std::invalid_argument("argument is invalid"); 26 | 27 | bool r = true; 28 | if (len == 4) { 29 | r = checkbool(from, len, strue); 30 | 31 | if (r) 32 | return true; 33 | } 34 | else { 35 | r = checkbool(from, len, sfalse); 36 | 37 | if (r) 38 | return false; 39 | } 40 | 41 | throw std::invalid_argument("argument is invalid"); 42 | } 43 | } 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /acid/common/lexical_cast.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2022/1/25. 3 | // 4 | 5 | #ifndef ACID_LEXICAL_CAST_H 6 | #define ACID_LEXICAL_CAST_H 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace acid { 16 | namespace detail { 17 | 18 | template 19 | struct Converter { 20 | }; 21 | 22 | //to numeric 23 | template 24 | struct Converter { 25 | static int convert(const From& from) { 26 | return std::stoi(from); 27 | } 28 | }; 29 | 30 | template 31 | struct Converter { 32 | static int convert(const From& from) { 33 | return std::stoul(from); 34 | } 35 | }; 36 | 37 | template 38 | struct Converter { 39 | static long convert(const From& from) { 40 | return std::stol(from); 41 | } 42 | }; 43 | 44 | template 45 | struct Converter { 46 | static long convert(const From& from) { 47 | return std::stoul(from); 48 | } 49 | }; 50 | 51 | template 52 | struct Converter { 53 | static long long convert(const From& from) { 54 | return std::stoll(from); 55 | } 56 | }; 57 | 58 | template 59 | struct Converter { 60 | static long long convert(const From& from) { 61 | return std::stoull(from); 62 | } 63 | }; 64 | 65 | template 66 | struct Converter { 67 | static double convert(const From& from) { 68 | return std::stod(from); 69 | } 70 | }; 71 | 72 | template 73 | struct Converter { 74 | static float convert(const From& from) { 75 | return (float)std::stof(from); 76 | } 77 | }; 78 | 79 | //to bool 80 | template 81 | struct Converter { 82 | static typename std::enable_if::value, bool>::type convert(From from) { 83 | return !!from; 84 | } 85 | }; 86 | 87 | bool checkbool(const char* from, const size_t len, const char* s); 88 | 89 | bool convert(const char* from); 90 | 91 | template <> 92 | struct Converter { 93 | static bool convert(const std::string& from) { 94 | return detail::convert(from.c_str()); 95 | } 96 | }; 97 | 98 | template <> 99 | struct Converter { 100 | static bool convert(const char* from) { 101 | return detail::convert(from); 102 | } 103 | }; 104 | 105 | template <> 106 | struct Converter { 107 | static bool convert(char* from) { 108 | return detail::convert(from); 109 | } 110 | }; 111 | 112 | template 113 | struct Converter { 114 | static bool convert(const char(&from)[N]) { 115 | return detail::convert(from); 116 | } 117 | }; 118 | 119 | template 120 | struct Converter { 121 | static bool convert(const char(&from)[N]) { 122 | return detail::convert(from); 123 | } 124 | }; 125 | 126 | //to string 127 | template 128 | struct Converter { 129 | static std::string convert(const From& from) { 130 | return std::to_string(from); 131 | } 132 | }; 133 | } 134 | 135 | template 136 | typename std::enable_if::value, To>::type 137 | lexical_cast(const From& from) { 138 | return detail::Converter::convert(from); 139 | } 140 | 141 | template 142 | typename std::enable_if::value, To>::type 143 | lexical_cast(const From& from) { 144 | return from; 145 | } 146 | 147 | } 148 | #endif //ACID_LEXICAL_CAST_H 149 | -------------------------------------------------------------------------------- /acid/common/stream.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2021/12/15. 3 | // 4 | 5 | #include "stream.h" 6 | 7 | namespace acid { 8 | 9 | ssize_t Stream::readFixSize(void *buffer, size_t length) { 10 | size_t offset = 0; 11 | size_t left = length; 12 | while (left > 0) { 13 | ssize_t n = read((char*)buffer + offset, left); 14 | if(n <= 0) { 15 | return n; 16 | } 17 | offset += n; 18 | left -= n; 19 | } 20 | return length; 21 | } 22 | 23 | ssize_t Stream::readFixSize(ByteArray::ptr buffer, size_t length) { 24 | size_t left = length; 25 | while (left > 0) { 26 | ssize_t n = read(buffer, left); 27 | if(n <= 0) { 28 | return n; 29 | } 30 | left -= n; 31 | } 32 | return length; 33 | } 34 | 35 | ssize_t Stream::writeFixSize(const void *buffer, size_t length) { 36 | size_t offset = 0; 37 | size_t left = length; 38 | while (left > 0) { 39 | ssize_t n = write((const char*)buffer + offset, left); 40 | if(n <= 0) { 41 | return n; 42 | } 43 | offset += n; 44 | left -= n; 45 | } 46 | return length; 47 | } 48 | 49 | ssize_t Stream::writeFixSize(ByteArray::ptr buffer, size_t length) { 50 | size_t offset = 0; 51 | size_t left = length; 52 | while (left > 0) { 53 | ssize_t n = write(buffer, left); 54 | if(n <= 0) { 55 | return n; 56 | } 57 | offset += n; 58 | left -= n; 59 | } 60 | return length; 61 | } 62 | } -------------------------------------------------------------------------------- /acid/common/stream.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2021/12/15. 3 | // 4 | 5 | #ifndef ACID_STREAM_H 6 | #define ACID_STREAM_H 7 | #include 8 | #include "acid/common/byte_array.h" 9 | namespace acid { 10 | class Stream { 11 | public: 12 | using ptr = std::shared_ptr; 13 | virtual ~Stream() {}; 14 | virtual ssize_t read(void* buffer, size_t length) = 0; 15 | virtual ssize_t read(ByteArray::ptr buffer, size_t length) = 0; 16 | 17 | virtual ssize_t write(const void* buffer, size_t length) = 0; 18 | virtual ssize_t write(ByteArray::ptr buffer, size_t length) = 0; 19 | 20 | virtual void close() = 0; 21 | 22 | ssize_t readFixSize(void* buffer, size_t length); 23 | ssize_t readFixSize(ByteArray::ptr buffer, size_t length); 24 | 25 | ssize_t writeFixSize(const void* buffer, size_t length); 26 | ssize_t writeFixSize(ByteArray::ptr buffer, size_t length); 27 | private: 28 | 29 | }; 30 | } 31 | #endif //ACID_STREAM_H 32 | -------------------------------------------------------------------------------- /acid/common/time_measure.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2021/9/28. 3 | // 4 | 5 | #ifndef ACID_TIMEMEASURE_H 6 | #define ACID_TIMEMEASURE_H 7 | #include 8 | #include 9 | #include 10 | 11 | namespace acid { 12 | using namespace std; 13 | using namespace std::chrono; 14 | /** 15 | * @brief 工具类,测试程序执行时间 16 | */ 17 | class TimeMeasure{ 18 | public: 19 | TimeMeasure(): m_begin(high_resolution_clock::now()){ 20 | 21 | } 22 | ~TimeMeasure(){ 23 | std::cout<< std::endl << " cost: " << sp(elapsed())<< " 毫秒" 24 | << " micro: " << sp(elapsed_micro()) << " 微妙" 25 | << " nano: "<< sp(elapsed_nano()) << " 纳秒"<< std::endl; 26 | } 27 | void reset() { m_begin = high_resolution_clock::now(); } 28 | 29 | int64_t elapsed() const 30 | { 31 | return duration_cast(high_resolution_clock::now() - m_begin).count(); 32 | } 33 | 34 | int64_t elapsed_micro() const 35 | { 36 | return duration_cast(high_resolution_clock::now() - m_begin).count(); 37 | } 38 | 39 | int64_t elapsed_nano() const 40 | { 41 | return duration_cast(high_resolution_clock::now() - m_begin).count(); 42 | } 43 | 44 | int64_t elapsed_seconds() const 45 | { 46 | return duration_cast(high_resolution_clock::now() - m_begin).count(); 47 | } 48 | 49 | int64_t elapsed_minutes() const 50 | { 51 | return duration_cast(high_resolution_clock::now() - m_begin).count(); 52 | } 53 | 54 | int64_t elapsed_hours() const 55 | { 56 | return duration_cast(high_resolution_clock::now() - m_begin).count(); 57 | } 58 | private: 59 | time_point m_begin; 60 | std::string sp(int64_t n){ 61 | std::string str=std::to_string(n); 62 | int cnt=0; 63 | for(int i=str.size()-1;i>=0;i--){ 64 | cnt++; 65 | if(cnt==3 && i){ 66 | cnt=0; 67 | str.insert(str.begin() +i,','); 68 | } 69 | } 70 | return str; 71 | } 72 | }; 73 | } 74 | #endif //ACID_TIMEMEASURE_H 75 | -------------------------------------------------------------------------------- /acid/common/traits.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2022/1/14. 3 | // 4 | 5 | #ifndef ACID_TRAITS_H 6 | #define ACID_TRAITS_H 7 | #include 8 | namespace acid { 9 | 10 | // 原型 11 | template 12 | struct function_traits; 13 | 14 | // 普通函数 15 | template 16 | struct function_traits 17 | { 18 | enum { arity = sizeof...(Args) }; 19 | using return_type = ReturnType; 20 | using function_type = ReturnType(Args...); 21 | using stl_function_type = std::function; 22 | using pointer = ReturnType(*)(Args...); 23 | 24 | template 25 | struct args 26 | { 27 | static_assert(I < arity, "index is out of range, index must less than sizeof Args"); 28 | using type = typename std::tuple_element>::type; 29 | }; 30 | 31 | using tuple_type = std::tuple>...>; 32 | using bare_tuple_type = std::tuple>...>; 33 | }; 34 | 35 | // 函数指针 36 | template 37 | struct function_traits : function_traits {}; 38 | 39 | // std::function 40 | template 41 | struct function_traits> : function_traits {}; 42 | 43 | // 成员函数 44 | #define FUNCTION_TRAITS(...)\ 45 | template \ 46 | struct function_traits : function_traits{};\ 47 | 48 | FUNCTION_TRAITS() 49 | FUNCTION_TRAITS(const) 50 | FUNCTION_TRAITS(volatile) 51 | FUNCTION_TRAITS(const volatile) 52 | 53 | // 函数对象 54 | template 55 | struct function_traits : function_traits {}; 56 | 57 | template 58 | typename function_traits::stl_function_type to_function(const Function& lambda) 59 | { 60 | return static_cast::stl_function_type>(lambda); 61 | } 62 | 63 | template 64 | typename function_traits::stl_function_type to_function(Function&& lambda) 65 | { 66 | return static_cast::stl_function_type>(std::forward(lambda)); 67 | } 68 | 69 | template 70 | typename function_traits::pointer to_function_pointer(const Function& lambda) 71 | { 72 | return static_cast::pointer>(lambda); 73 | } 74 | } 75 | #endif //ACID_TRAITS_H 76 | -------------------------------------------------------------------------------- /acid/common/util.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2021/10/20. 3 | // 4 | #include 5 | #include 6 | #include 7 | #include "util.h" 8 | namespace acid{ 9 | static std::shared_ptr GetLogInstanceHelper() { 10 | auto instance = spdlog::stdout_color_mt("acid"); 11 | #if ACID_ENABLE_DEBUGGER 12 | instance->set_level(spdlog::level::debug); 13 | SPDLOG_LOGGER_INFO(instance, "ENABLE_DEBUGGER"); 14 | #endif 15 | return instance; 16 | } 17 | 18 | std::shared_ptr GetLogInstance() { 19 | static std::shared_ptr instance = GetLogInstanceHelper(); 20 | return instance; 21 | } 22 | 23 | void Backtrace(std::vector& bt, int size, int skip) { 24 | void** array = (void**)malloc((sizeof(void*) * size)); 25 | size_t s = backtrace(array, size); 26 | 27 | char** strings = backtrace_symbols(array, s); 28 | if(strings == NULL) { 29 | SPDLOG_LOGGER_ERROR(GetLogInstance(),"backtrace_synbols error"); 30 | free(array); 31 | return; 32 | } 33 | 34 | for(size_t i = skip; i < s; ++i) { 35 | bt.push_back(strings[i]); 36 | } 37 | 38 | free(strings); 39 | free(array); 40 | } 41 | 42 | std::string BacktraceToString(int size, int skip, const std::string& prefix) { 43 | std::vector bt; 44 | Backtrace(bt, size, skip); 45 | std::stringstream ss; 46 | for(size_t i = 0; i < bt.size(); ++i) { 47 | ss << prefix << bt[i] << std::endl; 48 | } 49 | return ss.str(); 50 | } 51 | 52 | uint64_t GetCurrentMS(){ 53 | struct timeval tm; 54 | gettimeofday(&tm,0); 55 | return tm.tv_sec * 1000ul + tm.tv_usec / 1000; 56 | } 57 | 58 | uint64_t GetCurrentUS(){ 59 | struct timeval tm; 60 | gettimeofday(&tm,0); 61 | return tm.tv_sec * 1000ul * 1000ul + tm.tv_usec; 62 | } 63 | 64 | CycleTimerTocken CycleTimer(unsigned interval_ms, std::function cb, co::Scheduler* worker, int times) { 65 | if (!worker) { 66 | auto sc = co::Processer::GetCurrentScheduler(); 67 | if (sc) { 68 | worker = sc; 69 | } else { 70 | worker = &co_sched; 71 | } 72 | } 73 | 74 | CycleTimerTocken tocken(std::make_shared(false)); 75 | go co_scheduler(worker) [tocken, interval_ms, cb, times]() mutable { 76 | while (times) { 77 | co_sleep(interval_ms); 78 | if (tocken.isCancel()) return; 79 | cb(); 80 | if (times > 0) --times; 81 | } 82 | }; 83 | return tocken; 84 | } 85 | 86 | } -------------------------------------------------------------------------------- /acid/common/util.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2021/10/20. 3 | // 4 | 5 | #ifndef ACID_UTIL_H 6 | #define ACID_UTIL_H 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #if ACID_ENABLE_DEBUGGER 21 | # define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_DEBUG 22 | #endif 23 | 24 | #include 25 | #include 26 | 27 | // 前向声明 28 | namespace co { 29 | class Scheduler; 30 | } 31 | 32 | namespace acid{ 33 | 34 | std::shared_ptr GetLogInstance(); 35 | 36 | void Backtrace(std::vector& bt, int size, int skip = 1 ); 37 | std::string BacktraceToString(int size, int skip = 2, const std::string& prefix = ""); 38 | 39 | //时间 40 | uint64_t GetCurrentMS(); 41 | uint64_t GetCurrentUS(); 42 | 43 | 44 | //bit 45 | 46 | /** 47 | * @brief 字节序转换 48 | */ 49 | template 50 | constexpr T ByteSwap(T value) { 51 | if constexpr(sizeof(T) == sizeof(uint8_t)){ 52 | return (T)bswap_16((uint16_t)value); 53 | } else if constexpr(sizeof(T) == sizeof(uint16_t)){ 54 | return (T)bswap_16((uint16_t)value); 55 | } else if constexpr(sizeof(T) == sizeof(uint32_t)){ 56 | return (T)bswap_32((uint32_t)value); 57 | } else if constexpr(sizeof(T) == sizeof(uint64_t)){ 58 | return (T)bswap_64((uint64_t)value); 59 | } 60 | } 61 | 62 | /** 63 | * @brief 网络序主机序转换 64 | */ 65 | template 66 | constexpr T EndianCast(T value) { 67 | if constexpr(sizeof(T) == sizeof(uint8_t) 68 | || std::endian::native == std::endian::big){ 69 | return value; 70 | } else if constexpr(std::endian::native == std::endian::little){ 71 | return ByteSwap(value); 72 | } 73 | } 74 | 75 | class CycleTimerTocken { 76 | public: 77 | CycleTimerTocken(std::shared_ptr cancel = nullptr) : m_cancel(std::move(cancel)) {} 78 | 79 | operator bool () { 80 | return m_cancel || !isCancel(); 81 | } 82 | 83 | void stop() { 84 | if (!m_cancel) return; 85 | *m_cancel = true; 86 | } 87 | 88 | bool isCancel() { 89 | if (!m_cancel) { 90 | return true; 91 | } 92 | return *m_cancel; 93 | } 94 | 95 | private: 96 | std::shared_ptr m_cancel; 97 | }; 98 | 99 | /** 100 | * 设置循环定时器 101 | * @param interval_ms 循环间隔(ms) 102 | * @param cb 回调函数 103 | * @param worker 定时器所在的调度器,如果默认则会先检查函数是否在协程内,在的话则设置为所在协程的调度器,否则设置为 co_sched 104 | * @param times 循环次数,-1为无限循环 105 | * @return CycleTimerTocken 可用来停止循环定时器 106 | */ 107 | CycleTimerTocken CycleTimer(unsigned interval_ms, std::function cb, co::Scheduler* worker = nullptr, int times = -1); 108 | 109 | }; 110 | 111 | #endif //ACID_UTIL_H 112 | -------------------------------------------------------------------------------- /acid/http/http_connection.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2021/12/18. 3 | // 4 | 5 | #ifndef ACID_HTTP_CONNECTION_H 6 | #define ACID_HTTP_CONNECTION_H 7 | #include 8 | #include 9 | #include "acid/net/socket_stream.h" 10 | #include "acid/net/uri.h" 11 | #include "http.h" 12 | namespace acid::http { 13 | struct HttpResult { 14 | using ptr = std::shared_ptr; 15 | enum Result{ 16 | OK = 0, 17 | INVALID_URL, 18 | INVALID_HOST, 19 | CREATE_SOCKET_ERROR, 20 | CONNECT_FAIL, 21 | SEND_CLOSE_BY_PEER, 22 | SEND_SOCKET_ERROR, 23 | TIMEOUT, 24 | POOL_INVALID_CONNECTION, 25 | }; 26 | HttpResult(Result result_, HttpResponse::ptr response_, const std::string& msg_) 27 | : result(result_) 28 | , response(response_) 29 | , msg(msg_){ 30 | } 31 | std::string toString() const; 32 | Result result; 33 | HttpResponse::ptr response; 34 | std::string msg; 35 | }; 36 | class HttpConnectionPool; 37 | class HttpConnection : public SocketStream { 38 | friend HttpConnectionPool; 39 | public: 40 | using ptr = std::shared_ptr; 41 | ~HttpConnection(); 42 | static HttpResult::ptr DoGet(const std::string& url, 43 | uint64_t timeout_ms = -1, 44 | const std::map& header = {}, 45 | const std::string& body = ""); 46 | 47 | static HttpResult::ptr DoGet(Uri::ptr uri, 48 | uint64_t timeout_ms = -1, 49 | const std::map& header = {}, 50 | const std::string& body = ""); 51 | 52 | static HttpResult::ptr DoPost(const std::string& url, 53 | uint64_t timeout_ms = -1, 54 | const std::map& header = {}, 55 | const std::string& body = ""); 56 | 57 | static HttpResult::ptr DoPost(Uri::ptr uri, 58 | uint64_t timeout_ms = -1, 59 | const std::map& header = {}, 60 | const std::string& body = ""); 61 | 62 | static HttpResult::ptr DoRequest(HttpMethod method, 63 | const std::string& url, 64 | uint64_t timeout_ms = -1, 65 | const std::map& header = {}, 66 | const std::string& body = ""); 67 | 68 | static HttpResult::ptr DoRequest(HttpMethod method, 69 | Uri::ptr uri, 70 | uint64_t timeout_ms = -1, 71 | const std::map& header = {}, 72 | const std::string& body = ""); 73 | 74 | static HttpResult::ptr DoRequest(HttpRequest::ptr request, Uri::ptr uri, uint64_t timeout_ms = -1); 75 | 76 | HttpConnection(Socket::ptr socket, bool owner = true); 77 | 78 | HttpResponse::ptr recvResponse(); 79 | 80 | ssize_t sendRequest(HttpRequest::ptr request); 81 | private: 82 | uint64_t m_createTime = 0; 83 | uint64_t m_request = 0; 84 | }; 85 | 86 | class HttpConnectionPool { 87 | public: 88 | using ptr = std::shared_ptr; 89 | using MutexType = co::co_mutex; 90 | static HttpConnectionPool::ptr Create(const std::string& uri 91 | ,const std::string& vhost 92 | ,uint32_t max_size 93 | ,uint32_t max_alive_time 94 | ,uint32_t max_request); 95 | HttpConnectionPool(const std::string& host 96 | ,const std::string& vhost 97 | ,uint32_t port 98 | ,bool is_https 99 | ,uint32_t max_size 100 | ,uint32_t max_alive_time 101 | ,uint32_t max_request); 102 | 103 | HttpResult::ptr doGet(const std::string& url, 104 | uint64_t timeout_ms = -1, 105 | const std::map& header = {}, 106 | const std::string& body = ""); 107 | 108 | HttpResult::ptr doGet(Uri::ptr uri, 109 | uint64_t timeout_ms = -1, 110 | const std::map& header = {}, 111 | const std::string& body = ""); 112 | 113 | HttpResult::ptr doPost(const std::string& url, 114 | uint64_t timeout_ms = -1, 115 | const std::map& header = {}, 116 | const std::string& body = ""); 117 | 118 | HttpResult::ptr doPost(Uri::ptr uri, 119 | uint64_t timeout_ms = -1, 120 | const std::map& header = {}, 121 | const std::string& body = ""); 122 | 123 | HttpResult::ptr doRequest(HttpMethod method, 124 | const std::string& url, 125 | uint64_t timeout_ms = -1, 126 | const std::map& header = {}, 127 | const std::string& body = ""); 128 | 129 | HttpResult::ptr doRequest(HttpMethod method, 130 | Uri::ptr uri, 131 | uint64_t timeout_ms = -1, 132 | const std::map& header = {}, 133 | const std::string& body = ""); 134 | 135 | HttpResult::ptr doRequest(HttpRequest::ptr request, uint64_t timeout_ms = -1); 136 | 137 | HttpConnection::ptr getConnection(); 138 | 139 | private: 140 | static void RelesePtr(HttpConnection* ptr, HttpConnectionPool* pool); 141 | private: 142 | std::string m_host; 143 | std::string m_vhost; 144 | uint32_t m_port; 145 | uint32_t m_maxSize; 146 | uint32_t m_maxAliveTime; 147 | uint32_t m_maxRequest; 148 | bool m_isHttps; 149 | MutexType m_mutex; 150 | std::list m_conns; 151 | std::atomic m_total{0}; 152 | }; 153 | 154 | } 155 | 156 | #endif //ACID_HTTP_CONNECTION_H 157 | -------------------------------------------------------------------------------- /acid/http/http_server.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2021/12/15. 3 | // 4 | 5 | #include "acid/http/http_server.h" 6 | 7 | #include 8 | #include "acid/http/parse.h" 9 | #include "acid/http/servlet.h" 10 | namespace acid::http { 11 | static auto g_logger = GetLogInstance(); 12 | 13 | HttpServer::HttpServer(bool keepalive) 14 | : m_isKeepalive(keepalive){ 15 | m_dispatch.reset(new ServletDispatch); 16 | 17 | } 18 | 19 | void HttpServer::handleClient(Socket::ptr client) { 20 | SPDLOG_LOGGER_DEBUG(g_logger, "handleClient: {}", client->toString()); 21 | HttpRequestParser::ptr parser(new HttpRequestParser); 22 | HttpSession::ptr session(new HttpSession(client)); 23 | while (true) { 24 | HttpRequest::ptr request = session->recvRequest(); 25 | if (!request) { 26 | session->close(); 27 | SPDLOG_LOGGER_DEBUG(g_logger, "recv http request fail, errno={}, errstr={}, client={}, keep_alive={}", 28 | errno, strerror(errno), client->toString(), m_isKeepalive); 29 | break; 30 | } 31 | HttpResponse::ptr response(new HttpResponse(request->getVersion(), request->isClose() || !m_isKeepalive)); 32 | response->setHeader("Server" ,getName()); 33 | 34 | if (m_dispatch->handle(request, response, session) == 0) { 35 | session->sendResponse(response); 36 | } 37 | 38 | if (request->isClose() || !m_isKeepalive) { 39 | break; 40 | } 41 | } 42 | session->close(); 43 | } 44 | 45 | void HttpServer::setName(const std::string& name) { 46 | TcpServer::setName(name); 47 | m_dispatch->setDefault(std::make_shared(name)); 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /acid/http/http_server.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2021/12/15. 3 | // 4 | 5 | #ifndef ACID_HTTP_SERVER_H 6 | #define ACID_HTTP_SERVER_H 7 | #include 8 | #include "http.h" 9 | #include "http_session.h" 10 | #include "servlet.h" 11 | #include "acid/net/tcp_server.h" 12 | 13 | namespace acid::http { 14 | class HttpServer : public TcpServer { 15 | public: 16 | HttpServer(bool keepalive = false); 17 | ServletDispatch::ptr getServletDispatch() const { return m_dispatch;} 18 | void setServletDispatch(ServletDispatch::ptr v) { m_dispatch = v;} 19 | void setName(const std::string& name) override; 20 | protected: 21 | void handleClient(Socket::ptr client) override; 22 | 23 | private: 24 | bool m_isKeepalive; 25 | ServletDispatch::ptr m_dispatch; 26 | }; 27 | } 28 | #endif //ACID_HTTP_SERVER_H 29 | -------------------------------------------------------------------------------- /acid/http/http_session.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2021/12/15. 3 | // 4 | 5 | #include "acid/http/http_session.h" 6 | #include "acid/http/parse.h" 7 | namespace acid::http { 8 | static auto g_logger = GetLogInstance(); 9 | 10 | HttpSession::HttpSession(Socket::ptr socket, bool owner) 11 | : SocketStream(socket, owner) { 12 | } 13 | 14 | HttpRequest::ptr HttpSession::recvRequest() { 15 | HttpRequestParser::ptr parser(new HttpRequestParser); 16 | uint64_t buff_size = HttpRequestParser::GetHttpRequestBufferSize(); 17 | std::string buffer; 18 | buffer.resize(buff_size); 19 | char* data = &buffer[0]; 20 | size_t offset = 0; 21 | while (!parser->isFinished()) { 22 | ssize_t len = read(data + offset, buff_size - offset); 23 | if (len <= 0) { 24 | close(); 25 | return nullptr; 26 | } 27 | len += offset; 28 | size_t nparse = parser->execute(data, len); 29 | if (parser->hasError() || nparse == 0) { 30 | SPDLOG_LOGGER_DEBUG(g_logger, "parser error code: {}", parser->hasError()); 31 | close(); 32 | return nullptr; 33 | } 34 | offset = len - nparse; 35 | } 36 | uint64_t length = parser->getContentLength(); 37 | if (length >= 0) { 38 | std::string body; 39 | body.resize(length); 40 | size_t len = 0; 41 | if (length >= offset) { 42 | memcpy(&body[0], data, offset); 43 | len = offset; 44 | } else { 45 | memcpy(&body[0], data, length); 46 | len = length; 47 | } 48 | length -= len; 49 | if(length > 0) { 50 | if(readFixSize(&body[len], length) <= 0) { 51 | close(); 52 | return nullptr; 53 | } 54 | } 55 | parser->getData()->setBody(body); 56 | } 57 | return parser->getData(); 58 | } 59 | 60 | ssize_t HttpSession::sendResponse(HttpResponse::ptr response) { 61 | std::string str = response->toString(); 62 | return writeFixSize(str.c_str(), str.size()); 63 | } 64 | 65 | } -------------------------------------------------------------------------------- /acid/http/http_session.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2021/12/15. 3 | // 4 | 5 | #ifndef ACID_HTTP_SESSION_H 6 | #define ACID_HTTP_SESSION_H 7 | #include 8 | #include "acid/net/socket_stream.h" 9 | #include "http.h" 10 | 11 | namespace acid::http { 12 | class HttpSession : public SocketStream { 13 | public: 14 | using ptr = std::shared_ptr; 15 | 16 | HttpSession(Socket::ptr socket, bool owner = true); 17 | 18 | HttpRequest::ptr recvRequest(); 19 | 20 | ssize_t sendResponse(HttpResponse::ptr response); 21 | }; 22 | } 23 | 24 | #endif //ACID_HTTP_SESSION_H 25 | -------------------------------------------------------------------------------- /acid/http/parse.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2021/12/13. 3 | // 4 | 5 | #ifndef ACID_PARSE_H 6 | #define ACID_PARSE_H 7 | #include 8 | #include 9 | #include "http.h" 10 | namespace acid::http{ 11 | 12 | template 13 | struct Task { 14 | struct promise_type; 15 | using handle = std::coroutine_handle; 16 | struct promise_type { 17 | Task get_return_object() { 18 | return {std::coroutine_handle::from_promise(*this)}; 19 | } 20 | std::suspend_always initial_suspend() { 21 | return {}; 22 | } 23 | std::suspend_always final_suspend() noexcept { 24 | return {}; 25 | } 26 | void return_value(T val) { 27 | m_val = std::move(val); 28 | } 29 | std::suspend_always yield_value(T val) { 30 | m_val = val; 31 | return {}; 32 | } 33 | void unhandled_exception() { 34 | std::terminate(); 35 | } 36 | T m_val; 37 | }; 38 | Task() : m_handle(nullptr) {} 39 | Task(handle h): m_handle(h){} 40 | Task(const Task& rhs) = delete; 41 | Task(Task&& rhs) noexcept 42 | : m_handle(rhs.m_handle) { 43 | rhs.m_handle = nullptr; 44 | } 45 | 46 | Task& operator=(const Task& rhs) = delete; 47 | Task& operator=(Task&& rhs) noexcept { 48 | if (std::addressof(rhs) != this) 49 | { 50 | if (m_handle) 51 | { 52 | m_handle.destroy(); 53 | } 54 | 55 | m_handle = rhs.m_handle; 56 | rhs.m_handle = nullptr; 57 | } 58 | 59 | return *this; 60 | } 61 | T get() { 62 | return m_handle.promise().m_val; 63 | } 64 | void resume() { 65 | m_handle.resume(); 66 | } 67 | bool done() { 68 | return !m_handle || m_handle.done(); 69 | } 70 | void destroy() { 71 | m_handle.destroy(); 72 | m_handle = nullptr; 73 | } 74 | 75 | ~Task() { 76 | if (m_handle) { 77 | m_handle.destroy(); 78 | } 79 | } 80 | private: 81 | handle m_handle = nullptr; 82 | }; 83 | 84 | /** 85 | * @brief HTTP解析抽象类 86 | */ 87 | class HttpParser{ 88 | public: 89 | /// 错误码 90 | enum Error { 91 | NO_ERROR = 0, 92 | INVALID_METHOD, 93 | INVALID_PATH, 94 | INVALID_VERSION, 95 | INVALID_LINE, 96 | INVALID_HEADER, 97 | INVALID_CODE, 98 | INVALID_REASON, 99 | INVALID_CHUNK 100 | }; 101 | enum CheckState{ 102 | NO_CHECK, 103 | CHECK_LINE, 104 | CHECK_HEADER, 105 | CHECK_CHUNK 106 | }; 107 | /** 108 | * @brief 构造函数 109 | */ 110 | HttpParser(); 111 | 112 | /** 113 | * @brief 析构函数 114 | */ 115 | virtual ~HttpParser(); 116 | 117 | /** 118 | * @brief 解析协议 119 | * @param data 协议文本内存 120 | * @param len 协议文本内存长度 121 | * @param chunk 标识是否解析 http chunk 122 | * @return 返回实际解析的长度,并且将已解析的数据移除 123 | */ 124 | size_t execute(char* data, size_t len, bool chunk = false); 125 | /** 126 | * @brief 是否解析完成 127 | * @return 是否解析完成 128 | */ 129 | int isFinished(); 130 | 131 | /** 132 | * @brief 是否有错误 133 | * @return 是否有错误 134 | */ 135 | int hasError(); 136 | 137 | /** 138 | * @brief 设置错误 139 | * @param[in] v 错误值 140 | */ 141 | void setError(Error v) { m_error = v;} 142 | 143 | protected: 144 | 145 | virtual Task parse_line() = 0; 146 | 147 | virtual Task parse_header() = 0; 148 | 149 | virtual Task parse_chunk() = 0; 150 | 151 | protected: 152 | int m_error = 0; 153 | bool m_finish = false; 154 | char* m_cur = nullptr; 155 | 156 | //acid::IOManager* m_mgr = nullptr; 157 | //acid::Fiber::ptr m_executor; 158 | //acid::Fiber::ptr m_parser; 159 | Task m_parser{}; 160 | CheckState m_checkState = NO_CHECK; 161 | }; 162 | 163 | /** 164 | * @brief HTTP请求解析类 165 | */ 166 | class HttpRequestParser : public HttpParser{ 167 | public: 168 | using ptr = std::shared_ptr; 169 | HttpRequestParser() : HttpParser(), m_data(new HttpRequest) {} 170 | 171 | 172 | /** 173 | * @brief 返回HttpRequest结构体 174 | */ 175 | HttpRequest::ptr getData() const { return m_data;} 176 | 177 | /** 178 | * @brief 获取消息体长度 179 | */ 180 | uint64_t getContentLength(); 181 | public: 182 | /** 183 | * @brief 返回HttpRequest协议解析的缓存大小 184 | */ 185 | static uint64_t GetHttpRequestBufferSize(); 186 | 187 | /** 188 | * @brief 返回HttpRequest协议的最大消息体大小 189 | */ 190 | static uint64_t GetHttpRequestMaxBodySize(); 191 | 192 | private: 193 | 194 | void on_request_method(const std::string& str); 195 | 196 | void on_request_path(const std::string& str); 197 | 198 | void on_request_query(const std::string& str); 199 | 200 | void on_request_fragment(const std::string& str) ; 201 | 202 | void on_request_version(const std::string& str); 203 | 204 | void on_request_header(const std::string& key, const std::string& val); 205 | 206 | void on_request_header_done(); 207 | 208 | protected: 209 | Task parse_line() override; 210 | 211 | Task parse_header() override; 212 | 213 | Task parse_chunk() override; 214 | 215 | private: 216 | /// HttpRequest结构 217 | HttpRequest::ptr m_data; 218 | }; 219 | 220 | /** 221 | * @brief HTTP响应解析类 222 | */ 223 | class HttpResponseParser : public HttpParser{ 224 | public: 225 | using ptr = std::shared_ptr; 226 | HttpResponseParser() : HttpParser(), m_data(new HttpResponse) {} 227 | 228 | /** 229 | * @brief 返回HttpRequest结构体 230 | */ 231 | HttpResponse::ptr getData() const { return m_data;} 232 | 233 | /** 234 | * @brief 获取消息体长度 235 | */ 236 | uint64_t getContentLength(); 237 | 238 | /** 239 | * @brief 获取消息体是否为 chunk 格式 240 | */ 241 | bool isChunked(); 242 | public: 243 | /** 244 | * @brief 返回HttpRequest协议解析的缓存大小 245 | */ 246 | static uint64_t GetHttpResponseBufferSize(); 247 | 248 | /** 249 | * @brief 返回HttpRequest协议的最大消息体大小 250 | */ 251 | static uint64_t GetHttpResponseMaxBodySize(); 252 | 253 | private: 254 | 255 | void on_response_version(const std::string& str); 256 | 257 | void on_response_status(const std::string& str); 258 | 259 | void on_response_reason(const std::string& str); 260 | 261 | void on_response_header(const std::string& key, const std::string& val); 262 | 263 | void on_response_header_done(); 264 | 265 | void on_response_chunk(const std::string& str); 266 | 267 | protected: 268 | Task parse_line() override; 269 | 270 | Task parse_header() override; 271 | 272 | Task parse_chunk() override; 273 | 274 | private: 275 | /// HttpRequest结构 276 | HttpResponse::ptr m_data; 277 | }; 278 | 279 | 280 | } 281 | 282 | #endif //ACID_PARSE_H 283 | -------------------------------------------------------------------------------- /acid/http/servlet.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2021/12/17. 3 | // 4 | 5 | #include 6 | #include "acid/http/servlet.h" 7 | 8 | namespace acid::http { 9 | 10 | int32_t FunctionServlet::handle(HttpRequest::ptr request, HttpResponse::ptr response, HttpSession::ptr session) { 11 | return m_cb(request, response, session); 12 | } 13 | 14 | 15 | NotFoundServlet::NotFoundServlet(const std::string &name) 16 | : Servlet("NotFoundServlet") 17 | , m_name(name){ 18 | m_content = "" 19 | "404 Not Found" 20 | "" 21 | "

404 Not Found

" 22 | "
" + m_name + "
" 23 | "" 24 | ""; 25 | } 26 | 27 | int32_t NotFoundServlet::handle(HttpRequest::ptr request, HttpResponse::ptr response, HttpSession::ptr session) { 28 | response->setStatus(http::HttpStatus::NOT_FOUND); 29 | response->setHeader("Content-Type","text/html"); 30 | response->setHeader("Server", "acid/1.0.0"); 31 | response->setBody(m_content); 32 | return 0; 33 | } 34 | 35 | 36 | ServletDispatch::ServletDispatch() : Servlet("ServletDispatch") { 37 | m_default.reset(new NotFoundServlet("acid/1.0.0")); 38 | } 39 | 40 | int32_t ServletDispatch::handle(HttpRequest::ptr request, HttpResponse::ptr response, HttpSession::ptr session) { 41 | auto slt = getMatchedServlet(request->getPath()); 42 | if(slt) { 43 | slt->handle(request, response, session); 44 | } 45 | return 0; 46 | } 47 | 48 | void ServletDispatch::addServlet(const std::string &uri, Servlet::ptr slt) { 49 | std::unique_lock lock(m_mutex.Writer()); 50 | m_datas[uri] = slt; 51 | } 52 | 53 | void ServletDispatch::addServlet(const std::string &uri, FunctionServlet::callback cb) { 54 | std::unique_lock lock(m_mutex.Writer()); 55 | m_datas[uri] = FunctionServlet::ptr(new FunctionServlet(cb)); 56 | } 57 | 58 | void ServletDispatch::addGlobServlet(const std::string &uri, Servlet::ptr slt) { 59 | std::unique_lock lock(m_mutex.Writer()); 60 | m_globs[uri] = slt; 61 | } 62 | 63 | void ServletDispatch::addGlobServlet(const std::string &uri, FunctionServlet::callback cb) { 64 | std::unique_lock lock(m_mutex.Writer()); 65 | m_globs[uri] = FunctionServlet::ptr(new FunctionServlet(cb)); 66 | } 67 | 68 | void ServletDispatch::delServlet(const std::string &uri) { 69 | std::unique_lock lock(m_mutex.Writer()); 70 | m_datas.erase(uri); 71 | } 72 | 73 | void ServletDispatch::delGlobServlet(const std::string &uri) { 74 | std::unique_lock lock(m_mutex.Writer()); 75 | m_globs.erase(uri); 76 | } 77 | 78 | Servlet::ptr ServletDispatch::getServlet(const std::string &uri) { 79 | std::unique_lock lock(m_mutex.Reader()); 80 | auto it = m_datas.find(uri); 81 | if(it == m_datas.end()) { 82 | return nullptr; 83 | } 84 | return it->second; 85 | } 86 | 87 | Servlet::ptr ServletDispatch::getGlobServlet(const std::string &uri) { 88 | std::unique_lock lock(m_mutex.Reader()); 89 | auto it = m_globs.find(uri); 90 | if(it == m_globs.end()) { 91 | return nullptr; 92 | } 93 | return it->second; 94 | } 95 | 96 | Servlet::ptr ServletDispatch::getMatchedServlet(const std::string &uri) { 97 | std::unique_lock lock(m_mutex.Reader()); 98 | auto mit = m_datas.find(uri); 99 | if(mit != m_datas.end()) { 100 | return mit->second; 101 | } 102 | for(auto it = m_globs.begin(); 103 | it != m_globs.end(); ++it) { 104 | if(!fnmatch(it->first.c_str(), uri.c_str(), 0)) { 105 | return it->second; 106 | } 107 | } 108 | return m_default; 109 | } 110 | 111 | 112 | } -------------------------------------------------------------------------------- /acid/http/servlet.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2021/12/17. 3 | // 4 | 5 | #ifndef ACID_SERVLET_H 6 | #define ACID_SERVLET_H 7 | #include 8 | #include 9 | #include 10 | #include "http.h" 11 | #include "http_session.h" 12 | 13 | namespace acid::http { 14 | class Servlet { 15 | public: 16 | using ptr = std::shared_ptr; 17 | Servlet(const std::string& name) : m_name(name) {}; 18 | virtual ~Servlet() {}; 19 | virtual int32_t handle(HttpRequest::ptr request, HttpResponse::ptr response, HttpSession::ptr session) = 0; 20 | const std::string& getName() const { return m_name;} 21 | private: 22 | std::string m_name; 23 | }; 24 | 25 | 26 | class FunctionServlet : public Servlet{ 27 | public: 28 | using ptr = std::shared_ptr; 29 | using callback = std::function; 31 | FunctionServlet(callback cb) : Servlet("FunctionServlet") , m_cb(cb) {}; 32 | int32_t handle(HttpRequest::ptr request, HttpResponse::ptr response, HttpSession::ptr session) override; 33 | private: 34 | callback m_cb; 35 | }; 36 | 37 | class ServletDispatch : public Servlet{ 38 | public: 39 | using ptr = std::shared_ptr; 40 | using RWMutexType = co::co_rwmutex; 41 | ServletDispatch(); 42 | int32_t handle(HttpRequest::ptr request, HttpResponse::ptr response, HttpSession::ptr session) override; 43 | 44 | void addServlet(const std::string& uri, Servlet::ptr slt); 45 | void addServlet(const std::string& uri, FunctionServlet::callback cb); 46 | 47 | void addGlobServlet(const std::string& uri, Servlet::ptr slt); 48 | void addGlobServlet(const std::string& uri, FunctionServlet::callback cb); 49 | 50 | void delServlet(const std::string& uri); 51 | void delGlobServlet(const std::string& uri); 52 | 53 | Servlet::ptr getServlet(const std::string& uri); 54 | Servlet::ptr getGlobServlet(const std::string& uri); 55 | 56 | Servlet::ptr getMatchedServlet(const std::string& uri); 57 | 58 | Servlet::ptr getDefault() const { return m_default;} 59 | void setDefault(Servlet::ptr def) { m_default = def;} 60 | private: 61 | RWMutexType m_mutex; 62 | std::map m_datas; 63 | std::map m_globs; 64 | Servlet::ptr m_default; 65 | }; 66 | 67 | 68 | class NotFoundServlet : public Servlet { 69 | public: 70 | using ptr = std::shared_ptr; 71 | NotFoundServlet(const std::string& name) ; 72 | int32_t handle(HttpRequest::ptr request, HttpResponse::ptr response, HttpSession::ptr session) override; 73 | 74 | private: 75 | std::string m_name; 76 | std::string m_content; 77 | }; 78 | 79 | } 80 | 81 | #endif //ACID_SERVLET_H 82 | -------------------------------------------------------------------------------- /acid/http/servlets/file_servlet.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2021/12/27. 3 | // 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "file_servlet.h" 10 | 11 | namespace acid::http { 12 | FileServlet::FileServlet(const std::string &path) 13 | : Servlet("FileServlet"), m_path(path) { 14 | 15 | } 16 | 17 | int32_t FileServlet::handle(HttpRequest::ptr request, HttpResponse::ptr response, HttpSession::ptr session) { 18 | if (request->getPath().find("..") != std::string::npos) { 19 | NotFoundServlet("acid").handle(request, response, session); 20 | return 1; 21 | } 22 | std::string filename = m_path + request->getPath(); 23 | 24 | struct stat filestat; 25 | if(stat(filename.c_str(), &filestat) < 0){ 26 | NotFoundServlet("acid").handle(request, response, session); 27 | return 1; 28 | } 29 | 30 | if(!S_ISREG(filestat.st_mode) || !(S_IRUSR & filestat.st_mode)){ 31 | NotFoundServlet("acid").handle(request, response, session); 32 | return 1; 33 | } 34 | 35 | response->setStatus(http::HttpStatus::OK); 36 | //response->setHeader("Content-Type","text/plain"); 37 | response->setHeader("Server", "acid/1.0.0"); 38 | response->setHeader("Content-length",std::to_string(filestat.st_size)); 39 | 40 | session->sendResponse(response); 41 | 42 | int filefd = open(filename.c_str(), O_RDONLY); 43 | sendfile(session->getSocket()->getSocket(), filefd, 0, filestat.st_size); 44 | 45 | return 1; 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /acid/http/servlets/file_servlet.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2021/12/27. 3 | // 4 | 5 | #ifndef ACID_FILE_SERVLET_H 6 | #define ACID_FILE_SERVLET_H 7 | #include "../servlet.h" 8 | namespace acid::http { 9 | class FileServlet : public Servlet { 10 | public: 11 | using ptr = std::shared_ptr; 12 | FileServlet(const std::string &path); 13 | int32_t handle(HttpRequest::ptr request, HttpResponse::ptr response, HttpSession::ptr session) override; 14 | private: 15 | std::string m_path; 16 | }; 17 | 18 | } 19 | #endif //ACID_FILE_SERVLET_H 20 | -------------------------------------------------------------------------------- /acid/http/servlets/kvstore_servlet.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2022/12/6. 3 | // 4 | 5 | #include "kvstore_servlet.h" 6 | #include "../../kvraft/kvserver.h" 7 | 8 | namespace acid::http { 9 | namespace { 10 | struct Params { 11 | Params(const nlohmann::json& json) { 12 | auto it = json.find("command"); 13 | if (it == json.end()) { 14 | return; 15 | } 16 | command = it->get(); 17 | 18 | it = json.find("key"); 19 | if (it == json.end()) { 20 | return; 21 | } 22 | key = it->get(); 23 | 24 | it = json.find("value"); 25 | if (it == json.end()) { 26 | return; 27 | } 28 | value = it->get(); 29 | } 30 | std::optional command; 31 | std::optional key; 32 | std::optional value; 33 | }; 34 | } 35 | KVStoreServlet::KVStoreServlet(std::shared_ptr store) 36 | : Servlet("KVStoreServlet"), m_store(std::move(store)) { 37 | if (!m_store) { 38 | exit(EXIT_FAILURE); 39 | } 40 | } 41 | 42 | int32_t KVStoreServlet::handle(HttpRequest::ptr request, HttpResponse::ptr response, HttpSession::ptr session) { 43 | nlohmann::json req = request->getJson(); 44 | nlohmann::json resp; 45 | co_defer_scope { 46 | response->setJson(resp); 47 | }; 48 | 49 | // 允许跨域请求 50 | response->setHeader("Access-Control-Allow-Origin", "*"); 51 | response->setHeader("Access-Control-Allow-Methods", "*"); 52 | response->setHeader("Access-Control-Max-Age", "3600"); 53 | response->setHeader("Access-Control-Allow-Headers", "*"); 54 | 55 | Params params(req); 56 | 57 | if (!params.command) { 58 | resp["msg"] = "The request is missing a command"; 59 | return 0; 60 | } 61 | 62 | const std::string& command = *params.command; 63 | 64 | if (command == "dump") { 65 | const auto& data = m_store->getData(); 66 | resp["data"] = data; 67 | resp["msg"] = "OK"; 68 | return 0; 69 | } else if (command == "clear") { 70 | kvraft::CommandResponse commandResponse = m_store->Clear(); 71 | resp["msg"] = kvraft::toString(commandResponse.error); 72 | return 0; 73 | } 74 | 75 | if (!params.key) { 76 | resp["msg"] = "The request is missing a key"; 77 | return 0; 78 | } 79 | const std::string& key = *params.key; 80 | kvraft::CommandResponse commandResponse; 81 | if (command == "get") { 82 | commandResponse = m_store->Get(key); 83 | resp["msg"] = kvraft::toString(commandResponse.error); 84 | resp["value"] = commandResponse.value; 85 | } else if (command == "delete") { 86 | commandResponse = m_store->Delete(key); 87 | resp["msg"] = kvraft::toString(commandResponse.error); 88 | } else if (command == "put") { 89 | if (!params.value) { 90 | resp["msg"] = "The request is missing a value"; 91 | return 0; 92 | } 93 | const std::string& value = *params.value; 94 | commandResponse = m_store->Put(key, value); 95 | resp["msg"] = kvraft::toString(commandResponse.error); 96 | } else if (command == "append") { 97 | if (!params.value) { 98 | resp["msg"] = "The request is missing a value"; 99 | return 0; 100 | } 101 | const std::string& value = *params.value; 102 | commandResponse = m_store->Append(key, value); 103 | resp["msg"] = kvraft::toString(commandResponse.error); 104 | } else { 105 | resp["msg"] = "command not allowed"; 106 | } 107 | return 0; 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /acid/http/servlets/kvstore_servlet.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2022/12/6. 3 | // 4 | 5 | #ifndef ACID_KVSTORE_SERVLET_H 6 | #define ACID_KVSTORE_SERVLET_H 7 | 8 | #include "../servlet.h" 9 | 10 | namespace acid::kvraft { 11 | class KVServer; 12 | } 13 | namespace acid::http { 14 | class KVStoreServlet : public Servlet { 15 | public: 16 | using ptr = std::shared_ptr; 17 | KVStoreServlet(std::shared_ptr store); 18 | /** 19 | * request 和 response 都以 json 来交互 20 | * request json 格式: 21 | * { 22 | * "command": "put", 23 | * "key": "name", 24 | * "value": "zavier" 25 | * } 26 | * response json 格式: 27 | * { 28 | * "msg": "OK", 29 | * "value": "" // 如果 request 的 command 是 get 请求返回对应 key 的 value, 否则为空 30 | * "data": { // 如果 request 的 command 是 dump 请求返回数据库的全部键值对 31 | * "key1": "value1", 32 | * "key2": "value2", 33 | * ... 34 | * } 35 | * } 36 | */ 37 | int32_t handle(HttpRequest::ptr request, HttpResponse::ptr response, HttpSession::ptr session) override; 38 | private: 39 | std::shared_ptr m_store; 40 | }; 41 | } 42 | #endif //ACID_KVSTORE_SERVLET_H 43 | -------------------------------------------------------------------------------- /acid/kvraft/commom.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2022/12/3. 3 | // 4 | 5 | #ifndef ACID_COMMOM_H 6 | #define ACID_COMMOM_H 7 | 8 | #include 9 | #include 10 | #include 11 | #include "../rpc/serializer.h" 12 | 13 | namespace acid::kvraft { 14 | using namespace acid::rpc; 15 | 16 | inline const std::string COMMAND = "KVServer::handleCommand"; 17 | 18 | inline const std::string KEYEVENTS_PUT = "put"; 19 | inline const std::string KEYEVENTS_DEL = "del"; 20 | inline const std::string KEYEVENTS_APPEND = "append"; 21 | inline const std::string TOPIC_KEYEVENT = "__keyevent__:"; 22 | inline const std::string TOPIC_KEYSPACE = "__keyspace__:"; 23 | // 所有 key 的事件 24 | inline const std::string TOPIC_ALL_KEYEVENTS = "__keyevent__:*"; 25 | // 所有 key 的 put 事件 26 | inline const std::string TOPIC_KEYEVENTS_PUT = TOPIC_KEYEVENT + KEYEVENTS_PUT; 27 | // 所有 key 的 del 事件 28 | inline const std::string TOPIC_KEYEVENTS_DEL = TOPIC_KEYEVENT + KEYEVENTS_DEL; 29 | // 所有 key 的 append 事件 30 | inline const std::string TOPIC_KEYEVENTS_APPEND = TOPIC_KEYEVENT + KEYEVENTS_APPEND; 31 | 32 | enum Error { 33 | OK, 34 | NO_KEY, 35 | WRONG_LEADER, 36 | TIMEOUT, 37 | CLOSED, 38 | }; 39 | 40 | inline std::string toString(Error err) { 41 | std::string str; 42 | switch (err) { 43 | case OK: str = "OK"; break; 44 | case NO_KEY: str = "No Key"; break; 45 | case WRONG_LEADER: str = "Wrong Leader"; break; 46 | case TIMEOUT: str = "Timeout"; break; 47 | case CLOSED: str = "Closed"; break; 48 | default: str = "Unexpect Error"; 49 | } 50 | return str; 51 | } 52 | 53 | enum Operation { 54 | GET, 55 | PUT, 56 | APPEND, 57 | DELETE, 58 | CLEAR, 59 | }; 60 | 61 | inline std::string toString(Operation op) { 62 | std::string str; 63 | switch (op) { 64 | case GET: str = "GET"; break; 65 | case PUT: str = "PUT"; break; 66 | case APPEND: str = "APPEND"; break; 67 | case DELETE: str = "DELETE"; break; 68 | case CLEAR: str = "CLEAR"; break; 69 | default: str = "Unexpect Operation"; 70 | } 71 | return str; 72 | } 73 | 74 | struct CommandRequest { 75 | Operation operation; 76 | std::string key; 77 | std::string value; 78 | int64_t clientId; 79 | int64_t commandId; 80 | std::string toString() const { 81 | std::string str = fmt::format("operation: {}, key: {}, value: {}, clientId: {}, commandId: {}", 82 | kvraft::toString(operation), key, value, clientId, commandId); 83 | return "{" + str + "}"; 84 | } 85 | }; 86 | 87 | struct CommandResponse { 88 | Error error = OK; 89 | std::string value; 90 | int64_t leaderId = -1; 91 | std::string toString() const { 92 | std::string str = fmt::format("error: {}, value: {}, leaderId: {}", kvraft::toString(error), value, leaderId); 93 | return "{" + str + "}"; 94 | } 95 | }; 96 | 97 | } 98 | #endif //ACID_COMMOM_H 99 | -------------------------------------------------------------------------------- /acid/kvraft/kvclient.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2022/12/4. 3 | // 4 | 5 | #ifndef ACID_KVCLIENT_H 6 | #define ACID_KVCLIENT_H 7 | 8 | #include 9 | #include "commom.h" 10 | #include "../rpc/rpc_client.h" 11 | 12 | namespace acid::kvraft { 13 | using namespace acid::rpc; 14 | class KVClient : public RpcClient { 15 | public: 16 | using ptr = std::shared_ptr; 17 | using MutexType = co::co_mutex; 18 | KVClient(std::map& servers); 19 | ~KVClient(); 20 | kvraft::Error Get(const std::string& key, std::string& value); 21 | kvraft::Error Put(const std::string& key, const std::string& value); 22 | kvraft::Error Append(const std::string& key, const std::string& value); 23 | kvraft::Error Delete(const std::string& key); 24 | kvraft::Error Clear(); 25 | 26 | /** 27 | * 订阅频道,会阻塞 \n 28 | * @tparam Args std::string ... 29 | * @tparam Listener 用于监听订阅事件 30 | * @param channels 订阅的频道列表 31 | * @return 当成功取消所有的订阅后返回,否则一直阻塞 32 | */ 33 | template 34 | void subscribe(PubsubListener::ptr listener, const Args& ... channels) { 35 | static_assert(sizeof...(channels)); 36 | subscribe(std::move(listener), {channels...}); 37 | } 38 | 39 | void subscribe(PubsubListener::ptr listener, const std::vector& channels); 40 | 41 | /** 42 | * 取消订阅频道 43 | * @param channel 频道 44 | */ 45 | void unsubscribe(const std::string& channel); 46 | 47 | /** 48 | * 模式订阅,阻塞 \n 49 | * 匹配规则:\n 50 | * '?': 匹配 0 或 1 个字符 \n 51 | * '*': 匹配 0 或更多个字符 \n 52 | * '+': 匹配 1 或更多个字符 \n 53 | * '@': 如果任何模式恰好出现一次 \n 54 | * '!': 如果任何模式都不匹配 \n 55 | * @tparam Args std::string ... 56 | * @param listener 用于监听订阅事件 57 | * @param patterns 订阅的模式列表 58 | * @return 当成功取消所有的订阅后返回,否则一直阻塞 59 | */ 60 | template 61 | void patternSubscribe(PubsubListener::ptr listener, const Args& ... patterns) { 62 | static_assert(sizeof...(patterns)); 63 | patternSubscribe(std::move(listener), {patterns...}); 64 | } 65 | 66 | void patternSubscribe(PubsubListener::ptr listener, const std::vector& patterns); 67 | 68 | /** 69 | * 取消模式订阅频道 70 | * @param pattern 模式 71 | */ 72 | void patternUnsubscribe(const std::string& pattern); 73 | private: 74 | CommandResponse Command(CommandRequest& request); 75 | bool connect(); 76 | int64_t nextLeaderId(); 77 | static int64_t GetRandom(); 78 | private: 79 | std::unordered_map m_servers; 80 | int64_t m_clientId; 81 | int64_t m_leaderId; 82 | int64_t m_commandId; 83 | std::atomic_bool m_stop; 84 | co::co_chan m_stopChan; 85 | std::vector m_subs; 86 | MutexType m_pubsub_mutex; 87 | CycleTimerTocken m_heart; 88 | }; 89 | } 90 | 91 | 92 | #endif //ACID_KVCLIENT_H 93 | -------------------------------------------------------------------------------- /acid/kvraft/kvserver.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2022/12/3. 3 | // 4 | 5 | #ifndef ACID_KVSERVER_H 6 | #define ACID_KVSERVER_H 7 | 8 | #include "../raft/raft_node.h" 9 | #include "commom.h" 10 | 11 | namespace acid::kvraft { 12 | using namespace acid::raft; 13 | class KVServer { 14 | public: 15 | using ptr = std::shared_ptr; 16 | using MutexType = co::co_mutex; 17 | using KVMap = std::map; 18 | 19 | KVServer(std::map& servers, int64_t id, Persister::ptr persister, int64_t maxRaftState = 1000); 20 | ~KVServer(); 21 | void start(); 22 | void stop(); 23 | CommandResponse handleCommand(CommandRequest request); 24 | CommandResponse Get(const std::string& key); 25 | CommandResponse Put(const std::string& key, const std::string& value); 26 | CommandResponse Append(const std::string& key, const std::string& value); 27 | CommandResponse Delete(const std::string& key); 28 | CommandResponse Clear(); 29 | [[nodiscard]] 30 | const KVMap& getData() const { return m_data;} 31 | private: 32 | void applier(); 33 | void saveSnapshot(int64_t index); 34 | void readSnapshot(Snapshot::ptr snap); 35 | bool isDuplicateRequest(int64_t client, int64_t command); 36 | bool needSnapshot(); 37 | CommandResponse applyLogToStateMachine(const CommandRequest& request); 38 | private: 39 | MutexType m_mutex; 40 | int64_t m_id; 41 | co::co_chan m_applychan; 42 | 43 | KVMap m_data; 44 | Persister::ptr m_persister; 45 | std::unique_ptr m_raft; 46 | 47 | std::map> m_lastOperation; 48 | std::map> m_nofiyChans; 49 | 50 | int64_t m_lastApplied = 0; 51 | int64_t m_maxRaftState = -1; 52 | }; 53 | } 54 | #endif //ACID_KVSERVER_H 55 | -------------------------------------------------------------------------------- /acid/net/socket.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2021/12/6. 3 | // 4 | 5 | #ifndef ACID_SOCKET_H 6 | #define ACID_SOCKET_H 7 | #include 8 | #include "address.h" 9 | #include "acid/common/byte_array.h" 10 | namespace acid{ 11 | class Socket : public std::enable_shared_from_this { 12 | public: 13 | using ptr = std::shared_ptr; 14 | using weak_ptr = std::weak_ptr; 15 | 16 | enum Type { 17 | TCP = SOCK_STREAM, 18 | UDP = SOCK_DGRAM 19 | }; 20 | 21 | enum Family { 22 | IPv4 = AF_INET, 23 | IPv6 = AF_INET6, 24 | UNIX = AF_UNIX 25 | }; 26 | 27 | static Socket::ptr CreateTCP(Address::ptr address); 28 | static Socket::ptr CreateUDP(Address::ptr address); 29 | 30 | static Socket::ptr CreateTCPSocket(); 31 | static Socket::ptr CreateUDPSocket(); 32 | 33 | static Socket::ptr CreateTCPSocket6(); 34 | static Socket::ptr CreateUDPSocket6(); 35 | 36 | static Socket::ptr CreateUnixTCPSocket(); 37 | static Socket::ptr CreateUnixUDPSocket(); 38 | 39 | Socket(int family, int type, int protocol); 40 | virtual ~Socket(); 41 | 42 | void setSendTimeout(uint64_t t); 43 | uint64_t getSendTimeout(); 44 | 45 | void setRecvTimeout(uint64_t t); 46 | uint64_t getRecvTimeout(); 47 | 48 | bool getOption(int level, int option, void* result, size_t* len); 49 | 50 | template 51 | bool getOption(int level, int option, T* result) { 52 | size_t len = sizeof(T); 53 | return getOption(level, option, result, &len); 54 | } 55 | 56 | bool setOption(int level, int option, void* result, size_t len); 57 | 58 | template 59 | bool setOption(int level, int option, T* result) { 60 | return setOption(level, option, result, sizeof(T)); 61 | } 62 | 63 | Socket::ptr accept(); 64 | bool bind(const Address::ptr address); 65 | bool connect(const Address::ptr address, uint64_t timeout_ms = -1); 66 | bool listen(int backlog = SOMAXCONN); 67 | bool close(); 68 | 69 | /** 70 | * @brief 发送数据 71 | * @param[in] buffer 待发送数据的内存 72 | * @param[in] length 待发送数据的长度 73 | * @param[in] flags 标志字 74 | * @return 75 | * @retval >0 发送成功对应大小的数据 76 | * @retval =0 socket被关闭 77 | * @retval <0 socket出错 78 | */ 79 | virtual ssize_t send(const void* buffer, size_t length, int flags = 0); 80 | 81 | /** 82 | * @brief 发送数据 83 | * @param[in] buffers 待发送数据的内存(iovec数组) 84 | * @param[in] length 待发送数据的长度(iovec长度) 85 | * @param[in] flags 标志字 86 | * @return 87 | * @retval >0 发送成功对应大小的数据 88 | * @retval =0 socket被关闭 89 | * @retval <0 socket出错 90 | */ 91 | virtual ssize_t send(const iovec* buffers, size_t length, int flags = 0); 92 | 93 | /** 94 | * @brief 发送数据 95 | * @param[in] buffer 待发送数据的内存 96 | * @param[in] length 待发送数据的长度 97 | * @param[in] to 发送的目标地址 98 | * @param[in] flags 标志字 99 | * @return 100 | * @retval >0 发送成功对应大小的数据 101 | * @retval =0 socket被关闭 102 | * @retval <0 socket出错 103 | */ 104 | virtual ssize_t sendTo(const void* buffer, size_t length, const Address::ptr to, int flags = 0); 105 | 106 | /** 107 | * @brief 发送数据 108 | * @param[in] buffers 待发送数据的内存(iovec数组) 109 | * @param[in] length 待发送数据的长度(iovec长度) 110 | * @param[in] to 发送的目标地址 111 | * @param[in] flags 标志字 112 | * @return 113 | * @retval >0 发送成功对应大小的数据 114 | * @retval =0 socket被关闭 115 | * @retval <0 socket出错 116 | */ 117 | virtual ssize_t sendTo(const iovec* buffers, size_t length, const Address::ptr to, int flags = 0); 118 | 119 | /** 120 | * @brief 接受数据 121 | * @param[out] buffer 接收数据的内存 122 | * @param[in] length 接收数据的内存大小 123 | * @param[in] flags 标志字 124 | * @return 125 | * @retval >0 接收到对应大小的数据 126 | * @retval =0 socket被关闭 127 | * @retval <0 socket出错 128 | */ 129 | virtual ssize_t recv(void* buffer, size_t length, int flags = 0); 130 | 131 | /** 132 | * @brief 接受数据 133 | * @param[out] buffers 接收数据的内存(iovec数组) 134 | * @param[in] length 接收数据的内存大小(iovec数组长度) 135 | * @param[in] flags 标志字 136 | * @return 137 | * @retval >0 接收到对应大小的数据 138 | * @retval =0 socket被关闭 139 | * @retval <0 socket出错 140 | */ 141 | virtual ssize_t recv(iovec* buffers, size_t length, int flags = 0); 142 | 143 | /** 144 | * @brief 接受数据 145 | * @param[out] buffer 接收数据的内存 146 | * @param[in] length 接收数据的内存大小 147 | * @param[out] from 发送端地址 148 | * @param[in] flags 标志字 149 | * @return 150 | * @retval >0 接收到对应大小的数据 151 | * @retval =0 socket被关闭 152 | * @retval <0 socket出错 153 | */ 154 | virtual ssize_t recvFrom(void* buffer, size_t length, Address::ptr from, int flags = 0); 155 | 156 | /** 157 | * @brief 接受数据 158 | * @param[out] buffers 接收数据的内存(iovec数组) 159 | * @param[in] length 接收数据的内存大小(iovec数组长度) 160 | * @param[out] from 发送端地址 161 | * @param[in] flags 标志字 162 | * @return 163 | * @retval >0 接收到对应大小的数据 164 | * @retval =0 socket被关闭 165 | * @retval <0 socket出错 166 | */ 167 | virtual ssize_t recvFrom(iovec* buffers, size_t length, Address::ptr from, int flags = 0); 168 | 169 | Address::ptr getRemoteAddress(); 170 | Address::ptr getLocalAddress(); 171 | 172 | int getFamily() const { return m_family;}; 173 | int getType() const { return m_type;}; 174 | int getProtocol() const { return m_protocol;}; 175 | 176 | bool isConnected() const { return m_isConnected;}; 177 | int getSocket() const { return m_sock;}; 178 | 179 | bool isValid() const { return m_sock != -1;}; 180 | int getError(); 181 | 182 | std::ostream& dump(std::ostream& os) const; 183 | 184 | std::string toString() const; 185 | 186 | bool cancelRead(); 187 | bool cancelWrite(); 188 | bool cancelAccept(); 189 | bool cancelAll(); 190 | 191 | private: 192 | void initSocket(); 193 | void newSocket(); 194 | bool init(int sock); 195 | private: 196 | int m_sock; 197 | int m_family; 198 | int m_type; 199 | int m_protocol; 200 | 201 | bool m_isConnected = false; 202 | 203 | Address::ptr m_remoteAddress; 204 | Address::ptr m_localAddress; 205 | 206 | }; 207 | 208 | } 209 | #endif //ACID_SOCKET_H 210 | -------------------------------------------------------------------------------- /acid/net/socket_stream.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2021/12/15. 3 | // 4 | 5 | #include "socket_stream.h" 6 | 7 | namespace acid { 8 | 9 | SocketStream::SocketStream(Socket::ptr socket, bool owner) 10 | : m_socket(socket) 11 | , m_isOwner(owner){ 12 | 13 | } 14 | 15 | SocketStream::~SocketStream() { 16 | if(m_socket && m_isOwner) { 17 | m_socket->close(); 18 | } 19 | } 20 | 21 | ssize_t SocketStream::read(void *buffer, size_t length) { 22 | if (!isConnected()) { 23 | return -1; 24 | } 25 | return m_socket->recv(buffer, length); 26 | } 27 | 28 | ssize_t SocketStream::read(ByteArray::ptr buffer, size_t length) { 29 | if (!isConnected()) { 30 | return -1; 31 | } 32 | std::vector iovs; 33 | buffer->getWriteBuffers(iovs, length); 34 | ssize_t n =m_socket->recv(&iovs[0], iovs.size()); 35 | if(n > 0) { 36 | buffer->setPosition(buffer->getPosition() + n); 37 | } 38 | return n; 39 | } 40 | 41 | ssize_t SocketStream::write(const void *buffer, size_t length) { 42 | if (!isConnected()) { 43 | return -1; 44 | } 45 | return m_socket->send(buffer, length); 46 | } 47 | 48 | ssize_t SocketStream::write(ByteArray::ptr buffer, size_t length) { 49 | if (!isConnected()) { 50 | return -1; 51 | } 52 | std::vector iovs; 53 | buffer->getReadBuffers(iovs, length); 54 | ssize_t n = m_socket->send(&iovs[0], iovs.size()); 55 | if(n > 0) { 56 | buffer->setPosition(buffer->getPosition() + n); 57 | } 58 | return n; 59 | } 60 | 61 | void SocketStream::close() { 62 | if (m_socket) { 63 | m_socket->close(); 64 | } 65 | } 66 | 67 | 68 | } -------------------------------------------------------------------------------- /acid/net/socket_stream.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2021/12/15. 3 | // 4 | 5 | #ifndef ACID_SOCKET_STREAM_H 6 | #define ACID_SOCKET_STREAM_H 7 | #include 8 | #include "socket.h" 9 | #include "acid/common/stream.h" 10 | namespace acid { 11 | class SocketStream : public Stream { 12 | public: 13 | using ptr = std::shared_ptr; 14 | SocketStream(Socket::ptr socket, bool owner = true); 15 | ~SocketStream(); 16 | 17 | bool isConnected() const { return m_socket && m_socket->isConnected();} 18 | Socket::ptr getSocket() { return m_socket;} 19 | 20 | ssize_t read(void* buffer, size_t length) override; 21 | ssize_t read(ByteArray::ptr buffer, size_t length) override; 22 | 23 | ssize_t write(const void* buffer, size_t length) override; 24 | ssize_t write(ByteArray::ptr buffer, size_t length) override; 25 | 26 | void close() override; 27 | 28 | protected: 29 | Socket::ptr m_socket; 30 | bool m_isOwner; 31 | }; 32 | 33 | } 34 | #endif //ACID_SOCKET_STREAM_H 35 | -------------------------------------------------------------------------------- /acid/net/tcp_server.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2021/12/14. 3 | // 4 | #include "acid/common/config.h" 5 | #include "tcp_server.h" 6 | 7 | namespace acid { 8 | static auto g_logger = GetLogInstance(); 9 | 10 | static ConfigVar::ptr g_tcp_server_recv_timeout = 11 | Config::Lookup("tcp_server.recv_timeout", 12 | (uint64_t)(60 * 1000 * 2), "tcp server recv timeout"); 13 | 14 | TcpServer::TcpServer() 15 | : m_timer() 16 | , m_recvTimeout(g_tcp_server_recv_timeout->getValue()) 17 | , m_name("acid/1.0.0") 18 | , m_stop(true) { 19 | } 20 | 21 | TcpServer::~TcpServer() { 22 | stop(); 23 | } 24 | 25 | bool TcpServer::bind(Address::ptr addr) { 26 | std::vector addrs, fail; 27 | addrs.push_back(addr); 28 | return bind(addrs, fail); 29 | } 30 | 31 | bool TcpServer::bind(const std::vector &addrs, std::vector &fail) { 32 | for(Address::ptr addr : addrs){ 33 | Socket::ptr sock = Socket::CreateTCP(addr); 34 | if(!sock->bind(addr)) { 35 | SPDLOG_LOGGER_ERROR(g_logger, "bind fail errno={} errstr={} addr={}", errno, strerror(errno), addr->toString()); 36 | fail.push_back(addr); 37 | continue; 38 | } 39 | if(!sock->listen()) { 40 | SPDLOG_LOGGER_ERROR(g_logger, "listen fail errno={} errstr={} addr={}", errno, strerror(errno), addr->toString()); 41 | fail.push_back(addr); 42 | continue; 43 | } 44 | m_listens.push_back(sock); 45 | } 46 | if(!fail.empty()) { 47 | m_listens.clear(); 48 | return false; 49 | } 50 | for(auto& sock : m_listens) { 51 | SPDLOG_LOGGER_INFO(g_logger, "server {} bind {} success", m_name, sock->toString()); 52 | } 53 | 54 | return true; 55 | } 56 | 57 | void TcpServer::start() { 58 | if(!isStop()) { 59 | return; 60 | } 61 | m_stop = false; 62 | m_stopCh = co::co_chan(); 63 | for(auto& sock : m_listens) { 64 | go [sock, this] { 65 | this->startAccept(sock); 66 | }; 67 | } 68 | // 阻塞等待 69 | m_stopCh >> nullptr; 70 | } 71 | 72 | void TcpServer::stop() { 73 | if(isStop()) { 74 | return; 75 | } 76 | m_stop = true; 77 | for(auto& sock : m_listens) { 78 | sock->close(); 79 | } 80 | m_stopCh.Close(); 81 | } 82 | 83 | void TcpServer::startAccept(Socket::ptr sock) { 84 | while(!isStop()) { 85 | Socket::ptr client = sock->accept(); 86 | if(client) { 87 | client->setRecvTimeout(m_recvTimeout); 88 | go [client, this] { 89 | this->handleClient(client); 90 | }; 91 | } else { 92 | if (!sock->isConnected()) { 93 | return; 94 | } 95 | SPDLOG_LOGGER_ERROR(g_logger, "accept fail, errno={} errstr={}", errno, strerror(errno)); 96 | } 97 | } 98 | } 99 | 100 | void TcpServer::handleClient(Socket::ptr client) { 101 | SPDLOG_LOGGER_INFO(g_logger,"handleClient: {}", client->toString()); 102 | } 103 | 104 | } -------------------------------------------------------------------------------- /acid/net/tcp_server.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2021/12/14. 3 | // 4 | 5 | #ifndef ACID_TCP_SERVER_H 6 | #define ACID_TCP_SERVER_H 7 | 8 | #include 9 | #include 10 | #include "socket.h" 11 | 12 | namespace acid { 13 | 14 | class TcpServer { 15 | public: 16 | TcpServer(); 17 | virtual ~TcpServer(); 18 | 19 | virtual bool bind(Address::ptr addr); 20 | virtual bool bind(const std::vector& addrs, std::vector& fail); 21 | 22 | // 阻塞线程 23 | virtual void start(); 24 | virtual void stop(); 25 | 26 | uint64_t getRecvTimeout() const { return m_recvTimeout;} 27 | void setRecvTimeout(uint64_t timeout) { m_recvTimeout = timeout;} 28 | 29 | std::string getName() const { return m_name;} 30 | virtual void setName(const std::string& name) { m_name = name;} 31 | 32 | bool isStop() const { return m_stop;} 33 | 34 | protected: 35 | virtual void startAccept(Socket::ptr sock); 36 | virtual void handleClient(Socket::ptr client); 37 | protected: 38 | co_timer m_timer; 39 | private: 40 | /// 监听socket队列 41 | std::vector m_listens; 42 | uint64_t m_recvTimeout; 43 | std::string m_name; 44 | std::atomic_bool m_stop; 45 | co::co_chan m_stopCh; 46 | }; 47 | 48 | } 49 | #endif //ACID_TCP_SERVER_H 50 | -------------------------------------------------------------------------------- /acid/net/uri.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2021/12/20. 3 | // 4 | 5 | #ifndef ACID_URI_H 6 | #define ACID_URI_H 7 | #include 8 | #include 9 | #include 10 | #include "address.h" 11 | 12 | namespace acid { 13 | template 14 | struct Task { 15 | struct promise_type; 16 | using handle = std::coroutine_handle; 17 | struct promise_type { 18 | Task get_return_object() { 19 | return {std::coroutine_handle::from_promise(*this)}; 20 | } 21 | std::suspend_always initial_suspend() { 22 | return {}; 23 | } 24 | std::suspend_always final_suspend() noexcept { 25 | return {}; 26 | } 27 | void return_value(T val) { 28 | m_val = std::move(val); 29 | } 30 | std::suspend_always yield_value(T val) { 31 | m_val = val; 32 | return {}; 33 | } 34 | void unhandled_exception() { 35 | std::terminate(); 36 | } 37 | T m_val; 38 | }; 39 | Task() : m_handle(nullptr) {} 40 | Task(handle h): m_handle(h){} 41 | Task(const Task& rhs) = delete; 42 | Task(Task&& rhs) noexcept 43 | : m_handle(rhs.m_handle) { 44 | rhs.m_handle = nullptr; 45 | } 46 | 47 | Task& operator=(const Task& rhs) = delete; 48 | Task& operator=(Task&& rhs) noexcept { 49 | if (std::addressof(rhs) != this) 50 | { 51 | if (m_handle) 52 | { 53 | m_handle.destroy(); 54 | } 55 | 56 | m_handle = rhs.m_handle; 57 | rhs.m_handle = nullptr; 58 | } 59 | 60 | return *this; 61 | } 62 | T get() { 63 | return m_handle.promise().m_val; 64 | } 65 | void resume() { 66 | m_handle.resume(); 67 | } 68 | bool done() { 69 | return !m_handle || m_handle.done(); 70 | } 71 | void destroy() { 72 | m_handle.destroy(); 73 | m_handle = nullptr; 74 | } 75 | 76 | ~Task() { 77 | if (m_handle) { 78 | m_handle.destroy(); 79 | } 80 | } 81 | private: 82 | handle m_handle = nullptr; 83 | }; 84 | 85 | /** 86 | * @brief URI类,支持大部分协议包括 HTTP,HTTPS,FTP,FILE,磁力链接等等 87 | */ 88 | class Uri{ 89 | public: 90 | using ptr = std::shared_ptr; 91 | Uri(); 92 | 93 | /** 94 | * @brief 创建Uri对象 95 | * @param uri uri字符串 96 | * @return 解析成功返回Uri对象否则返回nullptr 97 | */ 98 | static Uri::ptr Create(const std::string& uri); 99 | 100 | Address::ptr createAddress(); 101 | const std::string& getScheme() const { return m_scheme;} 102 | const std::string& getUserinfo() const { return m_userinfo;} 103 | const std::string& getHost() const { return m_host;} 104 | const std::string& getPath() const; 105 | const std::string& getQuery() const { return m_query;} 106 | const std::string& getFragment() const { return m_fragment;} 107 | uint32_t getPort() const; 108 | 109 | void setScheme(const std::string& scheme) { m_scheme = scheme;} 110 | void setUserinfo(const std::string& userinfo) { m_userinfo = userinfo;} 111 | void setHost(const std::string& host) { m_host = host;} 112 | void setPath(const std::string& path) { m_path = path;} 113 | void setQuery(const std::string& query) { m_query = query;} 114 | void setFragment(const std::string& fragment) { m_fragment = fragment;} 115 | 116 | std::ostream& dump(std::ostream& ostream); 117 | std::string toString(); 118 | 119 | private: 120 | Task parse(); 121 | bool isDefaultPort() const; 122 | private: 123 | std::string m_scheme; 124 | std::string m_userinfo; 125 | std::string m_host; 126 | std::string m_path; 127 | std::string m_query; 128 | std::string m_fragment; 129 | uint32_t m_port; 130 | const char* m_cur; 131 | bool m_error{}; 132 | }; 133 | 134 | } 135 | #endif //ACID_URI_H 136 | -------------------------------------------------------------------------------- /acid/raft/entry.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2022/7/8. 3 | // 4 | 5 | #ifndef ACID_ENTRY_H 6 | #define ACID_ENTRY_H 7 | 8 | #include "../rpc/serializer.h" 9 | 10 | namespace acid::raft { 11 | 12 | /** 13 | * @brief 日志条目,一条日志 14 | */ 15 | struct Entry { 16 | // 日志索引 17 | int64_t index = 0; 18 | // 日志任期 19 | int64_t term = 0; 20 | // 日志内容data是一个二进制类型, 21 | // 使用者负责把业务序列化成二进制数, 22 | // 在apply日志的时候再反序列化执行相应业务操作 23 | std::string data{}; 24 | std::string toString() const { 25 | std::string str = fmt::format("Index: {}, Term: {}, Data: {}", index, term, data); 26 | return "{" + str + "}"; 27 | } 28 | friend rpc::Serializer &operator<<(rpc::Serializer &s, const Entry &entry) { 29 | s << entry.index << entry.term << entry.data; 30 | return s; 31 | } 32 | friend rpc::Serializer &operator>>(rpc::Serializer &s, Entry &entry) { 33 | s >> entry.index >> entry.term >> entry.data; 34 | return s; 35 | } 36 | }; 37 | } 38 | #endif //ACID_ENTRY_H 39 | -------------------------------------------------------------------------------- /acid/raft/persister.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2022/11/28. 3 | // 4 | 5 | #include 6 | #include "persister.h" 7 | 8 | namespace acid::raft { 9 | static auto g_logger = GetLogInstance(); 10 | 11 | Persister::Persister(const std::filesystem::path& path) : m_path(path), m_shotter(path / "snapshot") { 12 | if (m_path.empty()) { 13 | SPDLOG_LOGGER_WARN(g_logger, "Persist path is empty"); 14 | } else if (!std::filesystem::exists(m_path)) { 15 | SPDLOG_LOGGER_WARN(g_logger, "Persist path: {} is not exists, create directory", m_path.string()); 16 | std::filesystem::create_directories(path); 17 | } else if (!std::filesystem::is_directory(m_path)) { 18 | SPDLOG_LOGGER_WARN(g_logger, "Persist path: {} is not a directory", m_path.string()); 19 | } else { 20 | SPDLOG_LOGGER_INFO(g_logger, "Persist path : {}", getFullPathName()); 21 | } 22 | } 23 | 24 | std::optional Persister::loadHardState() { 25 | std::ifstream in(m_path / m_name, std::ios_base::in); 26 | if (!in.is_open()) { 27 | return std::nullopt; 28 | } 29 | std::string str((std::istreambuf_iterator(in)), std::istreambuf_iterator()); 30 | rpc::Serializer s(str); 31 | HardState hs{}; 32 | try { 33 | s >> hs; 34 | } catch (...) { 35 | return std::nullopt; 36 | } 37 | return hs; 38 | } 39 | 40 | std::optional> Persister::loadEntries() { 41 | std::unique_lock lock(m_mutex); 42 | std::ifstream in(m_path / m_name, std::ios_base::in); 43 | if (!in.is_open()) { 44 | return std::nullopt; 45 | } 46 | std::string str((std::istreambuf_iterator(in)), std::istreambuf_iterator()); 47 | rpc::Serializer s(str); 48 | std::vector ents; 49 | try { 50 | HardState hs{}; 51 | s >> hs >> ents; 52 | } catch (...) { 53 | return std::nullopt; 54 | } 55 | return ents; 56 | } 57 | 58 | Snapshot::ptr Persister::loadSnapshot() { 59 | std::unique_lock lock(m_mutex); 60 | return m_shotter.loadSnap(); 61 | } 62 | 63 | int64_t Persister::getRaftStateSize() { 64 | std::unique_lock lock(m_mutex); 65 | std::ifstream in(m_path / m_name, std::ios_base::in); 66 | if (!in.is_open()) { 67 | return -1; 68 | } 69 | in.seekg(0, in.end); 70 | auto fos = in.tellg(); 71 | return fos; 72 | } 73 | 74 | bool Persister::persist(const HardState &hs, const std::vector &ents, const Snapshot::ptr snapshot) { 75 | std::unique_lock lock(m_mutex); 76 | int fd = open((m_path / m_name).c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0600); 77 | if (fd < 0) { 78 | return false; 79 | } 80 | 81 | rpc::Serializer s; 82 | s << hs << ents; 83 | s.reset(); 84 | 85 | std::string data = s.toString(); 86 | if (write(fd, data.c_str(), data.size()) < 0) { 87 | return false; 88 | } 89 | // 持久化,必须马上刷新磁盘 90 | fsync(fd); 91 | close(fd); 92 | if (snapshot) { 93 | return m_shotter.saveSnap(snapshot); 94 | } 95 | return true; 96 | } 97 | 98 | } -------------------------------------------------------------------------------- /acid/raft/persister.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2022/11/28. 3 | // 4 | 5 | #ifndef ACID_PERSISTER_H 6 | #define ACID_PERSISTER_H 7 | 8 | #include 9 | #include 10 | #include "../rpc/serializer.h" 11 | #include "entry.h" 12 | #include "snapshot.h" 13 | 14 | namespace acid::raft { 15 | // raft节点状态的持久化数据 16 | struct HardState { 17 | // 当前任期 18 | int64_t term; 19 | // 任期内给谁投过票 20 | int64_t vote; 21 | // 已经commit的最大index 22 | int64_t commit; 23 | friend rpc::Serializer& operator<<(rpc::Serializer& s, const HardState& hs) { 24 | s << hs.term << hs.vote << hs.commit; 25 | return s; 26 | } 27 | friend rpc::Serializer& operator>>(rpc::Serializer& s, HardState& hs) { 28 | s >> hs.term >> hs.vote >> hs.commit; 29 | return s; 30 | } 31 | }; 32 | /** 33 | * @brief 持久化存储 34 | */ 35 | class Persister { 36 | public: 37 | using ptr = std::shared_ptr; 38 | using MutexType = co::co_mutex; 39 | 40 | explicit Persister(const std::filesystem::path& persist_path = "."); 41 | 42 | ~Persister() = default; 43 | 44 | /** 45 | * @brief 获取持久化的 raft 状态 46 | */ 47 | std::optional loadHardState(); 48 | /** 49 | * @brief 获取持久化的 log 50 | */ 51 | std::optional > loadEntries(); 52 | /** 53 | * @brief 获取快照 54 | */ 55 | Snapshot::ptr loadSnapshot(); 56 | /** 57 | * @brief 获取 raft state 的长度 58 | */ 59 | int64_t getRaftStateSize(); 60 | /** 61 | * @brief 持久化 62 | */ 63 | bool persist(const HardState &hs, const std::vector &ents, const Snapshot::ptr snapshot = nullptr); 64 | /** 65 | * @brief 获取快照路径 66 | */ 67 | std::string getFullPathName() { 68 | return canonical(m_path); 69 | } 70 | private: 71 | MutexType m_mutex; 72 | const std::filesystem::path m_path; 73 | Snapshotter m_shotter; 74 | const std::string m_name = "raft-state"; 75 | }; 76 | } 77 | #endif //ACID_PERSISTER_H 78 | -------------------------------------------------------------------------------- /acid/raft/raft_log.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2022/7/5. 3 | // 4 | 5 | #ifndef ACID_RAFT_LOG_H 6 | #define ACID_RAFT_LOG_H 7 | 8 | 9 | #include 10 | #include "../rpc/serializer.h" 11 | #include "entry.h" 12 | #include "snapshot.h" 13 | #include "persister.h" 14 | 15 | namespace acid::raft { 16 | /** 17 | * @brief raftLog记录着日志,封装了一些和日志有关的操作 18 | */ 19 | class RaftLog { 20 | public: 21 | using ptr = std::shared_ptr; 22 | static constexpr int64_t NO_LIMIT = INT64_MAX; 23 | RaftLog(Persister::ptr persister, int64_t maxNextEntsSize = NO_LIMIT); 24 | /** 25 | * @brief 追加日志,在收到leader追加日志的消息后被调用 26 | */ 27 | int64_t maybeAppend(int64_t prevLogIndex, int64_t prevLogTerm, int64_t committed, 28 | std::vector& entries); 29 | /** 30 | * @brief 追加日志 31 | */ 32 | int64_t append(const std::vector& entries); 33 | /** 34 | * @brief 追加日志 35 | */ 36 | void append(const Entry& ent); 37 | /** 38 | * @brief 从 entries 里找到冲突的日志 39 | * @details 如果没有冲突的日志,返回0; 40 | * 如果没有冲突的日志,entries 包含新的日志,返回第一个新日志的index; 41 | * 一条日志如果有一样的index但是term不一样,认为是冲突的日志; 42 | * 给定条目的index必须不断增加; 43 | */ 44 | int64_t findConflict(const std::vector& entries); 45 | /** 46 | * @brief 找出冲突任期的第一条日志索引 47 | */ 48 | int64_t findConflict(int64_t prevLogIndex, int64_t prevLogTerm); 49 | /** 50 | * @brief 获取[apply,commit]间的所有日志,这个函数用于输出给使用者apply日志 51 | */ 52 | std::vector nextEntries(); 53 | /** 54 | * @brief 判断是否有可应用的日志 55 | */ 56 | bool hasNextEntries(); 57 | /** 58 | * @brief 清除日志 59 | */ 60 | void clearEntries(int64_t lastSnapshotIndex = 0, int64_t lastSnapshotTerm = 0); 61 | /** 62 | * @brief 获取第一个索引 63 | * @note 你可能会问第一个日志索引不应该是0么,但是raft会周期的做快照,快照之前的日志就没用了,所以第一个日志索引不一定是0 64 | */ 65 | int64_t firstIndex(); 66 | /** 67 | * @brief 获取最后一条日志的索引。 68 | */ 69 | int64_t lastIndex(); 70 | /** 71 | * @brief 获取最后一条日志的任期。 72 | */ 73 | int64_t lastTerm(); 74 | /** 75 | * @brief 获取快照之前的一条日志的 index 76 | */ 77 | int64_t lastSnapshotIndex(); 78 | /** 79 | * @brief 获取快照之前的一条日志的 term 80 | */ 81 | int64_t lastSnapshotTerm(); 82 | /** 83 | * @brief 更新 commit 到 commit 84 | */ 85 | void commitTo(int64_t commit); 86 | /** 87 | * @brief 更新 applied 到 index 88 | */ 89 | void appliedTo(int64_t index); 90 | /** 91 | * @brief 获取指定索引日志的term 92 | */ 93 | int64_t term(int64_t index); 94 | /** 95 | * @brief 获取[index,lastIndex()]的所有日志,但是日志总量限制在 m_maxNextEntriesSize 96 | */ 97 | std::vector entries(int64_t index); 98 | /** 99 | * @brief 获取所有日志 100 | */ 101 | std::vector allEntries(); 102 | /** 103 | * @brief 判断给定日志的索引和任期是不是比自己新 104 | * @details Raft 通过比较两份日志中最后一条日志条目的索引值和任期号定义谁的日志比较新。 105 | * 如果两份日志最后的条目的任期号不同,那么任期号大的日志更加新。 106 | * 如果两份日志最后的条目任期号相同,那么日志比较长的那个就更加新。 107 | */ 108 | bool isUpToDate(int64_t index, int64_t term); 109 | /** 110 | * @brief 判断给定日志的索引和任期是否正确 111 | */ 112 | bool matchLog(int64_t index, int64_t term); 113 | /** 114 | * @brief Leader更新 commit index 115 | * @note Raft 永远不会通过计算副本数目的方式去提交一个之前任期内的日志条目。只有领导人当前任期里的日志条目通过计算副本数目可以被提交 116 | */ 117 | bool maybeCommit(int64_t maxIndex, int64_t term); 118 | /** 119 | * @brief 获取[low,high)的所有日志,但是总量限制在maxSize 120 | */ 121 | std::vector slice(int64_t low, int64_t high, int64_t maxSize); 122 | /** 123 | * @brief 检查 index 是否正确, 124 | * @note firstIndex <= low <= high <= lastIndex 125 | */ 126 | void mustCheckOutOfBounds(int64_t low, int64_t high); 127 | /** 128 | * @brief 创建快照并压缩 index 之前的日志 129 | */ 130 | Snapshot::ptr createSnapshot(int64_t index, const std::string &data); 131 | /** 132 | * @brief 压缩日志 133 | */ 134 | bool compact(int64_t compactIndex); 135 | 136 | [[nodiscard]] 137 | int64_t committed() const { return m_committed;} 138 | [[nodiscard]] 139 | int64_t applied() const { return m_applied;} 140 | [[nodiscard]] 141 | std::string toString() const { 142 | std::string str = fmt::format("committed: {}, applied: {}, offset: {}, length: {}", 143 | m_committed, m_applied, m_entries.front().index, m_entries.size()); 144 | return str; 145 | } 146 | 147 | private: 148 | // 日志 149 | std::vector m_entries; 150 | // commit 151 | int64_t m_committed; 152 | // 已经被应用到状态机的最高的日志条目的索引(初始值为0,单调递增) 153 | // 前文就提到了apply这个词,日志的有序存储并不是目的,日志的有序apply才是目的,已经提交的日志 154 | // 就要被使用者apply,apply就是把日志数据反序列化后在raft状态机上执行。applied 就是该节点已 155 | // 经被apply的最大索引。apply索引是节点状态(非集群状态),这取决于每个节点的apply速度。 156 | int64_t m_applied; 157 | // raftLog有一个成员函数nextEntries(),用于获取 (applied,committed] 的所有日志,很容易看出来 158 | // 这个函数是apply日志时调用的,maxNextEntriesSize就是用来限制获取日志大小总量的,避免一次调用 159 | // 产生过大粒度的apply操作。 160 | int64_t m_maxNextEntriesSize; 161 | }; 162 | 163 | } 164 | #endif //ACID_RAFT_LOG_H 165 | -------------------------------------------------------------------------------- /acid/raft/raft_node.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2022/6/13. 3 | // 4 | 5 | #ifndef ACID_RAFT_NODE_H 6 | #define ACID_RAFT_NODE_H 7 | 8 | #include 9 | #include "../rpc/rpc_server.h" 10 | #include "raft_log.h" 11 | #include "raft_peer.h" 12 | #include "snapshot.h" 13 | 14 | namespace acid::raft { 15 | using namespace acid::rpc; 16 | /** 17 | * @brief Raft 的状态 18 | */ 19 | enum RaftState { 20 | Follower, // 追随者 21 | Candidate, // 候选人 22 | Leader // 领导者 23 | }; 24 | 25 | struct ApplyMsg { 26 | enum MsgType { 27 | ENTRY, // 日志 28 | SNAPSHOT, // 快照 29 | }; 30 | ApplyMsg() = default; 31 | explicit ApplyMsg(const Entry& ent) : type(ENTRY), data(ent.data), index(ent.index), term(ent.term) {} 32 | explicit ApplyMsg(const Snapshot& snap) : type(SNAPSHOT), data(snap.data), index(snap.metadata.index), term(snap.metadata.term) {} 33 | MsgType type = ENTRY; 34 | std::string data{}; 35 | int64_t index{}; 36 | int64_t term{}; 37 | 38 | template 39 | T as() { 40 | T t; 41 | Serializer s(data); 42 | s >> t; 43 | return t; 44 | } 45 | 46 | std::string toString() const { 47 | std::string str; 48 | std::string t; 49 | switch (type) { 50 | case ENTRY: t = "Entry"; break; 51 | case SNAPSHOT: t = "Snapshot"; break; 52 | default: t = "Unexpect"; 53 | } 54 | str = fmt::format("type: {}, index: {}, term: {}, data size: {}", t, index, term, data.size()); 55 | return str; 56 | } 57 | }; 58 | 59 | /** 60 | * @brief Raft 节点,处理 rpc 请求,并改变状态,通过 RaftPeer 调用远端 Raft 节点 61 | */ 62 | class RaftNode : public acid::rpc::RpcServer { 63 | public: 64 | using ptr = std::shared_ptr; 65 | using MutexType = co::co_mutex; 66 | 67 | /** 68 | * Create a Raft server 69 | * @param servers 其他 raft 节点的地址 70 | * @param id 当前 raft 节点在集群内的唯一标识 71 | * @param persister raft 保存其持久状态的地方,并且从保存的状态初始化当前节点 72 | * @param applyChan 是发送达成共识的日志的 channel 73 | */ 74 | RaftNode(std::map& servers, int64_t id, Persister::ptr persister, co::co_chan applyChan); 75 | 76 | ~RaftNode(); 77 | /** 78 | * @brief 启动 raft 79 | */ 80 | void start() override; 81 | /** 82 | * @brief 关闭 raft 83 | */ 84 | void stop() override; 85 | /** 86 | * @brief 增加 raft 节点 87 | * @param[in] id raft 节点id 88 | * @param[in] address raft 节点地址 89 | */ 90 | void addPeer(int64_t id, Address::ptr address); 91 | 92 | bool isLeader(); 93 | /** 94 | * @brief 获取 raft 节点状态 95 | * @return currentTerm 任期,isLeader 是否为 leader 96 | */ 97 | std::pair getState(); 98 | /** 99 | * @brief 获取 leader id 100 | */ 101 | int64_t getLeaderId() const { return m_leaderId;} 102 | /** 103 | * @brief 发起一条消息 104 | * @return 如果该节点不是 Leader 返回 std::nullopt 105 | */ 106 | std::optional propose(const std::string& data); 107 | 108 | template 109 | std::optional propose(const T& data) { 110 | std::unique_lock lock(m_mutex); 111 | Serializer s; 112 | s << data; 113 | s.reset(); 114 | return Propose(s.toString()); 115 | } 116 | /** 117 | * @brief 处理远端 raft 节点的投票请求 118 | */ 119 | RequestVoteReply handleRequestVote(RequestVoteArgs request); 120 | /** 121 | * @brief 处理远端 raft 节点的日志追加请求 122 | */ 123 | AppendEntriesReply handleAppendEntries(AppendEntriesArgs request); 124 | /** 125 | * @brief 处理远端 raft 节点的快照安装请求 126 | */ 127 | InstallSnapshotReply handleInstallSnapshot(InstallSnapshotArgs arg); 128 | /** 129 | * @brief 获取节点 id 130 | */ 131 | int64_t getNodeId() const { return m_id;} 132 | /** 133 | * @brief 持久化,有加锁 134 | * @param index 从该 idex 之前的日志都去掉 135 | * @param snap 快照数据 136 | */ 137 | void persistStateAndSnapshot(int64_t index, const std::string& snap); 138 | /** 139 | * @brief 持久化,有加锁 140 | * @param snap 快照数据 141 | */ 142 | void persistSnapshot(Snapshot::ptr snap); 143 | /** 144 | * @brief 输出 Raft 节点状态 145 | */ 146 | std::string toString(); 147 | /** 148 | * @brief 获取心跳超时时间 149 | */ 150 | static uint64_t GetStableHeartbeatTimeout(); 151 | /** 152 | * @brief 获取随机选举时间 153 | */ 154 | static uint64_t GetRandomizedElectionTimeout(); 155 | private: 156 | /** 157 | * @brief 转化为 Follower 158 | * @param[in] term 任期 159 | * @param[in] leaderId 任期领导人id,如果还未选举出来默认为-1 160 | */ 161 | void becomeFollower(int64_t term, int64_t leaderId = -1); 162 | /** 163 | * @brief 转化为 Candidate 164 | */ 165 | void becomeCandidate(); 166 | /** 167 | * @brief 转换为 Leader 168 | */ 169 | void becomeLeader(); 170 | /** 171 | * @brief 重置选举定时器 172 | */ 173 | void rescheduleElection(); 174 | /** 175 | * @brief 重置心跳定时器 176 | */ 177 | void resetHeartbeatTimer(); 178 | /** 179 | * @brief 对一个节点发起复制请求 180 | * @param peer 对方节点 id 181 | */ 182 | void replicateOneRound(int64_t peer); 183 | /** 184 | * @brief 用来往 applyCh 中 push 提交的日志 185 | */ 186 | void applier(); 187 | /** 188 | * @brief 开始选举,发起异步投票 189 | */ 190 | void startElection(); 191 | /** 192 | * @brief 广播心跳 193 | */ 194 | void broadcastHeartbeat(); 195 | /** 196 | * @brief 持久化,内部调用,不加锁 197 | */ 198 | void persist(Snapshot::ptr snap = nullptr); 199 | /** 200 | * @brief 发起一条消息,不加锁 201 | */ 202 | std::optional Propose(const std::string& data); 203 | 204 | private: 205 | MutexType m_mutex; 206 | // 节点状态,初始为 Follower 207 | RaftState m_state = Follower; 208 | // 该 raft 节点的唯一id 209 | int64_t m_id; 210 | // 任期内的 leader id,用于 follower 返回给客户端让客户端重定向请求到leader,-1表示无leader 211 | int64_t m_leaderId = -1; 212 | // 服务器已知最新的任期(在服务器首次启动时初始化为0,单调递增) 213 | int64_t m_currentTerm = 0; 214 | // 当前任期内收到选票的 candidateId,如果没有投给任何候选人 则为-1 215 | int64_t m_votedFor = -1; 216 | // 日志条目,每个条目包含了用于状态机的命令,以及领导人接收到该条目时的任期(初始索引为1) 217 | RaftLog m_logs; 218 | // 远端的 raft 节点,id 到节点的映射 219 | std::map m_peers; 220 | // 对于每一台服务器,发送到该服务器的下一个日志条目的索引(初始值为领导人最后的日志条目的索引+1) 221 | std::map m_nextIndex; 222 | // 对于每一台服务器,已知的已经复制到该服务器的最高日志条目的索引(初始值为0,单调递增) 223 | std::map m_matchIndex; 224 | // 选举定时器,超时后节点将转换为候选人发起投票 225 | CycleTimerTocken m_electionTimer; 226 | // 心跳定时器,领导者定时发送日志维持心跳,和同步日志 227 | CycleTimerTocken m_heartbeatTimer; 228 | // 持久化 229 | Persister::ptr m_persister; 230 | co::co_condition_variable m_applyCond; 231 | // 用来已通过raft达成共识的已提交的提议通知给其它组件的信道。 232 | co::co_chan m_applyChan; 233 | }; 234 | 235 | } 236 | #endif //ACID_RAFT_NODE_H 237 | -------------------------------------------------------------------------------- /acid/raft/raft_peer.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2022/7/19. 3 | // 4 | 5 | #include 6 | 7 | #include "../common/config.h" 8 | #include "../raft/raft_peer.h" 9 | 10 | namespace acid::raft { 11 | static auto g_logger = GetLogInstance(); 12 | // rpc 连接超时时间 13 | static ConfigVar::ptr g_rpc_timeout = 14 | Config::Lookup("raft.rpc.timeout", 3000, "raft rpc timeout(ms)"); 15 | // rpc 连接重试次数 16 | static ConfigVar::ptr g_connect_retry = 17 | Config::Lookup("raft.rpc.connect_retry", 3, "raft rpc connect retry times"); 18 | 19 | static uint64_t s_rpc_timeout; 20 | static uint32_t s_connect_retry; 21 | 22 | namespace { 23 | struct RaftPeerIniter{ 24 | RaftPeerIniter(){ 25 | s_rpc_timeout = g_rpc_timeout->getValue(); 26 | g_rpc_timeout->addListener([](const uint64_t& old_val, const uint64_t& new_val){ 27 | SPDLOG_LOGGER_INFO(g_logger, "raft rpc timeout changed from {} to {}", old_val, new_val); 28 | s_rpc_timeout = new_val; 29 | }); 30 | s_connect_retry = g_connect_retry->getValue(); 31 | g_rpc_timeout->addListener([](const uint32_t& old_val, const uint32_t& new_val){ 32 | SPDLOG_LOGGER_INFO(g_logger, "raft rpc timeout changed from {} to {}", old_val, new_val); 33 | s_connect_retry = new_val; 34 | }); 35 | } 36 | }; 37 | 38 | // 初始化配置 39 | [[maybe_unused]] 40 | static RaftPeerIniter s_initer; 41 | } 42 | 43 | 44 | RaftPeer::RaftPeer(int64_t id, Address::ptr address) 45 | : m_id(id), m_address(std::move(address)) { 46 | m_client = std::make_shared(); 47 | // 关闭心跳 48 | m_client->setHeartbeat(false); 49 | // 设置rpc超时时间 50 | m_client->setTimeout(s_rpc_timeout); 51 | } 52 | 53 | bool RaftPeer::connect() { 54 | if (!m_client->isClose()) { 55 | return true; 56 | } 57 | for (int i = 1; i <= (int)s_connect_retry; ++i) { 58 | // 重连 Raft 节点 59 | m_client->connect(m_address); 60 | if (!m_client->isClose()) { 61 | return true; 62 | } 63 | co_sleep(10 * i); 64 | } 65 | return false; 66 | } 67 | 68 | std::optional RaftPeer::requestVote(const RequestVoteArgs& arg) { 69 | if (!connect()) { 70 | return std::nullopt; 71 | } 72 | rpc::Result result = m_client->call(REQUEST_VOTE, arg); 73 | if (result.getCode() == rpc::RpcState::RPC_SUCCESS) { 74 | return result.getVal(); 75 | } 76 | if (result.getCode() == rpc::RpcState::RPC_CLOSED) { 77 | m_client->close(); 78 | } 79 | SPDLOG_LOGGER_DEBUG(g_logger, "Rpc call Node[{}] method [ {} ] failed, code is {}, msg is {}, RequestVoteArgs is {}", 80 | m_id, REQUEST_VOTE, result.getCode(), result.getMsg(), arg.toString()); 81 | return std::nullopt; 82 | } 83 | 84 | std::optional RaftPeer::appendEntries(const AppendEntriesArgs& arg) { 85 | if (!connect()) { 86 | return std::nullopt; 87 | } 88 | rpc::Result result = m_client->call(APPEND_ENTRIES, arg); 89 | if (result.getCode() == rpc::RpcState::RPC_SUCCESS) { 90 | return result.getVal(); 91 | } 92 | if (result.getCode() == rpc::RpcState::RPC_CLOSED) { 93 | m_client->close(); 94 | } 95 | SPDLOG_LOGGER_DEBUG(g_logger, "Rpc call Node[{}] method [ {} ] failed, code is {}, msg is {}", 96 | m_id, APPEND_ENTRIES, result.getCode(), result.getMsg()); 97 | return std::nullopt; 98 | } 99 | 100 | std::optional RaftPeer::installSnapshot(const InstallSnapshotArgs& arg) { 101 | if (!connect()) { 102 | return std::nullopt; 103 | } 104 | rpc::Result result = m_client->call(INSTALL_SNAPSHOT, arg); 105 | if (result.getCode() == rpc::RpcState::RPC_SUCCESS) { 106 | return result.getVal(); 107 | } 108 | if (result.getCode() == rpc::RpcState::RPC_CLOSED) { 109 | m_client->close(); 110 | } 111 | SPDLOG_LOGGER_DEBUG(g_logger, "Rpc call Node[{}] method [ {} ] failed, code is {}, msg is {}, InstallSnapshotArgs is {}", 112 | m_id, INSTALL_SNAPSHOT, result.getCode(), result.getMsg(), arg.toString()); 113 | return std::nullopt; 114 | } 115 | 116 | } -------------------------------------------------------------------------------- /acid/raft/raft_peer.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2022/7/19. 3 | // 4 | 5 | #ifndef ACID_RAFT_PEER_H 6 | #define ACID_RAFT_PEER_H 7 | #include 8 | #include "../rpc/rpc_client.h" 9 | #include "raft_log.h" 10 | namespace acid::raft { 11 | using namespace acid::rpc; 12 | 13 | // constant rpc method name 14 | inline const std::string REQUEST_VOTE = "RaftNode::handleRequestVote"; 15 | inline const std::string APPEND_ENTRIES = "RaftNode::handleAppendEntries"; 16 | inline const std::string INSTALL_SNAPSHOT = "RaftNode::handleInstallSnapshot"; 17 | 18 | /** 19 | * @brief RequestVote rpc 调用的参数 20 | */ 21 | struct RequestVoteArgs { 22 | int64_t term; // 候选人的任期 23 | int64_t candidateId; // 请求选票的候选人的 ID 24 | int64_t lastLogIndex; // 候选人的最后日志条目的索引值 25 | int64_t lastLogTerm; // 候选人的最后日志条目的任期号 26 | std::string toString() const { 27 | std::string str = fmt::format("Term: {}, CandidateId: {}, LastLogIndex: {}, LastLogTerm: {}", 28 | term, candidateId, lastLogIndex, lastLogTerm); 29 | return "{" + str + "}"; 30 | } 31 | }; 32 | 33 | /** 34 | * @brief RequestVote rpc 调用的返回值 35 | */ 36 | struct RequestVoteReply { 37 | int64_t term; // 当前任期号大于候选人id时,候选人更新自己的任期号,并切换为追随者 38 | int64_t leaderId; // 当前任期的leader 39 | bool voteGranted; // true表示候选人赢得了此张选票 40 | std::string toString() const { 41 | std::string str = fmt::format("Term: {}, LeaderId: {}, VoteGranted: {}", term, leaderId, voteGranted); 42 | return "{" + str + "}"; 43 | } 44 | }; 45 | 46 | /** 47 | * @brief AppendEntries rpc 调用的参数 48 | */ 49 | struct AppendEntriesArgs { 50 | int64_t term; // 领导人的任期 51 | int64_t leaderId; // 领导id 52 | int64_t prevLogIndex; // 紧邻新日志条目之前的那个日志条目的索引 53 | int64_t prevLogTerm; // 紧邻新日志条目之前的那个日志条目的任期 54 | std::vector entries; // 需要被保存的日志条目(被当做心跳使用时,则日志条目内容为空;为了提高效率可能一次性发送多个) 55 | int64_t leaderCommit; // 领导人的已知已提交的最高的日志条目的索引 56 | std::string toString() const { 57 | std::string str = fmt::format("Term: {}, LeaderId: {}, PrevLogIndex: {}, PrevLogTerm: {}, LeaderCommit: {}, Entries: [", term, leaderId, prevLogIndex, prevLogTerm, leaderCommit); 58 | for (int i = 0; i < (int)entries.size(); ++i) { 59 | if (i) { 60 | str.push_back(','); 61 | } 62 | str += entries[i].toString(); 63 | } 64 | return "{" + str + "]}"; 65 | } 66 | }; 67 | 68 | /** 69 | * @brief AppendEntries rpc 调用的返回值 70 | */ 71 | struct AppendEntriesReply { 72 | bool success = false; // 如果跟随者所含有的条目和 prevLogIndex 以及 prevLogTerm 匹配上了,则为 true 73 | int64_t term = 0; // 当前任期,如果大于领导人的任期则切换为追随者 74 | int64_t leaderId = 0; // 当前任期的leader 75 | int64_t nextIndex = 0; // 下一个期望接收的日志 76 | std::string toString() const { 77 | std::string str = fmt::format("Success: {}, Term: {}, LeaderId: {}, NextIndex: {}", 78 | success, term, leaderId, nextIndex); 79 | return "{" + str + "}"; 80 | } 81 | }; 82 | 83 | struct InstallSnapshotArgs { 84 | int64_t term; // 领导人的任期号 85 | int64_t leaderId; // 领导人的 ID,以便于跟随者重定向请求 86 | Snapshot snapshot; // 快照 87 | std::string toString() const { 88 | std::string str = fmt::format("Term: {}, LeaderId: {}, Snapshot.Metadata.Index: {}, Snapshot.Metadata.Term: {}", 89 | term, leaderId, snapshot.metadata.index, snapshot.metadata.term); 90 | return "{" + str + "}"; 91 | } 92 | }; 93 | 94 | struct InstallSnapshotReply { 95 | int64_t term; // 当前任期号,便于领导人更新自己 96 | int64_t leaderId; // 当前任期领导人 97 | std::string toString() const { 98 | std::string str = fmt::format("Term: {}, LeaderId: {}", term, leaderId); 99 | return "{" + str + "}"; 100 | } 101 | }; 102 | 103 | /** 104 | * @brief RaftNode 通过 RaftPeer 调用远端 Raft 节点,封装了 rpc 请求 105 | */ 106 | class RaftPeer { 107 | public: 108 | using ptr = std::shared_ptr; 109 | RaftPeer(int64_t id, Address::ptr address); 110 | 111 | std::optional requestVote(const RequestVoteArgs& arg); 112 | 113 | std::optional appendEntries(const AppendEntriesArgs& arg); 114 | 115 | std::optional installSnapshot(const InstallSnapshotArgs& arg); 116 | 117 | Address::ptr getAddress() const { return m_address;} 118 | 119 | private: 120 | bool connect(); 121 | 122 | private: 123 | int64_t m_id; 124 | rpc::RpcClient::ptr m_client; 125 | Address::ptr m_address; 126 | }; 127 | 128 | } 129 | #endif //ACID_RAFT_PEER_H 130 | -------------------------------------------------------------------------------- /acid/raft/snapshot.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2022/7/8. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | #include "snapshot.h" 9 | 10 | namespace acid::raft { 11 | static auto g_logger = GetLogInstance(); 12 | 13 | Snapshotter::Snapshotter(const std::filesystem::path& dir, 14 | const std::string& snap_suffix /* = ".snap"*/) 15 | : m_dir(dir) 16 | , m_snap_suffix(snap_suffix) { 17 | if (m_dir.empty()) { 18 | SPDLOG_LOGGER_WARN(g_logger, "snapshot path is empty"); 19 | } else if (!std::filesystem::exists(m_dir)) { 20 | SPDLOG_LOGGER_WARN(g_logger, "snapshot path: {} is not exists, create directory", m_dir.string()); 21 | std::filesystem::create_directories(m_dir); 22 | } else if (!std::filesystem::is_directory(m_dir)) { 23 | SPDLOG_LOGGER_WARN(g_logger, "snapshot path: {} is not a directory", m_dir.string()); 24 | } 25 | } 26 | 27 | bool Snapshotter::saveSnap(const Snapshot::ptr& snapshot) { 28 | if (!snapshot || snapshot->empty()) { 29 | return false; 30 | } 31 | return save(*snapshot); 32 | } 33 | 34 | bool Snapshotter::saveSnap(const Snapshot& snapshot) { 35 | if (snapshot.empty()) { 36 | return false; 37 | } 38 | return save(snapshot); 39 | } 40 | 41 | Snapshot::ptr Snapshotter::loadSnap() { 42 | std::vector names = snapNames(); 43 | if (names.empty()) { 44 | return nullptr; 45 | } 46 | // 从最新到最旧来遍历所有snapshot文件 47 | for (auto& name: names) { 48 | std::unique_ptr snap = read(name); 49 | if (snap) { 50 | // 成功加载快照就返回 51 | return snap; 52 | } 53 | } 54 | return nullptr; 55 | } 56 | 57 | std::vector Snapshotter::snapNames() { 58 | std::vector snaps; 59 | if (!std::filesystem::exists(m_dir) || !std::filesystem::is_directory(m_dir)) { 60 | return {}; 61 | } 62 | std::filesystem::directory_iterator dirite(m_dir); 63 | for (auto& ite : dirite) { 64 | // 忽略其他类型文件 65 | if (ite.status().type() == std::filesystem::file_type::regular) { 66 | snaps.push_back(ite.path().filename().string()); 67 | } 68 | } 69 | snaps = checkSffix(snaps); 70 | std::sort(snaps.begin(), snaps.end(), std::greater<>()); 71 | return snaps; 72 | } 73 | 74 | std::vector Snapshotter::checkSffix(const std::vector& names) { 75 | std::vector snaps; 76 | for (auto &name: names) { 77 | // 检查文件后缀 78 | if (name.compare(name.size() - m_snap_suffix.size(), m_snap_suffix.size(), m_snap_suffix) == 0) { 79 | snaps.push_back(name); 80 | } else { 81 | SPDLOG_LOGGER_WARN(g_logger, "skipped unexpected non snapshot file {}", name); 82 | } 83 | } 84 | return snaps; 85 | } 86 | 87 | bool Snapshotter::save(const Snapshot& snapshot) { 88 | // 快照名格式 %016ld-%016ld%s 89 | std::unique_ptr buff = std::make_unique(16 + 1 + 16 + m_snap_suffix.size() + 1); 90 | sprintf(&buff[0],"%016ld-%016ld%s", snapshot.metadata.term, snapshot.metadata.index, m_snap_suffix.c_str()); 91 | std::string filename = m_dir / (&buff[0]); 92 | 93 | int fd = open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0600); 94 | if (fd < 0) { 95 | return false; 96 | } 97 | 98 | rpc::Serializer ser; 99 | ser << snapshot; 100 | ser.reset(); 101 | 102 | std::string data = ser.toString(); 103 | if (write(fd, data.c_str(), data.size()) < 0) { 104 | return false; 105 | } 106 | // 持久化,必须马上刷新磁盘 107 | fsync(fd); 108 | close(fd); 109 | return true; 110 | } 111 | 112 | std::unique_ptr Snapshotter::read(const std::string& snapname) { 113 | std::ifstream file(m_dir / snapname, std::ios::binary); 114 | file.seekg(0, file.end); 115 | size_t length = file.tellg(); 116 | file.seekg(0, file.beg); 117 | 118 | if (!length) { 119 | return nullptr; 120 | } 121 | 122 | std::string data; 123 | data.resize(length); 124 | file.read(&data[0], length); 125 | 126 | rpc::Serializer ser(data); 127 | std::unique_ptr snapshot = std::make_unique(); 128 | ser >> (*snapshot); 129 | return snapshot; 130 | } 131 | } -------------------------------------------------------------------------------- /acid/raft/snapshot.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2022/7/8. 3 | // 4 | 5 | #ifndef ACID_SNAPSHOT_H 6 | #define ACID_SNAPSHOT_H 7 | 8 | #include 9 | #include "../rpc/serializer.h" 10 | 11 | namespace acid::raft { 12 | 13 | /** 14 | * @brief 快照元数据 15 | */ 16 | struct SnapshotMetadata { 17 | // 快照中包含的最后日志条目的索引值 18 | int64_t index; 19 | // 快照中包含的最后日志条目的任期号 20 | int64_t term; 21 | friend rpc::Serializer& operator<<(rpc::Serializer& s, const SnapshotMetadata& snap) { 22 | s << snap.index << snap.term; 23 | return s; 24 | } 25 | friend rpc::Serializer& operator>>(rpc::Serializer& s, SnapshotMetadata& snap) { 26 | s >> snap.index >> snap.term; 27 | return s; 28 | } 29 | }; 30 | 31 | /** 32 | * @brief 快照 33 | */ 34 | struct Snapshot { 35 | using ptr = std::shared_ptr; 36 | // 使用者某一时刻的全量序列化后的数据 37 | SnapshotMetadata metadata; 38 | std::string data; 39 | bool empty() const { 40 | return metadata.index == 0; 41 | } 42 | friend rpc::Serializer& operator<<(rpc::Serializer& s, const Snapshot& snap) { 43 | s << snap.metadata << snap.data; 44 | return s; 45 | } 46 | friend rpc::Serializer& operator>>(rpc::Serializer& s, Snapshot& snap) { 47 | s >> snap.metadata >> snap.data; 48 | return s; 49 | } 50 | }; 51 | 52 | /** 53 | * @brief 快照管理器,复制快照的存储和加载 54 | */ 55 | class Snapshotter { 56 | public: 57 | Snapshotter(const std::filesystem::path& dir, const std::string& snap_suffix = ".snap"); 58 | /** 59 | * @brief 对外暴露的接口,存储并持久化一个snapshot 60 | * @return bool 是否成功存储 61 | */ 62 | bool saveSnap(const Snapshot::ptr& snapshot); 63 | /** 64 | * @brief 对外暴露的接口,存储并持久化一个snapshot 65 | * @return bool 是否成功存储 66 | */ 67 | bool saveSnap(const Snapshot& snapshot); 68 | /** 69 | * @brief 加载最新的一个快照 70 | * @return Snapshot::ptr 如果没有快照则返回nullptr 71 | */ 72 | Snapshot::ptr loadSnap(); 73 | private: 74 | /** 75 | * @brief 按逻辑顺序(最新的快照到旧快照)返回快照列表 76 | * @return std::vector 快照列表 77 | */ 78 | std::vector snapNames(); 79 | /** 80 | * @brief 对文件名后缀的合法性检查 81 | * @param names 文件名列表 82 | * @return 合法的快照名列表 83 | */ 84 | std::vector checkSffix(const std::vector& names); 85 | /** 86 | * @brief 将snapshot序列化后持久化到磁盘 87 | */ 88 | bool save(const Snapshot& snapshot); 89 | /** 90 | * @brief 反序列化成 snapshot 91 | */ 92 | std::unique_ptr read(const std::string& snapname); 93 | private: 94 | // 快照目录 95 | const std::filesystem::path m_dir; 96 | // 快照文件后缀 97 | const std::string m_snap_suffix; 98 | }; 99 | 100 | } 101 | #endif //ACID_SNAPSHOT_H 102 | -------------------------------------------------------------------------------- /acid/rpc/protocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2022/1/15. 3 | // 4 | 5 | #ifndef ACID_PROTOCOL_H 6 | #define ACID_PROTOCOL_H 7 | #include 8 | #include "acid/common/byte_array.h" 9 | namespace acid::rpc { 10 | /* 11 | * 私有通信协议 12 | * +--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+ 13 | * | BYTE | | | | | | | | | | | ........ | 14 | * +--------------------------------------------+--------+--------------------------+--------+-----------------+--------+--------+--------+--------+--------+--------+-----------------+ 15 | * | magic | version| type | sequence id | content length | content byte[] | 16 | * +--------+-----------------------------------------------------------------------------------------------------------------------------+--------------------------------------------+ 17 | * 第一个字节是魔法数。 18 | * 第二个字节代表协议版本号,以便对协议进行扩展,使用不同的协议解析器。 19 | * 第三个字节是请求类型,如心跳包,rpc请求。 20 | * 第四个字节开始是一个32位序列号。 21 | * 第七个字节开始的四字节表示消息长度,即后面要接收的内容长度。 22 | */ 23 | class Protocol { 24 | public: 25 | using ptr = std::shared_ptr; 26 | 27 | static constexpr uint8_t MAGIC = 0xcc; 28 | static constexpr uint8_t DEFAULT_VERSION = 0x01; 29 | static constexpr uint8_t BASE_LENGTH = 11; 30 | 31 | enum class MsgType : uint8_t { 32 | HEARTBEAT_PACKET, // 心跳包 33 | RPC_PROVIDER, // 向服务中心声明为provider 34 | RPC_CONSUMER, // 向服务中心声明为consumer 35 | 36 | RPC_REQUEST, // 通用请求 37 | RPC_RESPONSE, // 通用响应 38 | 39 | RPC_METHOD_REQUEST, // 请求方法调用 40 | RPC_METHOD_RESPONSE, // 响应方法调用 41 | 42 | RPC_SERVICE_REGISTER, // 向中心注册服务 43 | RPC_SERVICE_REGISTER_RESPONSE, 44 | 45 | RPC_SERVICE_DISCOVER, // 向中心请求服务发现 46 | RPC_SERVICE_DISCOVER_RESPONSE, 47 | 48 | RPC_PUBSUB_REQUEST, // 发布订阅 49 | RPC_PUBSUB_RESPONSE, 50 | }; 51 | 52 | static Protocol::ptr Create(MsgType type, const std::string& content, uint32_t id = 0) { 53 | Protocol::ptr proto = std::make_shared(); 54 | proto->setMsgType(type); 55 | proto->setContent(content); 56 | proto->setSequenceId(id); 57 | return proto; 58 | } 59 | 60 | static Protocol::ptr HeartBeat() { 61 | static Protocol::ptr Heartbeat = Protocol::Create(Protocol::MsgType::HEARTBEAT_PACKET, ""); 62 | return Heartbeat; 63 | } 64 | 65 | uint8_t getMagic() const { return m_magic;} 66 | uint8_t getVersion() const { return m_version;} 67 | MsgType getMsgType() const { return static_cast(m_type);} 68 | uint32_t getSequenceId() const { return m_sequence_id;} 69 | uint32_t getContentLength() const { return m_content_length;} 70 | const std::string& getContent() const { return m_content;} 71 | 72 | void setMagic(uint8_t magic) { m_magic = magic;} 73 | void setVersion(uint8_t version) { m_version = version;} 74 | void setMsgType(MsgType type) { m_type = static_cast(type);} 75 | void setSequenceId(uint32_t id) { m_sequence_id = id;} 76 | void setContentLength(uint32_t len) { m_content_length = len;} 77 | void setContent(const std::string& content) { m_content = content;} 78 | 79 | ByteArray::ptr encodeMeta() { 80 | ByteArray::ptr bt = std::make_shared(); 81 | bt->writeFuint8(m_magic); 82 | bt->writeFuint8(m_version); 83 | bt->writeFuint8(m_type); 84 | bt->writeFuint32(m_sequence_id); 85 | bt->writeFuint32(m_content.size()); 86 | bt->setPosition(0); 87 | return bt; 88 | } 89 | 90 | ByteArray::ptr encode() { 91 | ByteArray::ptr bt = std::make_shared(); 92 | bt->writeFuint8(m_magic); 93 | bt->writeFuint8(m_version); 94 | bt->writeFuint8(m_type); 95 | bt->writeFuint32(m_sequence_id); 96 | bt->writeStringF32(m_content); 97 | bt->setPosition(0); 98 | return bt; 99 | } 100 | 101 | void decodeMeta(ByteArray::ptr bt) { 102 | m_magic = bt->readFuint8(); 103 | m_version = bt->readFuint8(); 104 | m_type = bt->readFuint8(); 105 | m_sequence_id = bt->readFuint32(); 106 | m_content_length = bt->readFuint32(); 107 | } 108 | 109 | void decode(ByteArray::ptr bt) { 110 | m_magic = bt->readFuint8(); 111 | m_version = bt->readFuint8(); 112 | m_type = bt->readFuint8(); 113 | m_sequence_id = bt->readFuint32(); 114 | m_content = bt->readStringF32(); 115 | m_content_length = m_content.size(); 116 | } 117 | 118 | std::string toString() { 119 | auto str = fmt::format(R"("magic": {}, "version": {},"type": {},"id": {},"length": {},"content": {})", 120 | m_magic, m_version, m_type, m_sequence_id, m_content_length, m_content); 121 | return str; 122 | } 123 | private: 124 | uint8_t m_magic = MAGIC; 125 | uint8_t m_version = DEFAULT_VERSION; 126 | uint8_t m_type = 0; 127 | uint32_t m_sequence_id = 0; 128 | uint32_t m_content_length = 0; 129 | std::string m_content; 130 | }; 131 | 132 | } 133 | #endif //ACID_PROTOCOL_H 134 | -------------------------------------------------------------------------------- /acid/rpc/pubsub.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2022/12/21. 3 | // 4 | 5 | #ifndef ACID_PUBSUB_H 6 | #define ACID_PUBSUB_H 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | namespace acid::rpc { 13 | enum class PubsubMsgType { 14 | Publish, 15 | Message, 16 | Subscribe, 17 | Unsubscribe, 18 | PatternMessage, 19 | PatternSubscribe, 20 | PatternUnsubscribe, 21 | }; 22 | 23 | struct PubsubRequest { 24 | PubsubMsgType type; 25 | std::string channel; 26 | std::string message; 27 | std::string pattern; 28 | }; 29 | 30 | struct PubsubResponse { 31 | PubsubMsgType type; 32 | std::string channel; 33 | std::string pattern; 34 | }; 35 | 36 | // 发布订阅监听器 37 | class PubsubListener { 38 | public: 39 | using ptr = std::shared_ptr; 40 | 41 | virtual ~PubsubListener() {} 42 | /** 43 | * @param channel 频道 44 | * @param message 消息 45 | */ 46 | virtual void onMessage(const std::string& channel, const std::string& message) {} 47 | /** 48 | * @param channel 频道 49 | */ 50 | virtual void onSubscribe(const std::string& channel) {} 51 | /** 52 | * @param channel 频道 53 | */ 54 | virtual void onUnsubscribe(const std::string& channel) {} 55 | 56 | /** 57 | * @param pattern 模式 58 | * @param channel 频道 59 | * @param message 消息 60 | */ 61 | virtual void onPatternMessage(const std::string& pattern, const std::string& channel, const std::string& message) {} 62 | /** 63 | * @param pattern 模式 64 | */ 65 | virtual void onPatternSubscribe(const std::string& pattern) {} 66 | /** 67 | * @param pattern 模式 68 | */ 69 | virtual void onPatternUnsubscribe(const std::string& pattern) {} 70 | }; 71 | 72 | } 73 | #endif //ACID_PUBSUB_H -------------------------------------------------------------------------------- /acid/rpc/route_strategy.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2022/1/15. 3 | // 4 | 5 | #ifndef ACID_ROUTE_STRATEGY_H 6 | #define ACID_ROUTE_STRATEGY_H 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | namespace acid::rpc { 20 | enum class Strategy { 21 | //随机算法 22 | Random, 23 | //轮询算法 24 | Polling, 25 | //源地址hash算法 26 | HashIP 27 | }; 28 | /** 29 | * @brief 路由策略接口 30 | * @details 通用类型负载均衡路由引擎(含随机、轮询、哈希), 由客户端使用,在客户端实现负载均衡 31 | */ 32 | template 33 | class RouteStrategy { 34 | public: 35 | using ptr = std::shared_ptr; 36 | virtual ~RouteStrategy() {} 37 | /** 38 | * @brief负载策略算法 39 | * @param list 原始列表 40 | * @return 41 | */ 42 | virtual T& select(std::vector& list) = 0; 43 | }; 44 | namespace impl { 45 | /** 46 | * @brief 随机策略 47 | */ 48 | template 49 | class RandomRouteStrategyImpl : public RouteStrategy { 50 | public: 51 | T& select(std::vector& list) { 52 | srand((unsigned)time(NULL)); 53 | return list[rand() % list.size()]; 54 | } 55 | }; 56 | /** 57 | * @brief 轮询策略 58 | */ 59 | template 60 | class PollingRouteStrategyImpl : public RouteStrategy { 61 | public: 62 | T& select(std::vector& list) { 63 | std::lock_guard lock(m_mutex); 64 | if (m_index >= (int)list.size()) { 65 | m_index = 0; 66 | } 67 | return list[m_index++]; 68 | } 69 | private: 70 | int m_index = 0; 71 | std::mutex m_mutex; 72 | }; 73 | static std::string GetLocalHost(); 74 | /** 75 | * @brief 基于IP的哈希策略 76 | */ 77 | template 78 | class HashIPRouteStrategyImpl : public RouteStrategy { 79 | public: 80 | T& select(std::vector& list) { 81 | static std::string host = GetLocalHost(); 82 | //保证程序健壮性,若未取到域名,则采用随机算法 83 | if(host.empty()){ 84 | return RandomRouteStrategyImpl{}.select(list); 85 | } 86 | //获取源地址对应的hashcode 87 | size_t hashCode = std::hash()(host); 88 | //获取服务列表大小 89 | size_t size = list.size(); 90 | 91 | return list[hashCode % size]; 92 | } 93 | 94 | }; 95 | 96 | std::string GetLocalHost() { 97 | int sockfd; 98 | struct ifconf ifconf; 99 | struct ifreq *ifreq = nullptr; 100 | char buf[512];//缓冲区 101 | //初始化ifconf 102 | ifconf.ifc_len =512; 103 | ifconf.ifc_buf = buf; 104 | if ((sockfd = socket(AF_INET,SOCK_DGRAM,0))<0) 105 | { 106 | return std::string{}; 107 | } 108 | ioctl(sockfd, SIOCGIFCONF, &ifconf); //获取所有接口信息 109 | //接下来一个一个的获取IP地址 110 | ifreq = (struct ifreq*)ifconf.ifc_buf; 111 | for (int i=(ifconf.ifc_len/sizeof (struct ifreq)); i>0; i--) 112 | { 113 | if(ifreq->ifr_flags == AF_INET){ //for ipv4 114 | if (ifreq->ifr_name == std::string("eth0")) { 115 | return std::string(inet_ntoa(((struct sockaddr_in*)&(ifreq->ifr_addr))->sin_addr)); 116 | } 117 | ifreq++; 118 | } 119 | } 120 | return std::string{}; 121 | } 122 | } 123 | 124 | /** 125 | * @brief: 路由均衡引擎 126 | */ 127 | template 128 | class RouteEngine { 129 | public: 130 | static typename RouteStrategy::ptr queryStrategy(Strategy routeStrategyEnum) { 131 | switch (routeStrategyEnum){ 132 | case Strategy::Random: 133 | return s_randomRouteStrategy; 134 | case Strategy::Polling: 135 | return std::make_shared>(); 136 | case Strategy::HashIP: 137 | return s_hashIPRouteStrategy ; 138 | default: 139 | return s_randomRouteStrategy ; 140 | } 141 | } 142 | private: 143 | static typename RouteStrategy::ptr s_randomRouteStrategy; 144 | static typename RouteStrategy::ptr s_hashIPRouteStrategy; 145 | }; 146 | template 147 | typename RouteStrategy::ptr RouteEngine::s_randomRouteStrategy = std::make_shared>(); 148 | template 149 | typename RouteStrategy::ptr RouteEngine::s_hashIPRouteStrategy = std::make_shared>(); 150 | } 151 | #endif //ACID_ROUTE_STRATEGY_H 152 | -------------------------------------------------------------------------------- /acid/rpc/rpc.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2022/1/13. 3 | // 4 | 5 | #ifndef ACID_RPC_H 6 | #define ACID_RPC_H 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "serializer.h" 14 | 15 | namespace acid::rpc { 16 | 17 | // 连接池向注册中心订阅的前缀 18 | inline const char* RPC_SERVICE_SUBSCRIBE = "[[rpc service subscribe]]"; 19 | 20 | template 21 | struct return_type { 22 | using type = T; 23 | }; 24 | 25 | template<> 26 | struct return_type { 27 | using type = int8_t; 28 | }; 29 | 30 | /** 31 | * @brief 调用结果为 void 类型的,将类型转换为 int8_t 32 | */ 33 | template 34 | using return_type_t = typename return_type::type; 35 | 36 | 37 | /** 38 | * @brief RPC调用状态 39 | */ 40 | enum RpcState{ 41 | RPC_SUCCESS = 0, // 成功 42 | RPC_FAIL, // 失败 43 | RPC_NO_MATCH, // 函数不匹配 44 | RPC_NO_METHOD, // 没有找到调用函数 45 | RPC_CLOSED, // RPC 连接被关闭 46 | RPC_TIMEOUT // RPC 调用超时 47 | }; 48 | 49 | /** 50 | * @brief 包装 RPC调用结果 51 | */ 52 | template 53 | class Result{ 54 | public: 55 | using row_type = T; 56 | using type = return_type_t; 57 | using msg_type = std::string; 58 | using code_type = uint16_t; 59 | 60 | static Result Success() { 61 | Result res; 62 | res.setCode(RPC_SUCCESS); 63 | res.setMsg("success"); 64 | return res; 65 | } 66 | static Result Fail() { 67 | Result res; 68 | res.setCode(RPC_FAIL); 69 | res.setMsg("fail"); 70 | return res; 71 | } 72 | 73 | Result() {} 74 | bool valid() { return m_code == 0; } 75 | type& getVal() { return m_val; } 76 | void setVal(const type& val) { m_val = val; } 77 | void setCode(code_type code) { m_code = code; } 78 | int getCode() { return m_code; } 79 | void setMsg(msg_type msg) { m_msg = msg; } 80 | const msg_type& getMsg() { return m_msg; } 81 | 82 | type* operator->() noexcept { 83 | return &m_val; 84 | } 85 | const type* operator->() const noexcept { 86 | return &m_val; 87 | } 88 | 89 | /** 90 | * @brief 调试使用 !!!!!! 91 | */ 92 | std::string toString() { 93 | std::stringstream ss; 94 | ss << "[ code=" << m_code << " msg=" << m_msg << " val=" << m_val << " ]"; 95 | return ss.str(); 96 | } 97 | 98 | /** 99 | * @brief 反序列化回 Result 100 | * @param[in] in 序列化的结果 101 | * @param[in] d 反序列化回 Result 102 | * @return Serializer 103 | */ 104 | friend Serializer& operator >> (Serializer& in, Result& d) { 105 | in >> d.m_code >> d.m_msg; 106 | if (d.m_code == 0) { 107 | in >> d.m_val; 108 | } 109 | return in; 110 | } 111 | 112 | /** 113 | * @brief 将 Result 序列化 114 | * @param[in] out 序列化输出 115 | * @param[in] d 将 Result 序列化 116 | * @return Serializer 117 | */ 118 | friend Serializer& operator << (Serializer& out, Result d) { 119 | out << d.m_code << d.m_msg << d.m_val; 120 | return out; 121 | } 122 | private: 123 | /// 调用状态 124 | code_type m_code = 0; 125 | /// 调用消息 126 | msg_type m_msg; 127 | /// 调用结果 128 | type m_val; 129 | }; 130 | 131 | } 132 | #endif //ACID_RPC_H 133 | -------------------------------------------------------------------------------- /acid/rpc/rpc_server.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2022/1/13. 3 | // 4 | 5 | #ifndef ACID_RPC_SERVER_H 6 | #define ACID_RPC_SERVER_H 7 | #include 8 | #include 9 | #include "acid/net/socket_stream.h" 10 | #include "acid/net/tcp_server.h" 11 | #include "acid/common/traits.h" 12 | #include "protocol.h" 13 | #include "pubsub.h" 14 | #include "rpc.h" 15 | #include "rpc_session.h" 16 | namespace acid::rpc { 17 | /** 18 | * @brief RPC服务端 19 | */ 20 | class RpcServer : public TcpServer { 21 | public: 22 | using ptr = std::shared_ptr; 23 | using MutexType = co::co_mutex; 24 | 25 | RpcServer(); 26 | ~RpcServer(); 27 | bool bind(Address::ptr address) override; 28 | bool bindRegistry(Address::ptr address); 29 | void start() override; 30 | void stop() override; 31 | /** 32 | * @brief 注册函数 33 | * @param[in] name 注册的函数名 34 | * @param[in] func 注册的函数 35 | */ 36 | template 37 | void registerMethod(const std::string& name, Func func) { 38 | m_handlers[name] = [func, this](Serializer serializer, const std::string& arg) { 39 | proxy(func, serializer, arg); 40 | }; 41 | } 42 | /** 43 | * @brief 设置RPC服务器名称 44 | * @param[in] name 名字 45 | */ 46 | void setName(const std::string& name) override; 47 | 48 | /** 49 | * @brief 对一个频道发布消息 50 | * @param channel 频道名 51 | * @param message 消息 52 | */ 53 | void publish(const std::string& channel, const std::string& message); 54 | 55 | protected: 56 | /** 57 | * @brief 向服务注册中心发起注册 58 | * @param[in] name 注册的函数名 59 | */ 60 | void registerService(const std::string& name); 61 | /** 62 | * @brief 调用服务端注册的函数,返回序列化完的结果 63 | * @param[in] name 函数名 64 | * @param[in] arg 函数参数字节流 65 | * @return 函数调用的序列化结果 66 | */ 67 | Serializer call(const std::string& name, const std::string& arg); 68 | 69 | /** 70 | * @brief 调用代理 71 | * @param[in] fun 函数 72 | * @param[in] serializer 返回调用结果 73 | * @param[in] arg 函数参数字节流 74 | */ 75 | template 76 | void proxy(F fun, Serializer serializer, const std::string& arg) { 77 | typename function_traits::stl_function_type func(fun); 78 | using Return = typename function_traits::return_type; 79 | using Args = typename function_traits::tuple_type; 80 | 81 | acid::rpc::Serializer s(arg); 82 | // 反序列化字节流,存为参数tuple 83 | Args args; 84 | try { 85 | s >> args; 86 | } catch (...) { 87 | Result val; 88 | val.setCode(acid::rpc::RPC_NO_MATCH); 89 | val.setMsg("params not match"); 90 | serializer << val; 91 | return; 92 | } 93 | 94 | return_type_t rt{}; 95 | 96 | constexpr auto size = std::tuple_size::type>::value; 97 | auto invoke = [&func, &args](std::index_sequence){ 98 | return func(std::get(std::forward(args))...); 99 | }; 100 | 101 | if constexpr(std::is_same_v) { 102 | invoke(std::make_index_sequence{}); 103 | } else { 104 | rt = invoke(std::make_index_sequence{}); 105 | } 106 | 107 | Result val; 108 | val.setCode(acid::rpc::RPC_SUCCESS); 109 | val.setVal(rt); 110 | serializer << val; 111 | } 112 | /** 113 | * @brief 处理客户端连接 114 | * @param[in] client 客户端套接字 115 | */ 116 | void handleClient(Socket::ptr client) override; 117 | /** 118 | * @brief 处理客户端过程调用请求 119 | */ 120 | Protocol::ptr handleMethodCall(Protocol::ptr proto); 121 | /** 122 | * @brief 处理心跳包 123 | */ 124 | Protocol::ptr handleHeartbeatPacket(Protocol::ptr proto); 125 | /** 126 | * @brief 处理发布订阅 127 | */ 128 | Protocol::ptr handlePubsubRequest(Protocol::ptr proto, Socket::ptr client); 129 | 130 | void subscribe(const std::string& channel, Socket::ptr client); 131 | 132 | void unsubscribe(const std::string& channel, Socket::ptr client); 133 | 134 | void patternSubscribe(const std::string& pattern, Socket::ptr client); 135 | 136 | void patternUnsubscribe(const std::string& pattern, Socket::ptr client); 137 | private: 138 | // 保存注册的函数 139 | std::map> m_handlers; 140 | // 服务中心连接 141 | RpcSession::ptr m_registry; 142 | // 服务中心心跳定时器 143 | CycleTimerTocken m_heartTimer; 144 | // 开放服务端口 145 | uint32_t m_port; 146 | // 和客户端的心跳时间 默认 40s 147 | uint64_t m_aliveTime; 148 | //用于保存所有频道的订阅关系 149 | std::map > m_pubsub_channels; 150 | //保存所有模式订阅关系 151 | std::list> m_pubsub_patterns; 152 | 153 | MutexType m_pubsub_mutex; 154 | }; 155 | 156 | } 157 | #endif //ACID_RPC_SERVER_H 158 | -------------------------------------------------------------------------------- /acid/rpc/rpc_session.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2022/2/10. 3 | // 4 | 5 | #include "rpc_session.h" 6 | 7 | namespace acid::rpc { 8 | RpcSession::RpcSession(Socket::ptr socket, bool owner) 9 | : SocketStream(socket, owner) { 10 | } 11 | 12 | Protocol::ptr RpcSession::recvProtocol() { 13 | Protocol::ptr proto = std::make_shared(); 14 | ByteArray::ptr byteArray = std::make_shared(); 15 | if (readFixSize(byteArray, proto->BASE_LENGTH) <= 0) { 16 | return nullptr; 17 | } 18 | 19 | byteArray->setPosition(0); 20 | proto->decodeMeta(byteArray); 21 | 22 | if(proto->getMagic() != Protocol::MAGIC) { 23 | return nullptr; 24 | } 25 | 26 | if (!proto->getContentLength()) { 27 | return proto; 28 | } 29 | 30 | std::string buff; 31 | buff.resize(proto->getContentLength()); 32 | 33 | if (readFixSize(&buff[0], proto->getContentLength()) <= 0) { 34 | return nullptr; 35 | } 36 | proto->setContent(buff); 37 | return proto; 38 | } 39 | 40 | ssize_t RpcSession::sendProtocol(Protocol::ptr proto) { 41 | ByteArray::ptr byteArray = proto->encode(); 42 | std::unique_lock lock(m_mutex); 43 | return writeFixSize(byteArray, byteArray->getSize()); 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /acid/rpc/rpc_session.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2022/2/10. 3 | // 4 | 5 | #ifndef ACID_RPC_SESSION_H 6 | #define ACID_RPC_SESSION_H 7 | 8 | #include 9 | #include "acid/net/socket_stream.h" 10 | #include "protocol.h" 11 | 12 | namespace acid::rpc { 13 | /** 14 | * @brief rpc session 封装了协议的收发 15 | */ 16 | class RpcSession : public SocketStream { 17 | public: 18 | using ptr = std::shared_ptr; 19 | using MutexType = co::co_mutex; 20 | 21 | /** 22 | * @brief 构造函数 23 | * @param[in] sock Socket类型 24 | * @param[in] owner 是否托管Socket 25 | */ 26 | RpcSession(Socket::ptr socket, bool owner = true); 27 | /** 28 | * @brief 接收协议 29 | * @return 如果返回空协议则接收失败 30 | */ 31 | Protocol::ptr recvProtocol(); 32 | /** 33 | * @brief 发送协议 34 | * @param[in] proto 要发送的协议 35 | * @return 发送大小 36 | */ 37 | ssize_t sendProtocol(Protocol::ptr proto); 38 | private: 39 | MutexType m_mutex; 40 | }; 41 | } 42 | #endif //ACID_RPC_SESSION_H 43 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | sudo pwd 3 | mkdir -p third_party/libgo/build 4 | mkdir -p third_party/spdlog/build 5 | mkdir -p third_party/yaml-cpp/build 6 | 7 | cd third_party/libgo/build 8 | cmake .. 9 | sudo make install 10 | cd - 11 | 12 | cd third_party/spdlog/build 13 | cmake .. 14 | sudo make install 15 | cd - 16 | 17 | cd third_party/yaml-cpp/build 18 | cmake .. 19 | sudo make install 20 | cd - -------------------------------------------------------------------------------- /docs/go style协程设计.md: -------------------------------------------------------------------------------- 1 | 参考了鲨鱼哥的 [librf](https://github.com/tearshark/librf) ,实现了go style的协程。加上Channel,使用起来已经很接近go了。 2 | 3 | 先看一下使用样例 4 | 5 | ```cpp 6 | int main() { 7 | // 创建一个 Channel 8 | Channel chan(1); 9 | // 开启一个协程往 Channel 里发送数据 10 | Go { 11 | for (int i = 0; i < 10; ++i) { 12 | chan << i; 13 | LOG_DEBUG << "send: " << i; 14 | } 15 | chan.close(); 16 | }; 17 | // 开启一个协程从 Channel 里读取数据 18 | Go { 19 | int i; 20 | while (chan >> i) { 21 | LOG_DEBUG << "recv: " << i; 22 | } 23 | }; 24 | } 25 | ``` 26 | 27 | 可以看到非常容易上手,使用go关键字就能起一个协程。 28 | 29 | 现在看看具体该如何实现。 30 | 31 | 最原始调度器提交协程任务的方法为 32 | 33 | ```cpp 34 | template 35 | Scheduler* submit(FiberOrCb&& fc) { 36 | ... 37 | } 38 | ``` 39 | 40 | 按照这样提交,可以看到过程比较冗余,也不方便与宏配合。 41 | 42 | ```cpp 43 | Scheduler::GetThis()->submit(fiber); 44 | ``` 45 | 46 | 所以再重载一个运算符来简化一下 47 | 48 | 49 | ```cpp 50 | // 重载+运算符,将任务转发给submit 51 | template 52 | Scheduler* operator+(FiberOrCb&& fc) { 53 | return submit(std::move(fc)); 54 | } 55 | ``` 56 | 57 | 就可以这样提交协程了 58 | 59 | ```cpp 60 | (*Scheduler::GetThis()) + fiber 61 | ``` 62 | 63 | 由于前面部分都是固定的,可以用宏了简化一下 64 | 65 | ```cpp 66 | #define go (*acid::IOManager::GetThis()) + 67 | ``` 68 | 69 | 现在就能用go来启动函数或者协程了 70 | 71 | ```cpp 72 | // 普通函数 73 | go normal; 74 | // 协程 75 | go fiber; 76 | 77 | // 函数对象 78 | go [] { 79 | LOG_DEBUG << "Lambda"; 80 | }; 81 | 82 | std::string str = "hello"; 83 | // 但有个问题,捕获后变量是const,所以这个操作是错误,无法编译 84 | go [str] { 85 | str += "hello"; 86 | }; 87 | // 要想改变变量,得加上mutable 88 | go [str]() mutable { 89 | str += "world"; 90 | }; 91 | ``` 92 | 93 | 注意到最后一种情况有很强的通用性,即捕获变量进行修改,于是再设计一个宏来简化这种情况 94 | 95 | ```cpp 96 | // Go 关键字默认按值捕获变量 97 | // 等价于 go [=]() mutable {} 98 | #define Go (*acid::IOManager::GetThis()) + [=]()mutable 99 | ``` 100 | 101 | 现在如果要捕获变量到协程里修改,就可以用Go来启动 102 | 103 | ```cpp 104 | std::string str = "hello"; 105 | 106 | Go { 107 | str += "world"; 108 | }; 109 | ``` 110 | 111 | 注意Go默认按值捕获全部局部变量,所以使用起来要注意,如果变量太多就用go来按需捕获。 112 | 113 | 提供了配置默认调度器线程数量和调度器名字的方法,在还没启动go之前,可以使用如下方法设置 114 | 115 | ```cpp 116 | // 设置默认调度器的线程数量 117 | Config::Lookup("scheduler.threads")->setValue(2); 118 | // 设置默认调度器的名字 119 | Config::Lookup("scheduler.name")->setValue("default"); 120 | ``` 121 | 122 | 最新代码已经更新到GitHub上了 123 | 124 | [zavier-wong/acid: A high performance fiber RPC network framework. 高性能协程RPC网络框架 (github.com)](https://github.com/zavier-wong/acid) 125 | -------------------------------------------------------------------------------- /docs/images/多路分解.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zavier-wong/acid/7f405a6b48f57aa4d5a8fcad4c49935d7ecbc58b/docs/images/多路分解.png -------------------------------------------------------------------------------- /docs/images/多路复用.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zavier-wong/acid/7f405a6b48f57aa4d5a8fcad4c49935d7ecbc58b/docs/images/多路复用.png -------------------------------------------------------------------------------- /docs/rpc连接复用设计.md: -------------------------------------------------------------------------------- 1 | 本篇将探讨rpc连接多路复用与多路分解的设计思路与实现。 2 | 3 | 本系列的相关文章如果没有看的可以先看一下,后面会用到相关的知识。 4 | 5 | [通用协程同步原语设计](https://zhuanlan.zhihu.com/p/475554418) 6 | 7 | [C++高性能协程RPC框架设计](https://zhuanlan.zhihu.com/p/460646015) 8 | 9 | ### 问题分析 10 | 11 | 对于短连接来说,每次发起rpc调用就创建一条连接,由于没有竞争实现起来比较容易,但开销太大。所以本框架实现了rpc连接复用来支持更高的并发。 12 | 13 | 连接复用的问题在于,在一条连接上可以有多个并发的调用请求,由于服务器也是并发处理这些请求的,所以导致了服务器返回的响应顺序与请求顺序不一致。为了识别请求,我们很容易想到一个方法,就是给每个连接每个请求加上一个唯一的序列号,本框架的做法是在协议头加上序列号字段,具体结构如下 14 | 15 | ```c 16 | +--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+ 17 | | BYTE | | | | | | | | | | | ........ | 18 | +--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+ 19 | | magic | version| type | sequence id | content length | content byte[] | 20 | +--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+ 21 | 22 | ``` 23 | 24 | 第四个字段就是一个32位的序列号,用来识别请求顺序。 25 | 26 | 解决了请求标识的问题,剩下的问题就是如何收集并发的调用请求,按串行的顺序发送给服务提供方,以及如何将收到的调用结果转发给等待的调用者。即连接的多路复用与多路分解。 27 | 28 | ### 多路复用 29 | 30 | ![多路复用](images/多路复用.png) 31 | 32 | 先看一下 `RpcClient` 的大致结构 33 | ```cpp 34 | class RpcClient : public std::enable_shared_from_this 35 | using MutexType = CoMutex; 36 | private: 37 | ... 38 | /// 序列号 39 | uint32_t m_sequenceId = 0; 40 | /// 序列号到对应调用者协程的 Channel 映射 41 | std::map> m_responseHandle; 42 | /// 保护 m_responseHandle 的 mutex 43 | MutexType m_mutex; 44 | /// 消息发送通道 45 | Channel m_chan; 46 | } 47 | ``` 48 | 49 | 每个 `RpcClient` 连接对象都有一个不断自增的序列号,一个 `Channel`,一个序列号到对应调用者协程的 `Channel` 映射。 50 | 51 | 在每个对象连接到服务器时,我们开启了一个 `handleSend` 协程,这个协程的作用是不断从` Channel` 里读取调用请求,转发给服务器。通过上篇所讲的协程同步原语设计,我们知道 `Channel` 内部封装了锁和协程的 `yield`、`resume`。所以我们不用进行加锁就能优雅地收集了调用请求,在 `Channel` 没有消息时会自动挂起,等待请求到达。 52 | 53 | ```cpp 54 | void RpcClient::handleSend() { 55 | Protocol::ptr request; 56 | // 通过 Channel 收集调用请求,如果没有消息时 Channel 内部会挂起该协程等待消息到达 57 | // Channel 被关闭时会退出循环 58 | while (m_chan >> request) { 59 | // 发送请求 60 | m_session->sendProtocol(request); 61 | } 62 | } 63 | ``` 64 | 65 | 现在看一下比较重要的 `call` 方法也就是调用者使用的方法,`call` 里会开启一个 `Channel` 用于接收调用结果,将请求序列号与 `Channel` 关联起来放入 `m_responseHandle`。然后创建调用请求通过 `Channel` 向 ` handleSend` 协程发送请求。之后就通过自己的 `Channel` 挂起协程,等待调用结果。 66 | 67 | ```cpp 68 | template 69 | Result RpcClient::call(Serializer::ptr s) { 70 | Result val; 71 | ... 72 | // 开启一个 Channel 接收调用结果 73 | Channel recvChan(1); 74 | // 本次调用的序列号 75 | uint32_t id = 0; 76 | 77 | { 78 | MutexType::Lock lock(m_mutex); 79 | id = m_sequenceId; 80 | // 将请求序列号与接收 Channel 关联 81 | m_responseHandle.emplace(m_sequenceId, recvChan); 82 | ++m_sequenceId; 83 | } 84 | 85 | // 创建请求协议,附带上请求 id 86 | Protocol::ptr request = 87 | Protocol::Create(Protocol::MsgType::RPC_METHOD_REQUEST,s->toString(), id); 88 | 89 | // 向 send 协程的 Channel 发送消息 90 | m_chan << request; 91 | 92 | ... 93 | 94 | Protocol::ptr response; 95 | // 等待 response,Channel内部会挂起协程,如果有消息到达或者被关闭则会被唤醒 96 | recvChan >> response; 97 | 98 | ... 99 | 100 | Serializer serializer(response->getContent()); 101 | serializer >> val; 102 | 103 | return val; 104 | } 105 | ``` 106 | 107 | 这就是多路复用的设计,并发的调用请求通过 `Channel` 不用显式进行同步操作就能向 `handleSend` 协程发送请求, `handleSend`协程不断收集请求转发给服务器。 108 | 109 | ### 多路分解 110 | ![多路分解](images/多路分解.png) 111 | 112 | 接着讲讲多路分解。多路分解和多路复用就是一个相反的过程,具体就是如何将服务器的响应解析,转发给对应的调用者。 113 | 114 | 同样的,在每个rpc对象连接到服务器时,我们也开启了一个 `handleRecv` 协程用于接收服务器的消息,并且从中解析出响应类型进行对应的处理。 115 | 116 | ```cpp 117 | void RpcClient::handleRecv() { 118 | while (true) { 119 | // 接收响应 120 | Protocol::ptr response = m_session->recvProtocol(); 121 | ... 122 | // 获取响应类型 123 | Protocol::MsgType type = response->getMsgType(); 124 | // 判断响应类型进行对应的处理 125 | switch (type) { 126 | // 心跳处理 127 | case Protocol::MsgType::HEARTBEAT_PACKET: 128 | ... 129 | break; 130 | // 调用结果处理 131 | case Protocol::MsgType::RPC_METHOD_RESPONSE: 132 | handleMethodResponse(response); 133 | break; 134 | ... 135 | default: 136 | ... 137 | break; 138 | } 139 | } 140 | } 141 | ``` 142 | 143 | 我们看一下对服务器返回调用结果的处理。我们先获取该调用结果的序列号,这个序列号标识着一个之前已经发过的调用请求。然后查找该序列号对应的 `Channel` 是否还存在,如果调用超时到达,或者之前的调用请求已经被处理,则忽略本次调用结果。通过序列号获取等待该结果的 `Channel` ,并发送调用结果唤醒调用者,完成多路分解。 144 | 145 | ```cpp 146 | void RpcClient::handleMethodResponse(Protocol::ptr response) { 147 | // 获取该调用结果的序列号 148 | uint32_t id = response->getSequenceId(); 149 | std::map>::iterator it; 150 | 151 | MutexType::Lock lock(m_mutex); 152 | // 查找该序列号的 Channel 是否还存在,如果不存在直接返回 153 | it = m_responseHandle.find(id); 154 | if (it == m_responseHandle.end()) { 155 | return; 156 | } 157 | // 通过序列号获取等待该结果的 Channel 158 | Channel chan = it->second; 159 | // 对该 Channel 发送调用结果唤醒调用者 160 | chan << response; 161 | } 162 | ``` 163 | 164 | ### 最后 165 | 166 | 虽然单连接的rpc实现起来相对复杂一些,要在应用层面要实现多路复用的功能。但资源的利用率远远大于短连接,就性能而言,可发送和处理的消息数也比短连接多得多,这对一个高性能的rpc框架是必备的。 167 | 168 | 最新代码已经更新到GitHub上了 169 | 170 | [zavier-wong/acid: A high performance fiber RPC network framework. 高性能协程RPC网络框架 (github.com)](https://github.com/zavier-wong/acid) -------------------------------------------------------------------------------- /docs/反射实现无侵入式序列化.md: -------------------------------------------------------------------------------- 1 | ## 引言 2 | 由于 cpp 还未提供反射,所以一般项目里序列化里需要实现对应类的序列化,不仅繁琐还容易出错,使用宏也并没有本质差别,都是侵入式的序列化。 3 | 最近看 [yalantinglibs](https://github.com/alibaba/yalantinglibs) 库中 struct_pack 的反射非常有意思,很简单的一些代码就可以实现反射。 4 | 另外这个库的很多实现很 tricky 可以仔细阅读。 5 | 6 | 为了便于理解本文将简化一下实现。 7 | 8 | ## 反射的作用 9 | 对于一个类 10 | ```cpp 11 | struct Foo { 12 | int n; 13 | string str; 14 | }; 15 | ``` 16 | 如果我们想对该类的对象进行序列化,在没有反射的情况下,需要由用户来手动来遍历该类的成员,例如 17 | ```cpp 18 | struct Foo { 19 | int n; 20 | string str; 21 | friend Serializer& operator >> (Serializer& s, Foo& f) { 22 | s >> f.n >> f.str; 23 | return s; 24 | } 25 | friend Serializer& operator << (Serializer& out, Foo& f) { 26 | s << f.n << f.str; 27 | return s; 28 | } 29 | }; 30 | ``` 31 | 这样就可以实现 Foo 类的序列化和反序列化。但正如前文所说,这样不仅繁琐还容易出错。 32 | 33 | 我们希望实现一个非侵入式的序列化,用户只要定义类就行,由框架来完成遍历类的成员并完成序列化和反序列化,直接做到以下效果,这就是反射的作用 34 | 35 | ```cpp 36 | Foo foo; 37 | Serializer s; 38 | // 序列化 39 | s << foo; 40 | // 反序列化 41 | s >> foo; 42 | ``` 43 | 44 | ## 遍历类的成员 45 | 首先最为核心的地方,要想获取类的全部成员,在 cpp 17 里有个简单的方法,就是结构化绑定(structured binding)。 46 | 47 | ```cpp 48 | Foo foo; 49 | auto &&[a1, a2] = foo; 50 | ``` 51 | 52 | 现在 a1 就是对 foo.n 的引用,a2 就是对 foo.str 的引用。 53 | 54 | 简单封装一下,我们需要定义一个高阶函数 VisitMembers,实现 Vistor 模式,其接受两个参数: 55 | 56 | 1. 反射的对象 auto&& object 57 | 58 | 2. 一个函数 visitor,对对象全部字段进行访问、操作,签名为 `void(auto &&...items)` ,其中参数为变参模板 59 | 60 | ```cpp 61 | constexpr decltype(auto) VisitMembers(auto &&object, auto &&visitor) { 62 | using type = std::remove_cvref_t; 63 | constexpr auto Count = MemberCount(); 64 | ... 65 | if constexpr (Count == 0) { 66 | return visitor(); 67 | } 68 | else if constexpr (Count == 1) { 69 | auto &&[a1] = object; 70 | return visitor(a1); 71 | } 72 | else if constexpr (Count == 2) { 73 | auto &&[a1, a2] = object; 74 | return visitor(a1, a2); 75 | } 76 | else if constexpr (Count == 3) { 77 | auto &&[a1, a2, a3] = object; 78 | return visitor(a1, a2, a3); 79 | } 80 | ... 81 | } 82 | ``` 83 | 84 | 代码实现里一直暴力枚举下去。 85 | 86 | VisitMembers 里先获取类的成员数量,然后利用 if constexpr 来编译期生成对应成员数量的结构化绑定,将全部成员转发给 visitor,这就完成了对对象成员的访问。 87 | 88 | 到目前为止都很简单,但有个问题,MemberCount 获取类的成员数量该如何实现,这也是最为魔法的地方。 89 | 90 | ## 获取类的成员数量 91 | MemberCount 的真正实现是 MemberCountImpl 92 | 93 | ```cpp 94 | template 95 | consteval std::size_t MemberCount() { 96 | ... 97 | return MemberCountImpl(); 98 | } 99 | ``` 100 | 101 | MemberCountImpl 实现如下 102 | 103 | ```cpp 104 | struct UniversalType { 105 | template 106 | operator T(); 107 | }; 108 | 109 | template 110 | consteval std::size_t MemberCountImpl() { 111 | if constexpr (requires { 112 | T { 113 | {Args{}}..., 114 | {UniversalType{}} 115 | }; 116 | }) { 117 | return MemberCountImpl(); 118 | } else { 119 | return sizeof...(Args); 120 | } 121 | } 122 | ``` 123 | 124 | 要想理解这个函数必须先理解这个 concept 约束了什么。 125 | 126 | 这里涉及到了一个特性 127 | 128 | ```cpp 129 | struct Foo { 130 | int a; 131 | int b; 132 | int c; 133 | }; 134 | ``` 135 | 对于一个聚合类 Foo,以下初始化方法都是合法的 136 | ```cpp 137 | Foo a{1}; 138 | Foo a{1, 2}; 139 | Foo a{1, 2, 3}; 140 | ``` 141 | 142 | concept 里借助了一个万能类型 UniversalType,UniversalType 中有一个可以隐式转换成任意类的稻草人函数。然后将所有的变参 UniversalType 展开检查初始化类 T 时的参数个数是否合法。 143 | 144 | 第一个分支通过不断构造新的 UniversalType,当 concept 不满足时,说明当前参数的个数就等于类的成员数量。 145 | 146 | ## 序列化实现 147 | 148 | ```cpp 149 | template 150 | Serializer& operator << (const T& i){ 151 | using T = std::remove_cvref_t; 152 | static_assert(!std::is_pointer_v); 153 | if constexpr(std::is_same_v || std::is_same_v || std::is_same_v){ 154 | m_byteArray->writeFint8(t); 155 | } else if constexpr(std::is_same_v){ 156 | m_byteArray->writeFloat(t); 157 | } else if constexpr(std::is_same_v){ 158 | m_byteArray->writeDouble(t); 159 | } else if constexpr(std::is_same_v){ 160 | m_byteArray->writeFint8(t); 161 | } else if constexpr(std::is_same_v){ 162 | m_byteArray->writeFuint8(t); 163 | } else if constexpr(std::is_same_v){ 164 | m_byteArray->writeFint16(t); 165 | } else if constexpr(std::is_same_v){ 166 | m_byteArray->writeFuint16(t); 167 | } else if constexpr(std::is_same_v){ 168 | m_byteArray->writeInt32(t); 169 | } else if constexpr(std::is_same_v){ 170 | m_byteArray->writeUint32(t); 171 | } else if constexpr(std::is_same_v){ 172 | m_byteArray->writeInt64(t); 173 | } else if constexpr(std::is_same_v){ 174 | m_byteArray->writeUint64(t); 175 | } else if constexpr(std::is_same_v){ 176 | m_byteArray->writeStringVint(t); 177 | } else if constexpr(std::is_same_v){ 178 | m_byteArray->writeStringVint(std::string(t)); 179 | } else if constexpr(std::is_same_v){ 180 | m_byteArray->writeStringVint(std::string(t)); 181 | } else if constexpr(std::is_enum_v){ 182 | m_byteArray->writeInt32(static_cast(t)); 183 | } else if constexpr(std::is_class_v) { 184 | static_assert(std::is_aggregate_v); 185 | VisitMembers(t, [&](auto &&...items) { 186 | (void)((*this) << ... << items); 187 | }); 188 | } 189 | return *this; 190 | } 191 | ``` 192 | 193 | 在最后一个if constexpr 里,判断是否为聚合类,然后遍历所有的成员进行序列化。 194 | 195 | 当然,由于不是原生的反射,还是有许多缺陷,比如无法对一个非聚合类进行自动序列化,此时依旧可以通过模板特化来手动实现,例如 196 | ```cpp 197 | class Foo { 198 | private: 199 | int a; 200 | int b; 201 | public: 202 | friend Serializer& operator >> (Serializer& s, Foo& f) { 203 | s >> f.n >> f.str; 204 | return s; 205 | } 206 | friend Serializer& operator << (Serializer& out, Foo& f) { 207 | s << f.n << f.str; 208 | return s; 209 | } 210 | }; 211 | ``` 212 | 213 | ## 最后 214 | 215 | 虽然通过模拟反射,我们完成了无侵入式序列化的目的,但毕竟是模拟出来的,能获取到的元数据及其匮乏,实现起来也蹩脚。 216 | 217 | 期望在不久的将来能看到 cpp 提供足够的反射信息。 -------------------------------------------------------------------------------- /docs/服务订阅与通知.md: -------------------------------------------------------------------------------- 1 | 本篇将讲解框架的发布/订阅功能,这是框架的核心功能之一。发布者可对订阅相同主题的消费者主动推送消息,实现了系统解耦,易于维护。并且通过实时的发布/订阅模式实现自动维护服务列表,当订阅的服务发生了变化,同时更新自己的服务地址缓存。 2 | 3 | ## 接口介绍 4 | 客户端可发起对 key 的订阅,这样在服务端发布消息时可以及时收到消息,并且执行回调函数。回调函数的签名为`void(Serializer)`,在回调函数里可以反序列化服务器发布的数据并处理。 5 | ```cpp 6 | /** 7 | * @brief 订阅消息 8 | * @param[in] key 订阅的key 9 | * @param[in] func 回调函数 10 | */ 11 | template 12 | void RpcConnectionPool::subscribe(const std::string& key, Func func) 13 | ``` 14 | 至于为什么不用`std::function`而采用模板,是因为使用lambda作为入参时,如果捕获了太多的变量会导致``std::function``的内存动态分配,使用模板然后在函数里``std::move``就可以避免内存动态分配。 15 | 16 | 服务端的发布接口比较简单,发布订阅消息,所有订阅了 key 的客户端都可以获得消息。 17 | ```cpp 18 | /** 19 | * @brief 发布消息 20 | * @param[in] key 发布的key 21 | * @param[in] data 支持 Serializer 的都可以发布 22 | */ 23 | template 24 | void RpcServiceRegistry::publish(const std::string& key, T data) 25 | ``` 26 | ## 简单用例 27 | 28 | 实现了 Serializer 序列化的类型都可以直接发布,比如我们想发布一个 vector 直接 publish 就可以,所有订阅了data的客户端都会收到vector。 29 | ```cpp 30 | std::vector vec = { 1, 1, 4, 5, 1, 4}; 31 | // 发布订阅消息,所有订阅了 data 的客户端都可以获得消息 32 | // 框架实现了 STL 容器的序列化,所有可以直接发布 33 | server->publish("data", vec); 34 | ``` 35 | 客户端对 data 进行订阅,收到消息时在回调函数里将数据反序列化回 vector 并打印出来。 36 | ```cpp 37 | // 订阅data,服务端发布消息时会调用回调函数来处理 38 | client->subscribe("data",[](Serializer s){ 39 | std::vector vec; 40 | // 因为vector的序列化框架已经实现了,所以可以直接反序列化 41 | s >> vec; 42 | std::string str; 43 | std::for_each(vec.begin(), vec.end(),[&str](int i) mutable { str += std::to_string(i);}); 44 | LOG_DEBUG << "recv publish: " << str; 45 | }); 46 | ``` 47 | 熟悉完简单的使用方法,接下来就是本文的重点了 48 | 49 | ## 推拉结合的服务列表维护 50 | 当一个已有服务提供者节点下线, 或者一个新的服务提供者节点加入时,订阅对应接口的消费者能及时收到注册中心的通知, 并更新本地的服务地址缓存。 这样后续的服务调用就能避免调用已经下线的节点, 并且能调用到新的服务节点。 51 | 52 | 订阅通常有 pull(拉)和 push(推)两种方式。第一种是客户端定时轮询注册中心拉取开放服务的节点,另一种是注册中心主动推送数据给客户端。 这两种方式各有利弊,本框架则是两种一起使用,采用了推拉结合的方式来维护服务列表。 53 | 54 | 客户端第一次发起 RPC 调用时采用拉取的方式,将注册中心中本服务的所有提供者地址缓存到本地,并订阅了此服务节点的上下线通知。之后则是采用了注册中心主动推送的方式,推送服务节点的上下线以此维护服务列表。 55 | 56 | 下面看看具体的代码 57 | 58 | 我们用一个字符串前缀来区分服务订阅和普通订阅 59 | ```cpp 60 | // 连接池向注册中心订阅的前缀 61 | inline const char* RPC_SERVICE_SUBSCRIBE = "[[rpc service subscribe]]"; 62 | ``` 63 | 在注册中心处理服务注册的同时发布了服务上线的消息 64 | ```cpp 65 | Protocol::ptr RpcServiceRegistry::handleRegisterService(Protocol::ptr p, Address::ptr address) { 66 | std::string serviceAddress = address->toString(); 67 | std::string serviceName = p->getContent(); 68 | ... 69 | // 发布服务上线消息 70 | std::tuple data { true, serviceAddress}; 71 | publish(RPC_SERVICE_SUBSCRIBE + serviceName, data); 72 | ... 73 | return proto; 74 | } 75 | ``` 76 | 在注册中心处理服务下线的同时发布了服务下线的消息 77 | ```cpp 78 | void RpcServiceRegistry::handleUnregisterService(Address::ptr address) { 79 | ... 80 | for (auto& i: its) { 81 | m_services.erase(i); 82 | // 发布服务下线消息 83 | std::tuple data { false, address->toString()}; 84 | publish(RPC_SERVICE_SUBSCRIBE + i->first, data); 85 | } 86 | ... 87 | } 88 | ``` 89 | 在连接池第一次请求服务发现的同时,订阅了该服务的通知,动态维护服务列表。 90 | ```cpp 91 | std::vector RpcConnectionPool::discover(const std::string& name) { 92 | ... 93 | if (!m_subHandle.contains(RPC_SERVICE_SUBSCRIBE + name)) { 94 | // 向注册中心订阅服务变化的消息 95 | subscribe(RPC_SERVICE_SUBSCRIBE + name, [name, this](Serializer s){ 96 | // false 为服务下线,true 为新服务节点上线 97 | bool isNewServer = false; 98 | std::string addr; 99 | s >> isNewServer >> addr; 100 | MutexType::Lock lock(m_connMutex); 101 | if (isNewServer) { 102 | // 一个新的服务提供者节点加入,将服务地址加入服务列表缓存 103 | LOG_DEBUG << "service [ " << name << " : " << addr << " ] join"; 104 | m_serviceCache[name].push_back(addr); 105 | } else { 106 | // 已有服务提供者节点下线 107 | LOG_DEBUG << "service [ " << name << " : " << addr << " ] quit"; 108 | // 清理缓存中断开的连接地址 109 | auto its = m_serviceCache.find(name); 110 | if (its != m_serviceCache.end()) { 111 | std::erase(its->second, addr); 112 | } 113 | } 114 | }); 115 | } 116 | ... 117 | } 118 | ``` 119 | 实现的效果如下,控制台打印出来服务的变化 120 | ``` 121 | service [ add : 127.0.0.1:8080 ] quit 122 | service [ add : 127.0.0.1:8080 ] join 123 | ``` 124 | ## 最后 125 | 通过发布/订阅模式来实现推拉结合的服务列表维护是服务治理的重要手段。 126 | -------------------------------------------------------------------------------- /example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | project(example) 3 | 4 | #add_subdirectory(rpc) 5 | add_subdirectory(kvhttp) -------------------------------------------------------------------------------- /example/kvhttp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | project(example_kvhttp) 3 | set(CMAKE_CXX_COMPILER "/usr/bin/g++-11") 4 | set(CMAKE_VERBOSE_MAKEFILE ON) 5 | set(CMAKE_CXX_FLAGS "-std=c++20 -D__const__= -fPIC -fno-strict-aliasing -Wall ${CMAKE_CXX_FLAGS}") 6 | 7 | include_directories(${PROJECT_SOURCE_DIR}/../../) 8 | link_directories(${PROJECT_SOURCE_DIR}/../../lib) 9 | aux_source_directory(${PROJECT_SOURCE_DIR} SRC_LIST) 10 | 11 | foreach(var ${SRC_LIST}) 12 | string(REGEX REPLACE ".*/" "" var ${var}) 13 | string(REGEX REPLACE ".cpp" "" tgt ${var}) 14 | 15 | add_executable(${tgt} ${var}) 16 | set(LINK_ARGS libgo acid pthread dl yaml-cpp) 17 | 18 | target_link_libraries(${tgt} -Wl,--start-group ${LINK_ARGS} -Wl,--end-group) 19 | endforeach(var) -------------------------------------------------------------------------------- /example/kvhttp/KVHttpServer接口文档.md: -------------------------------------------------------------------------------- 1 | # 分布式 KV 存储 2 | 3 | ## 架构 4 | 启动三个 http 节点组成一个 kv 集群,默认绑定以下地址 5 | ``` 6 | localhost:10001 7 | localhost:10002 8 | localhost:10003 9 | ``` 10 | 启动方法 11 | ```shell 12 | ./test_kvserver 1 13 | ./test_kvserver 2 14 | ./test_kvserver 3 15 | ``` 16 | 17 | 18 | ## 文档说明 19 | 20 | - 每个response都有msg参数,其中,只有msg为OK,其他参数才有效 21 | 22 | - API 使用 POST 请求,向服务器发送json数据 23 | 24 | 25 | ## 请求与响应规范 26 | 27 | - 请求示例 28 | 29 | ```json 30 | { 31 | "command": "put", 32 | "key": "", 33 | "value": "" 34 | } 35 | 36 | ``` 37 | 38 | - 返回示例 39 | 40 | ```json 41 | { 42 | "msg": "OK", 43 | "value": "", 44 | "data": {} 45 | } 46 | ``` 47 | 48 | - 返回msg 49 | 50 | - OK ----正常返回 51 | 52 | - No Key ----没有该key 53 | 54 | - Wrong Leader ----请求的http server不是kv集群的leader 55 | 56 | - Timeout ----请求超时 57 | 58 | - 其他 msg 59 | 60 | ## API详细说明 61 | 62 | #### 查询一条数据 63 | 64 | ``` 65 | POST /kv 66 | // 请求参数 67 | { 68 | "command": "get", 69 | "key": "key" 70 | } 71 | // 响应参数 72 | { 73 | "msg": "OK", 74 | "value": "" 75 | } 76 | ``` 77 | 78 | #### 查询全部数据 79 | 80 | ``` 81 | POST /kv 82 | // 请求参数 83 | { 84 | "command": "dump" 85 | } 86 | // 响应参数 87 | { 88 | "msg": "OK", 89 | "data": { 90 | "key1": "value1", 91 | "key2": "value2", 92 | ... 93 | } 94 | } 95 | ``` 96 | #### 覆盖数据 97 | 98 | ``` 99 | POST /kv 100 | // 请求参数 101 | { 102 | "command": "put", 103 | "key": "key", 104 | "value": "value" 105 | } 106 | // 响应参数 107 | { 108 | "msg": "OK" 109 | } 110 | ``` 111 | #### 追加数据 112 | 113 | ``` 114 | POST /kv 115 | // 请求参数 116 | { 117 | "command": "append", 118 | "key": "key", 119 | "value": "value" 120 | } 121 | // 响应参数 122 | { 123 | "msg": "OK" 124 | } 125 | ``` 126 | #### 删除数据 127 | 128 | ``` 129 | POST /kv 130 | // 请求参数 131 | { 132 | "command": "delete", 133 | "key": "key" 134 | } 135 | // 响应参数 136 | { 137 | "msg": "OK" 138 | } 139 | ``` 140 | #### 清空数据 141 | 142 | ``` 143 | POST /kv 144 | // 请求参数 145 | { 146 | "command": "clear" 147 | } 148 | // 响应参数 149 | { 150 | "msg": "OK" 151 | } 152 | ``` 153 | -------------------------------------------------------------------------------- /example/kvhttp/kvhttp_server.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2022/12/6. 3 | // 4 | 5 | #include "acid/raft/persister.h" 6 | #include "acid/kvraft/kvserver.h" 7 | #include "acid/http/http_server.h" 8 | #include "acid/http/servlets/kvstore_servlet.h" 9 | 10 | using namespace acid; 11 | using namespace http; 12 | using namespace raft; 13 | using namespace kvraft; 14 | 15 | int64_t id; 16 | 17 | std::map kv_servers = { 18 | {1, "localhost:7001"}, 19 | {2, "localhost:7002"}, 20 | {3, "localhost:7003"}, 21 | }; 22 | 23 | std::map http_servers = { 24 | {1, "localhost:10001"}, 25 | {2, "localhost:10002"}, 26 | {3, "localhost:10003"}, 27 | }; 28 | 29 | void Main() { 30 | Persister::ptr persister = std::make_shared(fmt::format("kvhttp-{}", id)); 31 | KVServer::ptr kv = std::make_shared(kv_servers, id, persister); 32 | Address::ptr address = Address::LookupAny(http_servers[id]); 33 | http::HttpServer server(true); 34 | server.getServletDispatch()->addServlet("/kv", std::make_shared(kv)); 35 | server.getServletDispatch()->addServlet("/info",[](HttpRequest::ptr request, HttpResponse::ptr response, HttpSession::ptr session) ->uint32_t { 36 | response->setBody("

This is a kvstore

"); 37 | return 0; 38 | }); 39 | go [kv] { 40 | kv->start(); 41 | }; 42 | while (!server.bind(address)){ 43 | sleep(1); 44 | } 45 | server.start(); 46 | } 47 | 48 | // 启动方法 49 | // ./kvhttp_server 1 50 | // ./kvhttp_server 2 51 | // ./kvhttp_server 3 52 | 53 | int main(int argc, char** argv) { 54 | if (argc <= 1) { 55 | SPDLOG_ERROR("please input kvhttp id"); 56 | id = 1; 57 | } else { 58 | SPDLOG_INFO("argv[1] = {}", argv[1]); 59 | id = std::stoll(argv[1]); 60 | } 61 | go Main; 62 | co_sched.Start(); 63 | } 64 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | project(tests) 3 | add_subdirectory(http) 4 | add_subdirectory(kvraft) 5 | add_subdirectory(raft) 6 | add_subdirectory(rpc) 7 | add_subdirectory(test) -------------------------------------------------------------------------------- /tests/http/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | project(tests_http) 3 | set(CMAKE_CXX_COMPILER "/usr/bin/g++-11") 4 | set(CMAKE_VERBOSE_MAKEFILE ON) 5 | set(CMAKE_CXX_FLAGS "-std=c++20 -D__const__= -fPIC -fno-strict-aliasing -Wall ${CMAKE_CXX_FLAGS}") 6 | 7 | include_directories(${PROJECT_SOURCE_DIR}/../../) 8 | link_directories(${PROJECT_SOURCE_DIR}/../../lib) 9 | 10 | aux_source_directory(${PROJECT_SOURCE_DIR} SRC_LIST) 11 | 12 | foreach(var ${SRC_LIST}) 13 | string(REGEX REPLACE ".*/" "" var ${var}) 14 | string(REGEX REPLACE ".cpp" "" tgt ${var}) 15 | 16 | add_executable(${tgt} ${var}) 17 | set(LINK_ARGS libgo acid pthread dl yaml-cpp) 18 | target_link_libraries(${tgt} -Wl,--start-group ${LINK_ARGS} -Wl,--end-group) 19 | endforeach(var) 20 | 21 | -------------------------------------------------------------------------------- /tests/http/test_http.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2021/12/12. 3 | // 4 | #include 5 | #include "acid/http/http.h" 6 | 7 | void test_request(){ 8 | acid::http::HttpRequest h; 9 | //h.setPath("/index.h"); 10 | h.setHeader("host","vvip.icu"); 11 | h.setBody("Hello, World"); 12 | SPDLOG_INFO(h.toString()); 13 | } 14 | void test_response(){ 15 | acid::http::HttpResponse r; 16 | r.setHeader("host","vvip.icu"); 17 | r.setBody("Hello, World"); 18 | r.setStatus(404); 19 | SPDLOG_INFO(r.toString()); 20 | } 21 | int main(){ 22 | test_response; 23 | } -------------------------------------------------------------------------------- /tests/http/test_http_connection.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2021/12/18. 3 | // 4 | 5 | #include 6 | #include "acid/http/http_connection.h" 7 | 8 | void run(){ 9 | acid::Address::ptr addr = acid::IPAddress::Create("www.baidu.com", 80); 10 | if(!addr) { 11 | SPDLOG_INFO("get addr error"); 12 | return; 13 | } 14 | 15 | acid::Socket::ptr sock = acid::Socket::CreateTCP(addr); 16 | bool rt = sock->connect(addr); 17 | if(!rt) { 18 | SPDLOG_INFO("connect {} failed", addr->toString()); 19 | return; 20 | } 21 | 22 | acid::http::HttpConnection::ptr conn(new acid::http::HttpConnection(sock)); 23 | acid::http::HttpRequest::ptr req(new acid::http::HttpRequest); 24 | req->setPath("/index"); 25 | req->setHeader("host", "www.baidu.com"); 26 | SPDLOG_INFO("req: \n{}", req->toString()); 27 | int n = conn->sendRequest(req); 28 | SPDLOG_INFO(n); 29 | auto res = conn->recvResponse(); 30 | if(!res) { 31 | SPDLOG_INFO("recvResponse {} failed", addr->toString()); 32 | return; 33 | } 34 | SPDLOG_INFO(res->toString()); 35 | 36 | } 37 | void test_request() { 38 | acid::http::HttpResult::ptr result = acid::http::HttpConnection::DoGet("www.baidu.com"); 39 | //acid::http::HttpConnection::DoGet("localhost:8080/index.html"); 40 | SPDLOG_INFO(result->toString()); 41 | } 42 | void test_pool(){ 43 | acid::http::HttpConnectionPool::ptr pool(new acid::http::HttpConnectionPool( 44 | "www.baidu.com", "", 80, false, 10, 1000 * 30, 5)); 45 | auto r = pool->doGet("/", -1, {{"connection", "keep-alive"}}); 46 | SPDLOG_INFO(r->toString()); 47 | } 48 | int main(){ 49 | run(); 50 | SPDLOG_WARN("=================="); 51 | test_request(); 52 | SPDLOG_WARN("=================="); 53 | test_pool(); 54 | return 0; 55 | } 56 | -------------------------------------------------------------------------------- /tests/http/test_http_parse.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2021/12/11. 3 | // 4 | #include 5 | #include 6 | #include "acid/http/parse.h" 7 | using namespace acid; 8 | 9 | void test_response_parse(){ 10 | acid::http::HttpResponseParser::ptr parser(new acid::http::HttpResponseParser()); 11 | char res[100] = "HTTP/1.0 404 NOT FOUND\r\nContent-Length: 23330\r\nDate: Thu,08 Mar 202107:17:51 GMT\r\n\r\n"; 12 | parser->execute(res, strlen(res)); 13 | SPDLOG_INFO(parser->getData()->toString()); 14 | } 15 | const char test_request_data[] = "POST / HTTP/1.1\r\n" 16 | "Host: www.acid.icu\r\n" 17 | "Content-Length: 10\r\n\r\n"; 18 | 19 | void test_request() { 20 | acid::http::HttpRequestParser parser; 21 | std::string tmp = test_request_data; 22 | size_t s = parser.execute(&tmp[0], tmp.size()); 23 | SPDLOG_INFO("execute rt={} has_error={} is_finished={} total={} content_length={}", 24 | s, parser.hasError(), parser.isFinished(), tmp.size(), parser.getContentLength()); 25 | tmp.resize(tmp.size() - s); 26 | SPDLOG_INFO(parser.getData()->toString()); 27 | SPDLOG_INFO(tmp); 28 | } 29 | 30 | const char test_response_data[] = "HTTP/1.1 200 OK\r\n" 31 | "Date: Tue, 04 Jun 2050 15:43:56 GMT\r\n" 32 | "Server: Apache\r\n" 33 | "Last-Modified: Tue, 12 Jan 2010 13:48:00 GMT\r\n" 34 | "ETag: \"51-47cf7e6ee8400\"\r\n" 35 | "Accept-Ranges: bytes\r\n" 36 | "Content-Length: 81\r\n" 37 | "Cache-Control: max-age=86400\r\n" 38 | "Expires: Wed, 05 Jun 2019 15:43:56 GMT\r\n" 39 | "Connection: Close\r\n" 40 | "Content-Type: text/html\r\n\r\n" 41 | "\r\n" 42 | "\r\n" 43 | "\r\n"; 44 | 45 | void test_response() { 46 | acid::http::HttpResponseParser parser; 47 | std::string tmp = test_response_data; 48 | size_t s = parser.execute(&tmp[0], tmp.size()); 49 | SPDLOG_INFO("execute rt={} has_error={} is_finished={} total={} content_length={} tmp[s]={}", 50 | s, parser.hasError(), parser.isFinished(), tmp.size(), parser.getContentLength(), tmp[s]); 51 | 52 | tmp.resize(tmp.size() - s); 53 | 54 | SPDLOG_INFO(parser.getData()->toString()); 55 | SPDLOG_INFO(tmp); 56 | } 57 | int main(){ 58 | test_request(); 59 | test_response(); 60 | } -------------------------------------------------------------------------------- /tests/http/test_http_server.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2021/12/15. 3 | // 4 | #include 5 | #include "acid/http/http_server.h" 6 | using namespace acid; 7 | void Main() { 8 | // GetLogInstance()->set_level(spdlog::level::off); 9 | 10 | Address::ptr address = Address::LookupAny("0.0.0.0:8081"); 11 | http::HttpServer server(true); 12 | // server.getServletDispatch()->addGlobServlet("/*", std::make_shared("/mnt/c/Users/zavier/Desktop/acid/static")); 13 | server.getServletDispatch()->addServlet("/a",[](http::HttpRequest::ptr request 14 | , http::HttpResponse::ptr response 15 | , http::HttpSession::ptr session) ->uint32_t { 16 | //std::string str = request->toString(); 17 | response->setBody("hello world"); 18 | //response->setBody(str + "

hello world

"); 19 | //response->setContentType(http::HttpContentType::TEXT_HTML); 20 | return 0; 21 | }); 22 | 23 | server.getServletDispatch()->addServlet("/add",[](http::HttpRequest::ptr request 24 | , http::HttpResponse::ptr response 25 | , http::HttpSession::ptr session) ->uint32_t { 26 | int a = request->getParamAs("a", 0ll); 27 | int b = request->getParamAs("b", 0ll); 28 | response->setBody(std::to_string(a) + " + " + std::to_string(b) + "=" + std::to_string(a+b)); 29 | return 0; 30 | }); 31 | 32 | server.getServletDispatch()->addServlet("/json",[](http::HttpRequest::ptr request 33 | , http::HttpResponse::ptr response 34 | , http::HttpSession::ptr session) ->uint32_t { 35 | nlohmann::json json; 36 | std::stringstream ss; 37 | switch (request->getMethod()) { 38 | case http::HttpMethod::GET: 39 | json["bool"] = true; 40 | json["number"] = 114514; 41 | json["float"] = M_PI; 42 | json["string"] = "abcdefg"; 43 | response->setJson(json); 44 | break; 45 | case http::HttpMethod::POST: 46 | json = request->getJson(); 47 | ss << json; 48 | SPDLOG_INFO(json.type_name()); 49 | SPDLOG_INFO(ss.str()); 50 | response->setJson(json); 51 | break; 52 | default: 53 | break; 54 | } 55 | return 0; 56 | }); 57 | 58 | while (!server.bind(address)){ 59 | sleep(1); 60 | } 61 | 62 | server.start(); 63 | } 64 | 65 | int main() { 66 | go Main; 67 | co_sched.Start(0); 68 | } -------------------------------------------------------------------------------- /tests/kvraft/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | project(tests_kvraft) 3 | set(CMAKE_CXX_COMPILER "/usr/bin/g++-11") 4 | set(CMAKE_VERBOSE_MAKEFILE ON) 5 | set(CMAKE_CXX_FLAGS "-std=c++20 -D__const__= -fPIC -fno-strict-aliasing -Wall ${CMAKE_CXX_FLAGS}") 6 | 7 | include_directories(${PROJECT_SOURCE_DIR}/../../) 8 | link_directories(${PROJECT_SOURCE_DIR}/../../lib) 9 | 10 | aux_source_directory(${PROJECT_SOURCE_DIR} SRC_LIST) 11 | 12 | foreach(var ${SRC_LIST}) 13 | string(REGEX REPLACE ".*/" "" var ${var}) 14 | string(REGEX REPLACE ".cpp" "" tgt ${var}) 15 | 16 | add_executable(${tgt} ${var}) 17 | set(LINK_ARGS libgo acid pthread dl yaml-cpp fmt) 18 | target_link_libraries(${tgt} -Wl,--start-group ${LINK_ARGS} -Wl,--end-group) 19 | endforeach(var) 20 | 21 | -------------------------------------------------------------------------------- /tests/kvraft/test_kvclient.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2022/12/4. 3 | // 4 | 5 | #include "acid/kvraft/kvclient.h" 6 | 7 | using namespace acid; 8 | using namespace kvraft; 9 | 10 | class MyListener : public PubsubListener { 11 | public: 12 | void onSubscribe(const std::string& channel) override { 13 | SPDLOG_INFO("onSubscribe {}", channel); 14 | } 15 | void onMessage(const std::string& channel, const std::string& message) override { 16 | SPDLOG_INFO("Listener channel: [{}], message: [{}]", channel, message); 17 | } 18 | void onPatternMessage(const std::string& pattern, const std::string& channel, const std::string& message) override { 19 | SPDLOG_INFO("Listener pattern: [{}], channel: [{}], message: [{}]", pattern, channel, message); 20 | } 21 | }; 22 | 23 | std::map servers = { 24 | {1, "localhost:7001"}, 25 | {2, "localhost:7002"}, 26 | {3, "localhost:7003"}, 27 | }; 28 | 29 | void Main() { 30 | sleep(2); 31 | KVClient::ptr client = std::make_shared(servers); 32 | client->setTimeout(-1); 33 | std::string val; 34 | 35 | client->Get("name", val); 36 | SPDLOG_INFO("name: {}", val); 37 | 38 | client->Put("name", "zavier"); 39 | client->Get("name", val); 40 | SPDLOG_INFO("name: {}", val); 41 | 42 | client->Append("name", " wong"); 43 | client->Get("name", val); 44 | SPDLOG_INFO("name: {}", val); 45 | 46 | client->Put("name", ""); 47 | client->Get("name", val); 48 | SPDLOG_INFO("name: {}", val); 49 | 50 | for (int i = 0; ;++i) { 51 | if (i % 10 == 0) { 52 | client->Put("save data", ""); 53 | } 54 | client->Append("save data", std::to_string(i)); 55 | client->Get("save data", val); 56 | SPDLOG_INFO("save data: {}", val); 57 | sleep(2); 58 | } 59 | } 60 | 61 | void subscribe() { 62 | go [] { 63 | KVClient::ptr client = std::make_shared(servers); 64 | client->setTimeout(-1); 65 | std::string val; 66 | client->Get("name", val); 67 | client->subscribe(std::make_shared(), TOPIC_KEYSPACE + "save data"); 68 | SPDLOG_WARN("unsubscribe"); 69 | }; 70 | 71 | go [] { 72 | KVClient::ptr client = std::make_shared(servers); 73 | client->setTimeout(-1); 74 | std::string val; 75 | client->Get("name", val); 76 | client->patternSubscribe(std::make_shared(), TOPIC_ALL_KEYEVENTS); 77 | SPDLOG_WARN("unsubscribe"); 78 | }; 79 | }; 80 | 81 | int main() { 82 | go subscribe; 83 | go Main; 84 | co_sched.Start(); 85 | } -------------------------------------------------------------------------------- /tests/kvraft/test_kvserver.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2022/12/4. 3 | // 4 | #include 5 | #include "acid/kvraft/kvserver.h" 6 | 7 | using namespace acid; 8 | using namespace rpc; 9 | using namespace kvraft; 10 | 11 | int64_t id; 12 | 13 | std::map peers = { 14 | {1, "localhost:7001"}, 15 | {2, "localhost:7002"}, 16 | {3, "localhost:7003"}, 17 | }; 18 | 19 | void Main() { 20 | // raft状态和快照存放的地方 21 | Persister::ptr persister = std::make_shared(fmt::format("kvserver-{}", id)); 22 | KVServer server(peers, id, persister); 23 | server.start(); 24 | } 25 | 26 | // 启动方法 27 | // ./test_kvserver 1 28 | // ./test_kvserver 2 29 | // ./test_kvserver 3 30 | // 只要启动任意两个节点就可以运行分布式KV存储 31 | 32 | int main(int argc, char** argv) { 33 | if (argc <= 1) { 34 | SPDLOG_ERROR("please input kvserver id"); 35 | return 0; 36 | } else { 37 | SPDLOG_INFO("argv[1] = {}", argv[1]); 38 | id = std::stoll(argv[1]); 39 | } 40 | go Main; 41 | co_sched.Start(); 42 | } -------------------------------------------------------------------------------- /tests/raft/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | project(tests_rpc) 3 | set(CMAKE_CXX_COMPILER "/usr/bin/g++-11") 4 | set(CMAKE_VERBOSE_MAKEFILE ON) 5 | set(CMAKE_CXX_FLAGS "-std=c++20 -D__const__= -fPIC -fno-strict-aliasing -Wall ${CMAKE_CXX_FLAGS}") 6 | 7 | include_directories(${PROJECT_SOURCE_DIR}/../../) 8 | link_directories(${PROJECT_SOURCE_DIR}/../../lib) 9 | 10 | aux_source_directory(${PROJECT_SOURCE_DIR} SRC_LIST) 11 | 12 | foreach(var ${SRC_LIST}) 13 | string(REGEX REPLACE ".*/" "" var ${var}) 14 | string(REGEX REPLACE ".cpp" "" tgt ${var}) 15 | 16 | add_executable(${tgt} ${var}) 17 | set(LINK_ARGS libgo acid pthread dl yaml-cpp) 18 | target_link_libraries(${tgt} -Wl,--start-group ${LINK_ARGS} -Wl,--end-group) 19 | endforeach(var) 20 | 21 | -------------------------------------------------------------------------------- /tests/raft/test_persister.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2022/11/28. 3 | // 4 | 5 | #include "acid/raft/persister.h" 6 | 7 | using namespace acid::raft; 8 | 9 | int main() { 10 | Persister persister("."); 11 | 12 | HardState hs; 13 | hs.term = 1; 14 | hs.vote = 2; 15 | hs.commit = 3; 16 | 17 | std::vector ents; 18 | Entry entry; 19 | entry.index = 1; 20 | entry.term = 2; 21 | entry.data = "entry"; 22 | ents.push_back(entry); 23 | 24 | Snapshot::ptr snap = std::make_shared(); 25 | snap->metadata.term = 1; 26 | snap->metadata.index = 1; 27 | snap->data = "snap"; 28 | 29 | // 持久化存储 30 | persister.persist(hs, ents, snap); 31 | 32 | if (!persister.loadHardState() || !persister.loadEntries()) { 33 | SPDLOG_CRITICAL("persist fail, maybe path error"); 34 | return 0; 35 | } 36 | 37 | hs = *persister.loadHardState(); 38 | ents = *persister.loadEntries(); 39 | snap = persister.loadSnapshot(); 40 | 41 | SPDLOG_INFO("hs.term {}, hs.vote {}, hs.commit {}", hs.term, hs.vote, hs.commit); 42 | for (auto ent: ents) { 43 | SPDLOG_INFO("entry.index {}, entry.term {}, entry.data {}", entry.index, entry.term, entry.data); 44 | } 45 | SPDLOG_INFO("snap->metadata.term {}, snap->metadata.index {}, snap->data {}", snap->metadata.term, snap->metadata.index, snap->data); 46 | } -------------------------------------------------------------------------------- /tests/raft/test_raft_node_1.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2022/6/29. 3 | // 4 | 5 | #include "acid/raft/raft_node.h" 6 | 7 | using namespace acid; 8 | using namespace acid::raft; 9 | 10 | std::map peers = { 11 | {1, "localhost:7001"}, 12 | {2, "localhost:7002"}, 13 | {3, "localhost:7003"}, 14 | }; 15 | 16 | void Main() { 17 | int64_t id = 1; 18 | Persister::ptr persister = std::make_shared(fmt::format("raft-node-{}", id)); 19 | co::co_chan applyChan; 20 | RaftNode node(peers, id, persister, applyChan); 21 | Address::ptr addr = Address::LookupAny(peers[node.getNodeId()]); 22 | node.bind(addr); 23 | go [&node, id] { 24 | for (int i = 0; ; ++i) { 25 | if (node.isLeader()) { 26 | node.propose(fmt::format("Node[{}] propose {}", id, i)); 27 | } 28 | sleep(5); 29 | } 30 | }; 31 | go [applyChan, id, &node] { 32 | // 接收raft达成共识的日志 33 | ApplyMsg msg; 34 | while (applyChan.pop(msg)) { 35 | if (msg.type == ApplyMsg::ENTRY && msg.index % 10 == 0 && node.isLeader()) { 36 | // 十条日志做一次快照 37 | node.persistStateAndSnapshot(msg.index, fmt::format("Node[{}] create snapshot, index {}", id, msg.index)); 38 | } 39 | switch (msg.type) { 40 | case ApplyMsg::ENTRY: 41 | SPDLOG_INFO("entry-> index: {}, term: {}, data: {}", msg.index, msg.term, msg.data); 42 | break; 43 | case ApplyMsg::SNAPSHOT: 44 | SPDLOG_INFO("snapshot-> index: {}, term: {}, data: {}", msg.index, msg.term, msg.data); 45 | break; 46 | } 47 | } 48 | }; 49 | // 启动raft节点 50 | node.start(); 51 | } 52 | 53 | int main() { 54 | go Main; 55 | co_sched.Start(); 56 | } -------------------------------------------------------------------------------- /tests/raft/test_raft_node_2.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2022/11/30. 3 | // 4 | 5 | #include "acid/raft/raft_node.h" 6 | 7 | using namespace acid; 8 | using namespace acid::raft; 9 | 10 | std::map peers = { 11 | {1, "localhost:7001"}, 12 | {2, "localhost:7002"}, 13 | {3, "localhost:7003"}, 14 | }; 15 | 16 | void Main() { 17 | int64_t id = 2; 18 | Persister::ptr persister = std::make_shared(fmt::format("raft-node-{}", id)); 19 | co::co_chan applyChan; 20 | RaftNode node(peers, id, persister, applyChan); 21 | Address::ptr addr = Address::LookupAny(peers[node.getNodeId()]); 22 | node.bind(addr); 23 | go [&node, id] { 24 | for (int i = 0; ; ++i) { 25 | if (node.isLeader()) { 26 | node.propose(fmt::format("Node[{}] propose {}", id, i)); 27 | } 28 | sleep(5); 29 | } 30 | }; 31 | go [applyChan] { 32 | // 接收raft达成共识的日志 33 | ApplyMsg msg; 34 | while (applyChan.pop(msg)) { 35 | switch (msg.type) { 36 | case ApplyMsg::ENTRY: 37 | SPDLOG_INFO("entry-> index: {}, term: {}, data: {}", msg.index, msg.term, msg.data); 38 | break; 39 | case ApplyMsg::SNAPSHOT: 40 | SPDLOG_INFO("snapshot-> index: {}, term: {}, data: {}", msg.index, msg.term, msg.data); 41 | break; 42 | } 43 | } 44 | }; 45 | // 启动raft节点 46 | node.start(); 47 | } 48 | 49 | int main() { 50 | go Main; 51 | co_sched.Start(); 52 | } -------------------------------------------------------------------------------- /tests/raft/test_raft_node_3.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2022/11/30. 3 | // 4 | 5 | #include "acid/raft/raft_node.h" 6 | 7 | using namespace acid; 8 | using namespace acid::raft; 9 | 10 | std::map peers = { 11 | {1, "localhost:7001"}, 12 | {2, "localhost:7002"}, 13 | {3, "localhost:7003"}, 14 | }; 15 | 16 | void Main() { 17 | int64_t id = 3; 18 | Persister::ptr persister = std::make_shared(fmt::format("raft-node-{}", id)); 19 | co::co_chan applyChan; 20 | RaftNode node(peers, id, persister, applyChan); 21 | Address::ptr addr = Address::LookupAny(peers[node.getNodeId()]); 22 | node.bind(addr); 23 | go [&node, id] { 24 | for (int i = 0; ; ++i) { 25 | if (node.isLeader()) { 26 | node.propose(fmt::format("Node[{}] propose {}", id, i)); 27 | } 28 | sleep(5); 29 | } 30 | }; 31 | go [applyChan] { 32 | // 接收raft达成共识的日志 33 | ApplyMsg msg; 34 | while (applyChan.pop(msg)) { 35 | switch (msg.type) { 36 | case ApplyMsg::ENTRY: 37 | SPDLOG_INFO("entry-> index: {}, term: {}, data: {}", msg.index, msg.term, msg.data); 38 | break; 39 | case ApplyMsg::SNAPSHOT: 40 | SPDLOG_INFO("snapshot-> index: {}, term: {}, data: {}", msg.index, msg.term, msg.data); 41 | break; 42 | } 43 | } 44 | }; 45 | // 启动raft节点 46 | node.start(); 47 | } 48 | 49 | int main() { 50 | go Main; 51 | co_sched.Start(); 52 | } -------------------------------------------------------------------------------- /tests/rpc/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | project(tests_rpc) 3 | set(CMAKE_CXX_COMPILER "/usr/bin/g++-11") 4 | set(CMAKE_VERBOSE_MAKEFILE ON) 5 | set(CMAKE_CXX_FLAGS "-std=c++20 -D__const__= -fPIC -fno-strict-aliasing -Wall ${CMAKE_CXX_FLAGS}") 6 | 7 | include_directories(${PROJECT_SOURCE_DIR}/../../) 8 | link_directories(${PROJECT_SOURCE_DIR}/../../lib) 9 | 10 | aux_source_directory(${PROJECT_SOURCE_DIR} SRC_LIST) 11 | 12 | foreach(var ${SRC_LIST}) 13 | string(REGEX REPLACE ".*/" "" var ${var}) 14 | string(REGEX REPLACE ".cpp" "" tgt ${var}) 15 | 16 | add_executable(${tgt} ${var}) 17 | set(LINK_ARGS libgo acid pthread dl yaml-cpp) 18 | target_link_libraries(${tgt} -Wl,--start-group ${LINK_ARGS} -Wl,--end-group) 19 | endforeach(var) 20 | 21 | -------------------------------------------------------------------------------- /tests/rpc/test_route_strategy.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2022/1/15. 3 | // 4 | #include 5 | #include "acid/rpc/route_strategy.h" 6 | 7 | std::vector list{1, 2, 3, 4, 5}; 8 | 9 | void test_random() { 10 | acid::rpc::RouteStrategy::ptr strategy = 11 | acid::rpc::RouteEngine::queryStrategy(acid::rpc::Strategy::Random); 12 | SPDLOG_INFO("random"); 13 | for ([[maybe_unused]] auto i: list) { 14 | auto a = strategy->select(list); 15 | SPDLOG_INFO(a); 16 | } 17 | } 18 | 19 | void test_poll() { 20 | acid::rpc::RouteStrategy::ptr strategy = 21 | acid::rpc::RouteEngine::queryStrategy(acid::rpc::Strategy::Polling); 22 | SPDLOG_INFO("Poll"); 23 | for ([[maybe_unused]] auto i: list) { 24 | auto a = strategy->select(list); 25 | SPDLOG_INFO(a); 26 | } 27 | } 28 | void test_hash() { 29 | acid::rpc::RouteStrategy::ptr strategy = 30 | acid::rpc::RouteEngine::queryStrategy(acid::rpc::Strategy::HashIP); 31 | SPDLOG_INFO("Hash"); 32 | for ([[maybe_unused]] auto i: list) { 33 | auto a = strategy->select(list); 34 | SPDLOG_INFO(a); 35 | } 36 | } 37 | int main() { 38 | test_random(); 39 | test_poll(); 40 | test_hash(); 41 | } -------------------------------------------------------------------------------- /tests/rpc/test_rpc_client.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2022/1/13. 3 | // 4 | 5 | #include 6 | 7 | #include "acid/rpc/rpc_client.h" 8 | 9 | using namespace acid; 10 | using namespace rpc; 11 | 12 | void test1() { 13 | Address::ptr address = Address::LookupAny("127.0.0.1:9000"); 14 | RpcClient::ptr client(new RpcClient()); 15 | if (!client->connect(address)) { 16 | SPDLOG_INFO(address->toString()); 17 | return; 18 | } 19 | int n=0; 20 | while (n!=1000) { 21 | n++; 22 | client->callback("add", 0, n ,[](Result res) { 23 | SPDLOG_INFO(res.toString()); 24 | }); 25 | } 26 | auto rt = client->call("add", 0, n); 27 | SPDLOG_INFO(rt.toString()); 28 | sleep(3); 29 | //client->close(); 30 | client->setTimeout(1000); 31 | auto sl = client->call("sleep"); 32 | SPDLOG_INFO("sleep 2s {}", sl.toString()); 33 | co_sched.Stop(); 34 | } 35 | 36 | class ChannelListener : public PubsubListener { 37 | public: 38 | ChannelListener(RpcClient::ptr client) : m_client(std::move(client)) {} 39 | void onSubscribe(const std::string& channel) override { 40 | SPDLOG_INFO("subscribe channel {}", channel); 41 | } 42 | void onUnsubscribe(const std::string& channel) override { 43 | SPDLOG_INFO("unsubscribe channel {}", channel); 44 | } 45 | void onMessage(const std::string& channel, const std::string& message) override { 46 | if (m_n++ > 5) { 47 | // 取消订阅 48 | m_client->unsubscribe(channel); 49 | } 50 | SPDLOG_INFO("channel {}, message {}", channel, message); 51 | } 52 | private: 53 | int m_n = 0; 54 | RpcClient::ptr m_client; 55 | }; 56 | 57 | void subscribe() { 58 | Address::ptr address = Address::LookupAny("127.0.0.1:9000"); 59 | RpcClient::ptr client = std::make_shared(); 60 | 61 | if (!client->connect(address)) { 62 | SPDLOG_WARN(address->toString()); 63 | return; 64 | } 65 | 66 | if (client->subscribe(std::make_shared(client), "number")) { 67 | SPDLOG_WARN("unsubscribe"); 68 | } else { 69 | SPDLOG_WARN("connection error"); 70 | } 71 | } 72 | 73 | class PatternListener : public PubsubListener { 74 | public: 75 | PatternListener(RpcClient::ptr client) : m_client(std::move(client)) {} 76 | void onPatternSubscribe(const std::string& pattern) override { 77 | SPDLOG_INFO("subscribe pattern {}", pattern); 78 | } 79 | void onPatternUnsubscribe(const std::string& pattern) override { 80 | SPDLOG_INFO("unsubscribe pattern {}", pattern); 81 | } 82 | void onPatternMessage(const std::string& pattern, const std::string& channel, const std::string& message) override { 83 | if (m_n++ > 5) { 84 | // 取消订阅 85 | m_client->patternUnsubscribe(pattern); 86 | } 87 | SPDLOG_INFO("pattern {}, channel {}, message {}", pattern, channel, message); 88 | } 89 | private: 90 | int m_n = 0; 91 | RpcClient::ptr m_client; 92 | }; 93 | 94 | void pattern_subscribe() { 95 | Address::ptr address = Address::LookupAny("127.0.0.1:9000"); 96 | RpcClient::ptr client = std::make_shared(); 97 | 98 | if (!client->connect(address)) { 99 | SPDLOG_WARN(address->toString()); 100 | return; 101 | } 102 | 103 | // 模式订阅 104 | if (client->patternSubscribe(std::make_shared(client), "number_*")) { 105 | SPDLOG_WARN("unsubscribe"); 106 | } else { 107 | SPDLOG_WARN("connection error"); 108 | } 109 | } 110 | 111 | void publish() { 112 | Address::ptr address = Address::LookupAny("127.0.0.1:9000"); 113 | RpcClient::ptr client = std::make_shared(); 114 | 115 | if (!client->connect(address)) { 116 | SPDLOG_WARN(address->toString()); 117 | return; 118 | } 119 | sleep(1); 120 | int n = 0; 121 | while (!client->isClose() && n < 10) { 122 | SPDLOG_INFO("publish {}", n); 123 | client->publish("number", std::to_string(n)); 124 | client->publish("number_" + std::to_string(n), std::to_string(n)); 125 | n++; 126 | sleep(3); 127 | } 128 | co_sched.Stop(); 129 | } 130 | 131 | void test_pubsub() { 132 | go subscribe; 133 | go pattern_subscribe; 134 | go publish; 135 | } 136 | 137 | int main() { 138 | //go test1; 139 | go test_pubsub; 140 | co_sched.Start(0); 141 | } 142 | -------------------------------------------------------------------------------- /tests/rpc/test_rpc_server.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2022/1/13. 3 | // 4 | 5 | #include "acid/rpc/rpc_server.h" 6 | 7 | using namespace acid; 8 | using namespace rpc; 9 | 10 | int add(int a,int b){ 11 | return a + b; 12 | } 13 | std::string getStr() { 14 | return "hello world"; 15 | } 16 | std::string CatString(std::vector v){ 17 | std::string res; 18 | for(auto& s:v){ 19 | res+=s; 20 | } 21 | return res; 22 | } 23 | void Main() { 24 | int port = 9000; 25 | 26 | Address::ptr address = IPv4Address::Create("127.0.0.1",port); 27 | Address::ptr registry = Address::LookupAny("127.0.0.1:8000"); 28 | RpcServer server; 29 | std::string str = "lambda"; 30 | 31 | server.registerMethod("add",add); 32 | server.registerMethod("getStr",getStr); 33 | server.registerMethod("CatString", CatString); 34 | server.registerMethod("sleep", []{ 35 | sleep(2); 36 | }); 37 | while (!server.bind(address)){ 38 | sleep(1); 39 | } 40 | server.bindRegistry(registry); 41 | server.start(); 42 | } 43 | 44 | int main() { 45 | go Main; 46 | co_sched.Start(); 47 | } -------------------------------------------------------------------------------- /tests/rpc/test_serializer.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2022/1/13. 3 | // 4 | 5 | #include "acid/rpc/serializer.h" 6 | #include "acid/rpc/rpc.h" 7 | #include "acid/common/config.h" 8 | #include 9 | 10 | void test1() { 11 | acid::rpc::Serializer s; 12 | acid::rpc::Result r, t; 13 | r.setCode(acid::rpc::RPC_SUCCESS); 14 | r.setVal(23); 15 | s << r; 16 | s.reset(); 17 | s >> t; 18 | SPDLOG_INFO(t.toString()); 19 | } 20 | void test2() { 21 | std::list a; 22 | a.push_back("aa"); 23 | a.push_back("bb"); 24 | acid::rpc::Serializer s; 25 | s << a; 26 | s.reset(); 27 | std::list b; 28 | s >> b; 29 | SPDLOG_INFO(acid::LaxicalCast,std::string>()(b)); 30 | } 31 | void test3() { 32 | std::vector a; 33 | a.push_back("aa"); 34 | a.push_back("bb"); 35 | acid::rpc::Serializer s; 36 | s << a; 37 | s.reset(); 38 | std::vector b; 39 | s >> b; 40 | SPDLOG_INFO(acid::LaxicalCast,std::string>()(b)); 41 | } 42 | 43 | void test4() { 44 | std::set a {"aa","bb"}; 45 | acid::rpc::Serializer s; 46 | s << a; 47 | s.reset(); 48 | std::set b; 49 | s >> b; 50 | SPDLOG_INFO(acid::LaxicalCast,std::string>()(b)); 51 | } 52 | 53 | void test5() { 54 | std::map> a {{5,{"aa","bb"}},{7,{"aac","bbc"}}}; 55 | acid::rpc::Serializer s; 56 | s << a; 57 | s.reset(); 58 | std::map> b; 59 | s >> b; 60 | for(auto item:b){ 61 | for(auto i:item.second) { 62 | SPDLOG_INFO("{} ", i); 63 | } 64 | } 65 | } 66 | 67 | template 68 | void test_map(T& a) { 69 | acid::rpc::Serializer s; 70 | s << a; 71 | s.reset(); 72 | T b; 73 | s >> b; 74 | for(auto item:b){ 75 | SPDLOG_INFO("{} {}", item.first, item.second); 76 | } 77 | } 78 | 79 | void test6() { 80 | std::map a{{1,"a"},{2,"b"}}; 81 | test_map(a); 82 | } 83 | 84 | void test7() { 85 | std::multimap a{{1,"a"},{1,"a"}}; 86 | test_map(a); 87 | } 88 | 89 | void test8() { 90 | std::unordered_multimap a{{1,"a"},{1,"a"}}; 91 | test_map(a); 92 | } 93 | 94 | void test9() { 95 | std::unordered_map a{{1,"a"},{1,"a"}}; 96 | test_map(a); 97 | } 98 | 99 | void seq2seq() { 100 | std::vector a{"ab","cd"}; 101 | acid::rpc::Serializer s; 102 | s << a; 103 | std::list b; 104 | s.reset(); 105 | s >> b; 106 | SPDLOG_INFO(acid::LaxicalCast,std::string>()(b)); 107 | } 108 | 109 | enum class Color : uint8_t { 110 | red, 111 | green, 112 | yellow, 113 | }; 114 | 115 | struct UserDefine { 116 | int a{}; 117 | char b{}; 118 | std::string c{}; 119 | Color d; 120 | std::vector e; 121 | std::string toString() const { 122 | std::string str = fmt::format("a: {}, b: {}, c: {}, d: {}, e:[", a, b, c, static_cast(d)); 123 | for (auto item: e) { 124 | str += fmt::format("{} ", item); 125 | } 126 | return str + "]"; 127 | } 128 | }; 129 | // 无侵入式序列化 130 | void test10() { 131 | UserDefine userDefine = UserDefine{.a = 1, .b = '2', .c = "3", .d = Color::green, .e = {4, 5}}; 132 | SPDLOG_INFO(userDefine.toString()); 133 | acid::rpc::Serializer serializer; 134 | serializer << userDefine; 135 | serializer.reset(); 136 | 137 | userDefine = UserDefine{}; 138 | SPDLOG_INFO(userDefine.toString()); 139 | 140 | serializer >> userDefine; 141 | SPDLOG_INFO(userDefine.toString()); 142 | } 143 | 144 | int main() { 145 | test10(); 146 | } -------------------------------------------------------------------------------- /tests/test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | project(test) 3 | set(CMAKE_CXX_COMPILER "/usr/bin/g++-11") 4 | set(CMAKE_VERBOSE_MAKEFILE ON) 5 | set(CMAKE_CXX_FLAGS "-std=c++20 -D__const__= -fPIC -fno-strict-aliasing -Wall ${CMAKE_CXX_FLAGS}") 6 | 7 | include_directories(${PROJECT_SOURCE_DIR}/../../) 8 | link_directories(${PROJECT_SOURCE_DIR}/../../lib) 9 | aux_source_directory(${PROJECT_SOURCE_DIR} SRC_LIST) 10 | 11 | foreach(var ${SRC_LIST}) 12 | string(REGEX REPLACE ".*/" "" var ${var}) 13 | string(REGEX REPLACE ".cpp" "" tgt ${var}) 14 | 15 | add_executable(${tgt} ${var}) 16 | set(LINK_ARGS libgo acid pthread dl yaml-cpp) 17 | 18 | target_link_libraries(${tgt} -Wl,--start-group ${LINK_ARGS} -Wl,--end-group) 19 | endforeach(var) -------------------------------------------------------------------------------- /tests/test/test_address.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2021/12/8. 3 | // 4 | 5 | #include 6 | #include 7 | #include "stdio.h" 8 | #include 9 | #include "acid/net/address.h" 10 | #include "acid/common/config.h" 11 | char buff[100000]; 12 | void test_http(){ 13 | acid::Address::ptr address = acid::IPAddress::Create("baidu.com", 80); 14 | //address->insert(std::cout); 15 | spdlog::info(address->toString()); 16 | int fd = socket(AF_INET, SOCK_STREAM, 0); 17 | int rt = connect(fd, address->getAddr(), address->getAddrLen()); 18 | spdlog::info(rt); 19 | //read(fd,buff,100); 20 | const char data[] = "GET / HTTP/1.0\r\n\r\n"; 21 | rt = send(fd, data, sizeof(data), 0); 22 | char *p = buff; 23 | while((rt = recv(fd,p,4096,0)) > 0){ 24 | p += rt; 25 | } 26 | 27 | puts(buff); 28 | } 29 | void test_addr(){ 30 | std::vector res; 31 | acid::Address::Lookup(res,"iptv.tsinghua.edu.cn"); 32 | for(auto i:res){ 33 | spdlog::info(i->toString()); 34 | } 35 | } 36 | void test_iface(){ 37 | std::multimap> r; 38 | acid::Address::GetInterfaceAddresses(r,AF_INET6); 39 | for(auto item:r){ 40 | spdlog::info("{}, {}, {}", item.first, item.second.first->toString(), item.second.second); 41 | } 42 | } 43 | void test_ipv4(){ 44 | auto addr = acid::IPAddress::Create("iptv.tsinghua.edu.cn"); 45 | //auto addr = acid::IPAddress::Create("127.0.0.8"); 46 | if(addr) { 47 | spdlog::info(addr->toString()); 48 | } 49 | } 50 | int main(){ 51 | test_http(); 52 | spdlog::warn("===================="); 53 | test_addr(); 54 | spdlog::warn("===================="); 55 | test_iface(); 56 | spdlog::warn("===================="); 57 | test_ipv4(); 58 | } -------------------------------------------------------------------------------- /tests/test/test_bytearray.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2021/12/11. 3 | // 4 | #include 5 | #include "acid/common/byte_array.h" 6 | using namespace acid; 7 | 8 | void test1(){ 9 | std::string str = "<2021-12-11 19:57:05 [ INFO ] [root] 32507 main 0 tests/test_bytearray.cpp:20 1956297539--1956297539"; 10 | std::vector vec; 11 | ByteArray byteArray(1); 12 | for(int i = 0; i < 100 ; ++i){ 13 | //vec.push_back(rand()); 14 | //byteArray.writeFint32(vec[i]); 15 | byteArray.writeStringVint(str); 16 | } 17 | byteArray.setPosition(0); 18 | for(int i = 0; i < 100 ; ++i){ 19 | //int a = byteArray.readFint32(); 20 | std::string tmp = byteArray.readStringVint(); 21 | spdlog::info(tmp); 22 | } 23 | 24 | } 25 | class Student{ 26 | public: 27 | Student(){}; 28 | Student(bool g, short age, int id, std::string name):m_isMale(g), m_age(age), m_stuId(id), m_name(name){} 29 | std::string toString(){ 30 | std::stringstream ss; 31 | ss<< "[" <<(m_isMale? "male" : "female" ) << "," << m_age << "," << m_stuId << "," << m_name << "]"; 32 | return ss.str(); 33 | } 34 | void readFromByeArray(ByteArray& byteArray){ 35 | m_isMale = byteArray.readFint8(); 36 | m_age = byteArray.readFint16(); 37 | m_stuId = byteArray.readFint32(); 38 | m_name = byteArray.readStringF16(); 39 | } 40 | void writeToByeArray(ByteArray& byteArray){ 41 | byteArray.writeFint8(m_isMale); 42 | byteArray.writeFint16(m_age); 43 | byteArray.writeFint32(m_stuId); 44 | byteArray.writeStringF16(m_name); 45 | } 46 | private: 47 | bool m_isMale = false; 48 | short m_age = 0; 49 | int m_stuId = 0; 50 | std::string m_name; 51 | }; 52 | 53 | //序列化、反序列化 54 | void test_2(){ 55 | Student student(true, 20, 201912900, "Zavier"); 56 | ByteArray byteArray{}; 57 | student.writeToByeArray(byteArray); 58 | byteArray.setPosition(0); 59 | byteArray.writeToFile("Student.dat"); 60 | 61 | ByteArray b; 62 | b.readFromFile("Student.dat"); 63 | b.setPosition(0); 64 | Student s; 65 | s.readFromByeArray(b); 66 | spdlog::info(s.toString()); 67 | } 68 | void test3() { 69 | int n; 70 | ByteArray byteArray{}; 71 | byteArray.writeInt32(128); 72 | byteArray.writeInt32(1); 73 | byteArray.setPosition(0); 74 | spdlog::info(byteArray.toHexString()); 75 | n = byteArray.readInt32(); 76 | spdlog::info(n); 77 | spdlog::info(byteArray.toHexString()); 78 | } 79 | int main(){ 80 | test3(); 81 | } -------------------------------------------------------------------------------- /tests/test/test_config.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2021/10/26. 3 | // 4 | #include 5 | #include "acid/common/config.h" 6 | using namespace std; 7 | auto port = acid::Config::Lookup("system.port",8080,"bind port"); 8 | auto vec = acid::Config::Lookup("system.vec",vector{2,3},"vec"); 9 | //auto logss = acid::Config::Lockup("logs",vector(),"logs"); 10 | class Person{ 11 | public: 12 | Person() = default; 13 | Person(const string& name,int age): m_age(age), m_name(name){} 14 | int m_age = 0; 15 | std::string m_name ; 16 | std::string toString() const { 17 | std::stringstream ss; 18 | ss<<"[ name="< 27 | class LaxicalCast{ 28 | public: 29 | Person operator()(const std::string& str){ 30 | YAML::Node node = YAML::Load(str); 31 | Person res; 32 | res.m_name = node["name"].as(); 33 | res.m_age = node["age"].as(); 34 | return res; 35 | } 36 | }; 37 | 38 | template<> 39 | class LaxicalCast{ 40 | public: 41 | std::string operator()(const Person& v){ 42 | YAML::Node node; 43 | node["name"] = v.m_name; 44 | node["age"] = v.m_age; 45 | std::stringstream ss; 46 | ss<toString()); 53 | 54 | //YAML::Node config = YAML::LoadFile("config/log.yaml"); 55 | acid::Config::LoadFromFile("config/log.yaml"); 56 | acid::Config::Lookup("system.port","123"s,"vec"); 57 | spdlog::info("After\n{}", vec->toString()); 58 | //cout<>()("- 1\n- 2\n- 5")[2]; 59 | //cout<>>,string>()(list>>{{{1,2,4,5},{2,3}}}); 60 | list value{1.2,3.1415}; 61 | cout<>,string>()(map> {{"acid",value}}); 62 | } 63 | void test2(){ 64 | //auto pm = acid::Config::Lookup("map",map>(),""); 65 | 66 | //pm->setValue(map>()); 67 | auto a = acid::Config::Lookup("class.person",Person(),""); 68 | // a->addListener(1,[](const auto& old_val, const auto& new_val){ 69 | // ACID_LOG_INFO(ACID_LOG_ROOT())< "<setValue(Person("aaa",44)); 73 | //ACID_LOG_DEBUG(ACID_LOG_ROOT())<<"Before \n"<toString()<<" "<getValue().toString(); 74 | //acid::Config::LoadFromFile("config/log.yaml"); 75 | 76 | //auto tmp = pm->getValue(); 77 | acid::Config::Visit([](acid::ConfigVarBase::ptr config){ 78 | spdlog::info("{} {} {}", config->getName(), config->getDescription(), config->toString()); 79 | }); 80 | 81 | //auto b = a->getValue().toString(); 82 | } 83 | int main(){ 84 | test2(); 85 | } 86 | -------------------------------------------------------------------------------- /tests/test/test_socket.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2021/12/6. 3 | // 4 | #include 5 | #include 6 | #include 7 | #include "acid/net/socket.h" 8 | #include "acid/net/socket_stream.h" 9 | 10 | void test_socket(){ 11 | acid::Address::ptr addr = acid::IPAddress::Create("vvip.icu",80); 12 | if(addr){ 13 | spdlog::info(addr->toString()); 14 | } else { 15 | return; 16 | } 17 | acid::Socket::ptr sock = acid::Socket::CreateTCP(addr); 18 | if(sock->connect(addr)){ 19 | spdlog::info("{} connected", addr->toString()); 20 | } else { 21 | return; 22 | } 23 | char buff[]="GET / HTTP/1.0\r\n\r\n"; 24 | sock->send(buff,sizeof buff); 25 | std::string str; 26 | str.resize(4096); 27 | sock->recv(&str[0],str.size()); 28 | spdlog::info(str); 29 | } 30 | std::set clients; 31 | void sendAll(acid::Socket::ptr sock, const char *msg, int len){ 32 | char* buff = new char[4096]; 33 | sprintf(buff,"%d says: ",sock->getSocket()); 34 | strcat(buff,msg); 35 | puts(buff); 36 | for(auto i : clients){ 37 | if(i != sock){ 38 | i->send(buff, strlen(buff)); 39 | } 40 | } 41 | delete[] buff; 42 | } 43 | void test_server(){ 44 | acid::Address::ptr address = acid::IPv4Address::Create("127.0.0.1", 8080); 45 | acid::Socket::ptr sock = acid::Socket::CreateTCPSocket(); 46 | sock->bind(address); 47 | sock->listen(); 48 | while(true){ 49 | acid::Socket::ptr client = sock->accept(); 50 | clients.insert(client); 51 | sendAll(client,"New User, welcome him\n", 21); 52 | go [client] { 53 | char *buff = new char[4096]; 54 | 55 | while(1){ 56 | int n = client->recv(buff,4096); 57 | if(n == 0){ 58 | spdlog::info("user quit"); 59 | break; 60 | } 61 | buff[n] = 0; 62 | //send(fd,buff,n,0); 63 | sendAll(client,buff,n); 64 | } 65 | sendAll(client,"user quit\n", 10); 66 | delete[] buff; 67 | clients.erase(client); 68 | client->close(); 69 | }; 70 | } 71 | } 72 | void test_byte_array() { 73 | acid::Address::ptr addr = acid::IPAddress::Create("localhost",8080); 74 | if(addr){ 75 | spdlog::info(addr->toString()); 76 | } else { 77 | return; 78 | } 79 | acid::Socket::ptr sock = acid::Socket::CreateTCP(addr); 80 | if(sock->connect(addr, 10000)){ 81 | spdlog::info("{} connected", addr->toString()); 82 | } else { 83 | return; 84 | } 85 | 86 | char buff[]="hello"; 87 | acid::ByteArray::ptr bt(new acid::ByteArray()); 88 | bt->write(buff, 6); 89 | bt->setPosition(0); 90 | //sock->send(buff,sizeof buff); 91 | 92 | acid::SocketStream ss(sock); 93 | ss.writeFixSize(bt, 6); 94 | 95 | std::string str; 96 | str.resize(4096); 97 | sock->recv(&str[0],str.size()); 98 | spdlog::info(str); 99 | } 100 | int main(){ 101 | go test_server; 102 | go test_byte_array; 103 | co_sched.Start(); 104 | } -------------------------------------------------------------------------------- /tests/test/test_tcp_server.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2021/12/14. 3 | // 4 | #include "acid/net/socket_stream.h" 5 | #include "acid/net/tcp_server.h" 6 | 7 | using namespace acid; 8 | 9 | class EchoServer : public TcpServer { 10 | public: 11 | void handleClient(Socket::ptr client) override { 12 | SPDLOG_INFO("handleClient {}", client->toString()); 13 | ByteArray::ptr buff(new ByteArray); 14 | while (true) { 15 | buff->clear(); 16 | std::vector iovs; 17 | buff->getWriteBuffers(iovs, 1024); 18 | int n = client->recv(&iovs[0], iovs.size()); 19 | if(n == 0) { 20 | SPDLOG_INFO("Client Close {}", client->toString()); 21 | break; 22 | } else if (n < 0){ 23 | SPDLOG_INFO("Client Error, errno={}, errstr={}", errno, strerror(errno)); 24 | break; 25 | } 26 | buff->setPosition(buff->getPosition() + n); 27 | buff->setPosition(0); 28 | auto str = buff->toString(); 29 | SPDLOG_INFO("Client: {}", str); 30 | client->send(str.c_str(), str.length()); 31 | } 32 | } 33 | }; 34 | 35 | int main(){ 36 | go [] { 37 | auto addr = Address::LookupAny("0.0.0.0:8080"); 38 | SPDLOG_INFO(addr->toString()); 39 | EchoServer server; 40 | while(!server.bind(addr)){ 41 | sleep(3); 42 | } 43 | server.start(); 44 | }; 45 | co_sched.Start(); 46 | } 47 | -------------------------------------------------------------------------------- /tests/test/test_timer.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2022/11/23. 3 | // 4 | #include 5 | #include 6 | #include "acid/common/util.h" 7 | 8 | using namespace acid; 9 | 10 | static int n = 0; 11 | void test_cycle_timer() { 12 | CycleTimer(1000, [] { 13 | SPDLOG_INFO("{}", n); 14 | n++; 15 | }, nullptr, 3); 16 | sleep(4); 17 | auto tk = CycleTimer(1000, [] { 18 | SPDLOG_INFO("cancel {}", n); 19 | n++; 20 | }); 21 | go [=]() mutable { 22 | sleep(5); 23 | SPDLOG_WARN("stop"); 24 | tk.stop(); 25 | co_sched.Stop(); 26 | }; 27 | }; 28 | 29 | int main() { 30 | go test_cycle_timer; 31 | co_sched.Start(0); 32 | } -------------------------------------------------------------------------------- /tests/test/test_uri.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by zavier on 2021/12/20. 3 | // 4 | 5 | #include "acid/net/uri.h" 6 | using namespace acid; 7 | 8 | int main(){ 9 | std::string http = "www.baidu.com/s?wd=%E4%BD%A0%E5%A5%BD"; 10 | std::string https = "https://baidu.com/add?a=1&b=2#aaa"; 11 | std::string ftp = "ftp://admin:passwd@www.myftp.com/profile"; 12 | std::string file = "file:///c:/desktop/a.cpp"; 13 | std::string magnet = "magnet:?xt=urn:btih:2F9D75F4CB3385CB06FFB30695A34FAC7033902C"; 14 | SPDLOG_INFO(Uri::Create(http)->toString()); 15 | SPDLOG_INFO(Uri::Create(https)->toString()); 16 | SPDLOG_INFO(Uri::Create(ftp)->toString()); 17 | SPDLOG_INFO(Uri::Create(file)->toString()); 18 | SPDLOG_INFO(Uri::Create(magnet)->toString()); 19 | SPDLOG_INFO(Uri::Create(https)->createAddress()->toString()); 20 | } -------------------------------------------------------------------------------- /third_party/json/single_include/nlohmann/json_fwd.hpp: -------------------------------------------------------------------------------- 1 | // __ _____ _____ _____ 2 | // __| | __| | | | JSON for Modern C++ 3 | // | | |__ | | | | | | version 3.11.2 4 | // |_____|_____|_____|_|___| https://github.com/nlohmann/json 5 | // 6 | // SPDX-FileCopyrightText: 2013-2022 Niels Lohmann 7 | // SPDX-License-Identifier: MIT 8 | 9 | #ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_ 10 | #define INCLUDE_NLOHMANN_JSON_FWD_HPP_ 11 | 12 | #include // int64_t, uint64_t 13 | #include // map 14 | #include // allocator 15 | #include // string 16 | #include // vector 17 | 18 | // #include 19 | // __ _____ _____ _____ 20 | // __| | __| | | | JSON for Modern C++ 21 | // | | |__ | | | | | | version 3.11.2 22 | // |_____|_____|_____|_|___| https://github.com/nlohmann/json 23 | // 24 | // SPDX-FileCopyrightText: 2013-2022 Niels Lohmann 25 | // SPDX-License-Identifier: MIT 26 | 27 | 28 | 29 | // This file contains all macro definitions affecting or depending on the ABI 30 | 31 | #ifndef JSON_SKIP_LIBRARY_VERSION_CHECK 32 | #if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH) 33 | #if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 11 || NLOHMANN_JSON_VERSION_PATCH != 2 34 | #warning "Already included a different version of the library!" 35 | #endif 36 | #endif 37 | #endif 38 | 39 | #define NLOHMANN_JSON_VERSION_MAJOR 3 // NOLINT(modernize-macro-to-enum) 40 | #define NLOHMANN_JSON_VERSION_MINOR 11 // NOLINT(modernize-macro-to-enum) 41 | #define NLOHMANN_JSON_VERSION_PATCH 2 // NOLINT(modernize-macro-to-enum) 42 | 43 | #ifndef JSON_DIAGNOSTICS 44 | #define JSON_DIAGNOSTICS 0 45 | #endif 46 | 47 | #ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 48 | #define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0 49 | #endif 50 | 51 | #if JSON_DIAGNOSTICS 52 | #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag 53 | #else 54 | #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS 55 | #endif 56 | 57 | #if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 58 | #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp 59 | #else 60 | #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON 61 | #endif 62 | 63 | #ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION 64 | #define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0 65 | #endif 66 | 67 | // Construct the namespace ABI tags component 68 | #define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) json_abi ## a ## b 69 | #define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b) \ 70 | NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) 71 | 72 | #define NLOHMANN_JSON_ABI_TAGS \ 73 | NLOHMANN_JSON_ABI_TAGS_CONCAT( \ 74 | NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \ 75 | NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON) 76 | 77 | // Construct the namespace version component 78 | #define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \ 79 | _v ## major ## _ ## minor ## _ ## patch 80 | #define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(major, minor, patch) \ 81 | NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) 82 | 83 | #if NLOHMANN_JSON_NAMESPACE_NO_VERSION 84 | #define NLOHMANN_JSON_NAMESPACE_VERSION 85 | #else 86 | #define NLOHMANN_JSON_NAMESPACE_VERSION \ 87 | NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(NLOHMANN_JSON_VERSION_MAJOR, \ 88 | NLOHMANN_JSON_VERSION_MINOR, \ 89 | NLOHMANN_JSON_VERSION_PATCH) 90 | #endif 91 | 92 | // Combine namespace components 93 | #define NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) a ## b 94 | #define NLOHMANN_JSON_NAMESPACE_CONCAT(a, b) \ 95 | NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) 96 | 97 | #ifndef NLOHMANN_JSON_NAMESPACE 98 | #define NLOHMANN_JSON_NAMESPACE \ 99 | nlohmann::NLOHMANN_JSON_NAMESPACE_CONCAT( \ 100 | NLOHMANN_JSON_ABI_TAGS, \ 101 | NLOHMANN_JSON_NAMESPACE_VERSION) 102 | #endif 103 | 104 | #ifndef NLOHMANN_JSON_NAMESPACE_BEGIN 105 | #define NLOHMANN_JSON_NAMESPACE_BEGIN \ 106 | namespace nlohmann \ 107 | { \ 108 | inline namespace NLOHMANN_JSON_NAMESPACE_CONCAT( \ 109 | NLOHMANN_JSON_ABI_TAGS, \ 110 | NLOHMANN_JSON_NAMESPACE_VERSION) \ 111 | { 112 | #endif 113 | 114 | #ifndef NLOHMANN_JSON_NAMESPACE_END 115 | #define NLOHMANN_JSON_NAMESPACE_END \ 116 | } /* namespace (inline namespace) NOLINT(readability/namespace) */ \ 117 | } // namespace nlohmann 118 | #endif 119 | 120 | 121 | /*! 122 | @brief namespace for Niels Lohmann 123 | @see https://github.com/nlohmann 124 | @since version 1.0.0 125 | */ 126 | NLOHMANN_JSON_NAMESPACE_BEGIN 127 | 128 | /*! 129 | @brief default JSONSerializer template argument 130 | 131 | This serializer ignores the template arguments and uses ADL 132 | ([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl)) 133 | for serialization. 134 | */ 135 | template 136 | struct adl_serializer; 137 | 138 | /// a class to store JSON values 139 | /// @sa https://json.nlohmann.me/api/basic_json/ 140 | template class ObjectType = 141 | std::map, 142 | template class ArrayType = std::vector, 143 | class StringType = std::string, class BooleanType = bool, 144 | class NumberIntegerType = std::int64_t, 145 | class NumberUnsignedType = std::uint64_t, 146 | class NumberFloatType = double, 147 | template class AllocatorType = std::allocator, 148 | template class JSONSerializer = 149 | adl_serializer, 150 | class BinaryType = std::vector, // cppcheck-suppress syntaxError 151 | class CustomBaseClass = void> 152 | class basic_json; 153 | 154 | /// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document 155 | /// @sa https://json.nlohmann.me/api/json_pointer/ 156 | template 157 | class json_pointer; 158 | 159 | /*! 160 | @brief default specialization 161 | @sa https://json.nlohmann.me/api/json/ 162 | */ 163 | using json = basic_json<>; 164 | 165 | /// @brief a minimal map-like container that preserves insertion order 166 | /// @sa https://json.nlohmann.me/api/ordered_map/ 167 | template 168 | struct ordered_map; 169 | 170 | /// @brief specialization that maintains the insertion order of object keys 171 | /// @sa https://json.nlohmann.me/api/ordered_json/ 172 | using ordered_json = basic_json; 173 | 174 | NLOHMANN_JSON_NAMESPACE_END 175 | 176 | #endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_ 177 | --------------------------------------------------------------------------------