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