开源服务器设计总计(plain framework2020年总计)
2020年注定会被历史铭记,世界遭受着一场前所未有的灾难,这种灾难到现在还在持续。还记得19年末的时候,那时候听到一点点消息,哪里想得到年关难过,灾难来的让人猝不及防。由于疫情防控,2020年感觉转瞬即逝,仿佛晃眼的功夫。本来做些自身职业上的改变,去年因为自身的原因因此搁浅。想想这几年自己维护的这个框架并未有太大的起色,甚者一度遗忘了它,所规划的很多内容并未得到实现。自己的懒惰搁浅,未免是对生命的严重浪费。今年的虽然暂时没有太多的计划,但是改变已经走在路上,希望这对自己的人生有所帮助。plain framework原本是面向游戏服务器设计的,不过在设计上尽量兼容所有的网络应用,从14年开始到现在已经六七年了,经过多次的修改,也扩充了不少的接口,在使用上更加的便捷。在这篇总结中,我主要讲述的是最近加入的控制台(console)模块,其实这个模块在许多框架中是很常见的。未来的技术日新月异,如果大家对编程上面有所兴趣,不妨可以做一定的参考,如有不足的地方也请指正,我将认真的思考其中遗留的问题。在这里我也祝愿大家在2021年,在风雨之后迎来希望的彩虹!
总结
在以前的文章中,我已经总结过plain framework的成长过程,从C++98到C++11经历了不小的变化。如今的目录结构和当初很不同,从参考到自己的一些设计,让整个框架越来越好用越来越高效是最终的目的。但是这种改变是漫长的,而且总觉得力不从心,许多的设计虽然有过临时的想法始终没能得到执行。当前这个框架现阶段如果直接用于项目中问题不大,却不能保证有所隐藏的一些BUG。今年我要做出一些改变,不单单做游戏方面的设计,我对于未来的技术也有过兴趣,如在之前的项目中使用过VUE框架来做后台的相应设计。不过相对于未来,算法始终是大方向以及技术上需要突破的,比如AI人工智能,其中的算法需要许多的数学知识,但这些相应的知识我自己要么遗忘要么还没认真接触,作为自己的兴趣后续会向这些方向研究。
人生总要有些改变,支持迟来和早来!因此我需要有段时间来放空脑袋,想一想未来的方向了。随着年龄的增长就产生了一种莫名的危机感,在职业上也有些乏味缺少新鲜,但是生活总是要继续的。一个人如果有了计划而不执行,到了没有精力和时间去做得时候,那时候再后悔未免可笑。因此我觉得一个人就得坚定地向着自己的目标走去,就算目标看来那样遥不可及,但我们可以不断改变策略,毕竟没有人随随便便就成功,但如果不去行动那么连成功的机会都没有。
希望大家都能慢慢接近自己的理想,也希望这场人类的灾难早点过去!
控制台(console)
控制台是在应用中提供调试的工具,一般情况下可以来分析应用运行过程中一些数据。在经典的操作系统中,控制台实在是太常见了,如windows中的命令行控制器,可以使用相应的命令对系统或者应用进行运行、调试和分析。
plain framework加入控制台的目的,也是为了调试以及在应用运行中做一些调试和处理,提供了比较丰富的命令接口使得外部注册命令比较容易。在服务器的设计中,很多时候想要看看应用的线程内存情况,还有网络的链接和数据的收发情况。增加控制台,有助于我们在测试的时候,对不同情况下特别是压力测试时分析出重要的数据,这有助于帮助我们对程序进行优化。目前PF 中的命令不多,主要几个常见的命令,后续会继续完善。
下图为PF中控制台的调试(包括在LINUX下的编译部分),目前仅支持网络调试(直接输入的方式很快集成,由于感觉用处不大暂时没实现):
部分代码
由于控制台需要使用网络命令行,因此在框架中增加了standard的网络协议(protocol),这个网络协议是遇到换行便将内容读出并调用注册的执行接口:
#include "pf/basic/string.h" #include "pf/net/stream/input.h" #include "pf/net/stream/output.h" #include "pf/net/connection/basic.h" #include "pf/net/connection/manager/listener.h" #include "pf/net/protocol/standard.h" using namespace pf_basic::string; using namespace pf_net::protocol; bool Standard::command(connection::Basic *connection, uint16_t count) { if (connection->is_disconnect()) return false; //Leave this when not connected. stream::Input *istream = &connection->istream(); auto line = istream->readline(); if (!line.empty()) { if (!connection->check_safe_encrypt()) return false; auto listener = connection->get_listener(); if (!is_null(listener)) { rtrim(line); // Remove '\n' '\r' or other words on last. auto callback = listener->get_standard_callback(); if (callback) callback(line, connection); } } return true; } bool Standard::send(connection::Basic *connection, packet::Interface *packet) { return true; }
在处理命令的时候注册的回调函数如下:
void console_net_handle( const std::string &cmd, pf_net::connection::Basic *connection) { using namespace pf_console; using namespace pf_basic::string; if (is_null(ENGINE_POINTER)) return; auto console = ENGINE_POINTER->get_console(); if (is_null(console)) return; if ("quit" == cmd) { connection->exit(); return; } StringInput input(cmd); NetOutput output(connection); console->run(&input, &output); }
整个控制台的实现目录结构如下:
控制台应用代码:
#include "pf/basic/string.h" #include "pf/console/argv_input.h" #include "pf/console/array_input.h" #include "pf/console/commands/app.h" #include "pf/console/commands/help.h" #include "pf/console/commands/list.h" #include "pf/basic/logger.h" #include "pf/console/application.h" using namespace pf_console; using namespace pf_interfaces::console; using namespace pf_basic::string; uint8_t Application::run(Input *input, Output *output) { std::unique_ptr<Input> input_temp; std::unique_ptr<Output> output_temp; if (is_null(input)) { unique_move(Input, new ArgvInput(), input_temp); input = input_temp.get(); } if (is_null(output)) { unique_move(Output, new Output(), output_temp); output = output_temp.get(); } configure_IO(input, output); uint8_t exit_code{0}; try { exit_code = do_run(input, output); } catch (std::exception &e) { std::cout << "Application::run get error!!!: " << e.what() << std::endl; } return exit_code; } uint8_t Application::do_run(Input *input, Output *output) { if (input->has_parameter_option({"--version", "-V"})) { output->write_ln(get_long_version()); return 0; } try { input->bind(get_definition()); } catch(...) { } auto name = get_command_name(input); std::unique_ptr<Input> input_temp; if (input->has_parameter_option({"--help", "-h"}, true)) { if (name == "") { name = "help"; unique_move(Input, new ArrayInput({{"command_name", default_command_name_}}), input_temp); input = input_temp.get(); } else { want_helps_ = false; } } if (name == "") { name = default_command_name_; auto definition = get_definition(); definition->set_argument(InputArgument("command", InputArgument::kModeOptional, definition->get_argument("command").get_description(), name)); } Command *command{nullptr}; try { running_command_ = nullptr; command = find(name); } catch (...) { } if (is_null(command)) { FAST_ERRORLOG(CONSOLE_MODULENAME, "[console] (Application::run)" " can't find the command: %s", name.c_str()); return 1; } running_command_ = command; auto exit_code = do_runcommand(command, input, output); return exit_code; } InputDefinition *Application::get_definition() { if (is_null(definition_)) { std::unique_ptr<InputDefinition> temp(new InputDefinition()); *temp = get_default_input_definition(); definition_ = std::move(temp); if (single_command_) { if (is_null(definition_temp_)) { unique_move(InputDefinition, new InputDefinition(), definition_temp_); *definition_temp_ = *definition_; } definition_temp_->set_arguments({}); return definition_temp_.get(); } } return definition_.get(); } std::string Application::get_long_version() const { std::string r{"Console Tool"}; if (name_ != "") { if (version_ != "") { r = name_ + " " + version_; } r = name_; } return r; } Command *Application::add(Command *command) { // std::cout << "Add command: " << command->name() << std::endl; if (!command->is_enabled()) { return nullptr; } init(); command->set_application(this); // Will throw if the command is not correctly initialized. command->get_definition(); command->configure(); auto name = command->name(); if (name == "") { throw std::logic_error("command cannot have an empty name."); } if (commands_.find(name) != commands_.end()) { return commands_[name].get(); } std::unique_ptr<Command> temp; unique_move(Command, command, temp); commands_[name] = std::move(temp); for (auto const &alias : command->get_aliases()) { command_aliases_[alias] = name; } return command; } Command *Application::get(const std::string &_name) { auto name = get_command_real_name(_name); if ("" == name) { std::string e = "The command \"" + _name + "\" does not exist."; throw std::invalid_argument(e); } if (commands_.find(name) == commands_.end()) return nullptr; auto command = commands_[name].get(); if (want_helps_) { want_helps_ = false; auto help_command = get("help"); help_command->set_command(command); return help_command; } return command; } std::vector<std::string> Application::get_namespaces() { std::vector<std::string> r; auto commands = all(); for (auto it = commands.begin(); it != commands.end(); ++it) { if (it->second->is_hidden()) continue; auto temp = extract_all_namespace(it->first); for (const auto &one : temp) r.emplace_back(one); for (const auto &alias : it->second->get_aliases()) { auto temp1 = extract_all_namespace(alias); for (const auto &one : temp1) r.emplace_back(one); } } // * The result maybe need use array_unique to remove the same values. return r; } std::string Application::find_namespace(const std::string &_namespace) { return ""; } Command *Application::find(const std::string &name) { init(); for (auto it = commands_.begin(); it != commands_.end(); ++it) { if (!is_null(it->second)) { for (const auto &alias : it->second->get_aliases()) { if ("" == command_aliases_[alias]) command_aliases_[alias] = it->second->name(); } } else { std::cout << "find no command: " << it->first << std::endl; } } return get(name); } std::map<std::string, Command *> Application::all(const std::string &_namespace) { std::map<std::string, Command *> r; init(); if ("" == _namespace) { for (auto it = commands_.begin(); it != commands_.end(); ++it) { r[it->first] = it->second.get(); } return r; } for (auto it = commands_.begin(); it != commands_.end(); ++it) { if (_namespace == extract_namespace(it->first)) { r[it->first] = it->second.get(); } } return r; } Application &Application::set_default_command( const std::string &name, bool is_single_command) { default_command_name_ = name; if (is_single_command) { // Ensure the command exist find(name); single_command_ = true; } return *this; } uint8_t Application::do_runcommand( Command *command, Input *input, Output *output) { // std::cout << "do_runcommand: " << command->name() << std::endl; return command->run(input, output); } std::string Application::get_command_name(Input *input) const { return single_command_ ? default_command_name_ : input->get_first_argument(); } InputDefinition Application::get_default_input_definition() const { std::vector<InputParameter *> p; std::unique_ptr<InputParameter> p1(new InputArgument( "command", InputParameter::kModeRequired, "The command to execute", "" )); p.emplace_back(p1.get()); std::unique_ptr<InputParameter> p2(new InputOption( "--help", "-h", InputParameter::kModeNone, "Display help for the given command. When no command" " is given display help for the" + default_command_name_ + "command", "" )); p.emplace_back(p2.get()); std::unique_ptr<InputParameter> p3(new InputOption( "--quiet", "-q", InputParameter::kModeNone, "Do not output any message", "" )); p.emplace_back(p3.get()); std::unique_ptr<InputParameter> p4(new InputOption( "--verbose", "-v|vv|vvv", InputParameter::kModeNone, "Increase the verbosity of messages: 1 for normal output, " "2 for more verbose output and 3 for debug", "" )); p.emplace_back(p4.get()); std::unique_ptr<InputParameter> p5(new InputOption( "--version", "-V", InputParameter::kModeNone, "Display this application version", "" )); p.emplace_back(p5.get()); std::unique_ptr<InputParameter> p6(new InputOption( "--ansi", "", InputParameter::kModeNone, "Force ANSI output", "" )); p.emplace_back(p6.get()); std::unique_ptr<InputParameter> p7(new InputOption( "--no-ansi", "", InputParameter::kModeNone, "Disable ANSI output", "" )); p.emplace_back(p7.get()); std::unique_ptr<InputParameter> p8(new InputOption( "--no-interaction", "-n", InputParameter::kModeNone, "Do not ask any interactive question", "" )); p.emplace_back(p8.get()); return InputDefinition(p); } std::vector<Command *> Application::get_default_commands() const { std::vector<Command *> r; return r; } std::string Application::get_abbreviation_suggestions( const std::vector<std::string> &abbrevs) const { return ""; } std::vector<std::string> Application::find_alternatives( const std::string &name, const std::vector<std::string> &collection) const { return {}; } void Application::configure_IO(Input *input, Output *output) { if (input->has_parameter_option({"--ansi"}, true)) { output->set_decorated(true); } else if (input->has_parameter_option({"'--no-ansi'"}, true)) { output->set_decorated(false); } } std::vector<std::string> Application::extract_all_namespace( const std::string &name) { std::vector<std::string> r; std::vector<std::string> parts; explode(name.c_str(), parts, ":", true, true); for (const auto &part : parts) { if (r.size() > 0) { std::string temp = r[r.size() - 1] + ":" + part; r.emplace_back(temp); } else { r.emplace_back(part); } } return r; } std::string Application::extract_namespace( const std::string &name, int32_t limit) const { return ""; } void Application::init() { if (initialized_) return; initialized_ = true; // std::cout << "Application::init" << std::endl; add(new commands::Help()); add(new commands::List()); add(new commands::App()); }
命令实现代码:
#include "pf/basic/string.h" #include "pf/console/argv_input.h" #include "pf/console/array_input.h" #include "pf/console/commands/app.h" #include "pf/console/commands/help.h" #include "pf/console/commands/list.h" #include "pf/basic/logger.h" #include "pf/console/application.h" using namespace pf_console; using namespace pf_interfaces::console; using namespace pf_basic::string; uint8_t Application::run(Input *input, Output *output) { std::unique_ptr<Input> input_temp; std::unique_ptr<Output> output_temp; if (is_null(input)) { unique_move(Input, new ArgvInput(), input_temp); input = input_temp.get(); } if (is_null(output)) { unique_move(Output, new Output(), output_temp); output = output_temp.get(); } configure_IO(input, output); uint8_t exit_code{0}; try { exit_code = do_run(input, output); } catch (std::exception &e) { std::cout << "Application::run get error!!!: " << e.what() << std::endl; } return exit_code; } uint8_t Application::do_run(Input *input, Output *output) { if (input->has_parameter_option({"--version", "-V"})) { output->write_ln(get_long_version()); return 0; } try { input->bind(get_definition()); } catch(...) { } auto name = get_command_name(input); std::unique_ptr<Input> input_temp; if (input->has_parameter_option({"--help", "-h"}, true)) { if (name == "") { name = "help"; unique_move(Input, new ArrayInput({{"command_name", default_command_name_}}), input_temp); input = input_temp.get(); } else { want_helps_ = false; } } if (name == "") { name = default_command_name_; auto definition = get_definition(); definition->set_argument(InputArgument("command", InputArgument::kModeOptional, definition->get_argument("command").get_description(), name)); } Command *command{nullptr}; try { running_command_ = nullptr; command = find(name); } catch (...) { } if (is_null(command)) { FAST_ERRORLOG(CONSOLE_MODULENAME, "[console] (Application::run)" " can't find the command: %s", name.c_str()); return 1; } running_command_ = command; auto exit_code = do_runcommand(command, input, output); return exit_code; } InputDefinition *Application::get_definition() { if (is_null(definition_)) { std::unique_ptr<InputDefinition> temp(new InputDefinition()); *temp = get_default_input_definition(); definition_ = std::move(temp); if (single_command_) { if (is_null(definition_temp_)) { unique_move(InputDefinition, new InputDefinition(), definition_temp_); *definition_temp_ = *definition_; } definition_temp_->set_arguments({}); return definition_temp_.get(); } } return definition_.get(); } std::string Application::get_long_version() const { std::string r{"Console Tool"}; if (name_ != "") { if (version_ != "") { r = name_ + " " + version_; } r = name_; } return r; } Command *Application::add(Command *command) { // std::cout << "Add command: " << command->name() << std::endl; if (!command->is_enabled()) { return nullptr; } init(); command->set_application(this); // Will throw if the command is not correctly initialized. command->get_definition(); command->configure(); auto name = command->name(); if (name == "") { throw std::logic_error("command cannot have an empty name."); } if (commands_.find(name) != commands_.end()) { return commands_[name].get(); } std::unique_ptr<Command> temp; unique_move(Command, command, temp); commands_[name] = std::move(temp); for (auto const &alias : command->get_aliases()) { command_aliases_[alias] = name; } return command; } Command *Application::get(const std::string &_name) { auto name = get_command_real_name(_name); if ("" == name) { std::string e = "The command \"" + _name + "\" does not exist."; throw std::invalid_argument(e); } if (commands_.find(name) == commands_.end()) return nullptr; auto command = commands_[name].get(); if (want_helps_) { want_helps_ = false; auto help_command = get("help"); help_command->set_command(command); return help_command; } return command; } std::vector<std::string> Application::get_namespaces() { std::vector<std::string> r; auto commands = all(); for (auto it = commands.begin(); it != commands.end(); ++it) { if (it->second->is_hidden()) continue; auto temp = extract_all_namespace(it->first); for (const auto &one : temp) r.emplace_back(one); for (const auto &alias : it->second->get_aliases()) { auto temp1 = extract_all_namespace(alias); for (const auto &one : temp1) r.emplace_back(one); } } // * The result maybe need use array_unique to remove the same values. return r; } std::string Application::find_namespace(const std::string &_namespace) { return ""; } Command *Application::find(const std::string &name) { init(); for (auto it = commands_.begin(); it != commands_.end(); ++it) { if (!is_null(it->second)) { for (const auto &alias : it->second->get_aliases()) { if ("" == command_aliases_[alias]) command_aliases_[alias] = it->second->name(); } } else { std::cout << "find no command: " << it->first << std::endl; } } return get(name); } std::map<std::string, Command *> Application::all(const std::string &_namespace) { std::map<std::string, Command *> r; init(); if ("" == _namespace) { for (auto it = commands_.begin(); it != commands_.end(); ++it) { r[it->first] = it->second.get(); } return r; } for (auto it = commands_.begin(); it != commands_.end(); ++it) { if (_namespace == extract_namespace(it->first)) { r[it->first] = it->second.get(); } } return r; } Application &Application::set_default_command( const std::string &name, bool is_single_command) { default_command_name_ = name; if (is_single_command) { // Ensure the command exist find(name); single_command_ = true; } return *this; } uint8_t Application::do_runcommand( Command *command, Input *input, Output *output) { // std::cout << "do_runcommand: " << command->name() << std::endl; return command->run(input, output); } std::string Application::get_command_name(Input *input) const { return single_command_ ? default_command_name_ : input->get_first_argument(); } InputDefinition Application::get_default_input_definition() const { std::vector<InputParameter *> p; std::unique_ptr<InputParameter> p1(new InputArgument( "command", InputParameter::kModeRequired, "The command to execute", "" )); p.emplace_back(p1.get()); std::unique_ptr<InputParameter> p2(new InputOption( "--help", "-h", InputParameter::kModeNone, "Display help for the given command. When no command" " is given display help for the" + default_command_name_ + "command", "" )); p.emplace_back(p2.get()); std::unique_ptr<InputParameter> p3(new InputOption( "--quiet", "-q", InputParameter::kModeNone, "Do not output any message", "" )); p.emplace_back(p3.get()); std::unique_ptr<InputParameter> p4(new InputOption( "--verbose", "-v|vv|vvv", InputParameter::kModeNone, "Increase the verbosity of messages: 1 for normal output, " "2 for more verbose output and 3 for debug", "" )); p.emplace_back(p4.get()); std::unique_ptr<InputParameter> p5(new InputOption( "--version", "-V", InputParameter::kModeNone, "Display this application version", "" )); p.emplace_back(p5.get()); std::unique_ptr<InputParameter> p6(new InputOption( "--ansi", "", InputParameter::kModeNone, "Force ANSI output", "" )); p.emplace_back(p6.get()); std::unique_ptr<InputParameter> p7(new InputOption( "--no-ansi", "", InputParameter::kModeNone, "Disable ANSI output", "" )); p.emplace_back(p7.get()); std::unique_ptr<InputParameter> p8(new InputOption( "--no-interaction", "-n", InputParameter::kModeNone, "Do not ask any interactive question", "" )); p.emplace_back(p8.get()); return InputDefinition(p); } std::vector<Command *> Application::get_default_commands() const { std::vector<Command *> r; return r; } std::string Application::get_abbreviation_suggestions( const std::vector<std::string> &abbrevs) const { return ""; } std::vector<std::string> Application::find_alternatives( const std::string &name, const std::vector<std::string> &collection) const { return {}; } void Application::configure_IO(Input *input, Output *output) { if (input->has_parameter_option({"--ansi"}, true)) { output->set_decorated(true); } else if (input->has_parameter_option({"'--no-ansi'"}, true)) { output->set_decorated(false); } } std::vector<std::string> Application::extract_all_namespace( const std::string &name) { std::vector<std::string> r; std::vector<std::string> parts; explode(name.c_str(), parts, ":", true, true); for (const auto &part : parts) { if (r.size() > 0) { std::string temp = r[r.size() - 1] + ":" + part; r.emplace_back(temp); } else { r.emplace_back(part); } } return r; } std::string Application::extract_namespace( const std::string &name, int32_t limit) const { return ""; } void Application::init() { if (initialized_) return; initialized_ = true; // std::cout << "Application::init" << std::endl; add(new commands::Help()); add(new commands::List()); add(new commands::App()); } [viticm@izuf633l0ge76tbdctmljaz core]$ cat /home/viticm/develop/github/plain/framework/core/src/console/command.cc #include "pf/support/helpers.h" #include "pf/console/application.h" #include "pf/console/command.h" using namespace pf_support; using namespace pf_console; // Static member must be initialized. std::string Command::default_name_{"unknown"}; void Command::merge_application_definition(bool merge_args) { if (is_null(app_) or !is_null(full_definition_)) return; unique_move(InputDefinition, new InputDefinition(), full_definition_); full_definition_->set_options(array_values(definition_->get_options())); full_definition_->add_options( array_values(app_->get_definition()->get_options())); if (merge_args) { full_definition_->set_arguments( array_values(app_->get_definition()->get_arguments())); full_definition_->add_arguments(array_values(definition_->get_arguments())); } else { full_definition_->set_arguments(array_values(definition_->get_arguments())); } } uint8_t Command::run(Input *input, Output *output) { uint8_t r{0}; merge_application_definition(); // bind the input against the command specific arguments/options try { input->bind(get_definition(), is_parse_input()); } catch (std::exception &e) { if (!ignore_validation_errors_) throw std::runtime_error(e.what()); } initialize(input, output); // Set process title. if (!process_title_.empty()) { } if (input->is_interactive()) { interact(input, output); } // The command name argument is often omitted when a command is executed // directly with its run() method. // It would fail the validation if we didn't make sure the command argument // is present, since it's required by the application. if (input->has_argument("command") && input->get_argument("command") == "") { input->set_argument("command", name_); } input->validate(); if (code_) { r = code_(input, output); } else { r = execute(input, output); } return r; }
更多
可以在github上找到完整的项目:https://github.com/viticm/plain