diff --git a/main/context_imp.cpp b/main/context_imp.cpp index 38154858d729182ab2ee6305aebf3224a3dc890c..0a092e495e5a60250df22973bc751322ad7ea407 100644 --- a/main/context_imp.cpp +++ b/main/context_imp.cpp @@ -15,17 +15,20 @@ ContextImp::ContextImp() : sp_thread_pool_(new eventx::ThreadPool(sp_loop_)), sp_timer_pool_(new eventx::TimerPool(sp_loop_)), sp_terminal_(new terminal::Terminal), - sp_telnetd_(new terminal::Telnetd(sp_loop_, sp_terminal_)) + sp_telnetd_(new terminal::Telnetd(sp_loop_, sp_terminal_)), + sp_tcp_rpc_(new terminal::TcpRpc(sp_loop_, sp_terminal_)) { assert(sp_loop_ != nullptr); assert(sp_thread_pool_ != nullptr); assert(sp_timer_pool_ != nullptr); assert(sp_terminal_ != nullptr); assert(sp_telnetd_ != nullptr); + assert(sp_tcp_rpc_ != nullptr); } ContextImp::~ContextImp() { + delete sp_tcp_rpc_; delete sp_telnetd_; delete sp_terminal_; delete sp_timer_pool_; @@ -64,6 +67,14 @@ bool ContextImp::initialize(const Json &cfg) telnetd_init_ok = true; } } + if (cfg.contains("tcp_rpc")) { + auto &js_tcp_rpc = cfg["tcp_rpc"]; + if (js_tcp_rpc.contains("bind")) { + auto &js_tcp_rpc_bind = js_tcp_rpc["bind"]; + if (sp_tcp_rpc_->initialize(js_tcp_rpc_bind.get())) + tcp_rpc_init_ok = true; + } + } buildTerminalNodes(); @@ -74,17 +85,25 @@ bool ContextImp::start() { if (telnetd_init_ok) sp_telnetd_->start(); + + if (tcp_rpc_init_ok) + sp_tcp_rpc_->start(); + return true; } void ContextImp::stop() { + if (tcp_rpc_init_ok) + sp_tcp_rpc_->stop(); + if (telnetd_init_ok) sp_telnetd_->stop(); } void ContextImp::cleanup() { + sp_tcp_rpc_->cleanup(); sp_telnetd_->cleanup(); sp_timer_pool_->cleanup(); sp_thread_pool_->cleanup(); diff --git a/main/context_imp.h b/main/context_imp.h index a19c76c081e6d50f69c93ff527d867bc9c453c2f..a414c19d05f0e1db214427f2f2b43edc17d6f50f 100644 --- a/main/context_imp.h +++ b/main/context_imp.h @@ -5,7 +5,8 @@ #include #include -#include +#include +#include namespace tbox::main { @@ -38,7 +39,9 @@ class ContextImp : public Context { terminal::Terminal *sp_terminal_ = nullptr; terminal::Telnetd *sp_telnetd_ = nullptr; + terminal::TcpRpc *sp_tcp_rpc_ = nullptr; bool telnetd_init_ok = false; + bool tcp_rpc_init_ok = false; }; } diff --git a/network/tcp_server.cpp b/network/tcp_server.cpp index 5a419f3b20bd404a4cf90fb1439f5a91e8ed5191..aec6a851114c4185431b157bc9044461fa2877c5 100644 --- a/network/tcp_server.cpp +++ b/network/tcp_server.cpp @@ -137,9 +137,12 @@ bool TcpServer::send(const ClientToken &client, const void *data_ptr, size_t dat bool TcpServer::disconnect(const ClientToken &client) { - auto conn = d_->conns.at(client); - if (conn != nullptr) - return conn->disconnect(); + auto conn = d_->conns.remove(client); + if (conn != nullptr) { + conn->disconnect(); + d_->wp_loop->run([conn] { delete conn; }); + return true; + } return false; } diff --git a/terminal/Makefile b/terminal/Makefile index 7e39803714e3c95b966dfe06d2613c95112522aa..b80a7006f0ac2ed43ee86ea84722d1be9991906d 100644 --- a/terminal/Makefile +++ b/terminal/Makefile @@ -9,14 +9,15 @@ HEAD_FILES = \ terminal_interact.h \ terminal_nodes.h \ session.h \ - telnetd.h \ terminal.h \ + service/telnetd.h \ + service/tcp_rpc.h \ CPP_SRC_FILES = \ - telnetd.cpp \ terminal.cpp \ session.cpp \ - impl/telnetd.cpp \ + service/telnetd.cpp \ + service/tcp_rpc.cpp \ impl/terminal.cpp \ impl/terminal_key_events.cpp \ impl/terminal_commands.cpp \ @@ -24,6 +25,8 @@ CPP_SRC_FILES = \ impl/key_event_scanner.cpp \ impl/dir_node.cpp \ impl/func_node.cpp \ + impl/service/telnetd.cpp \ + impl/service/tcp_rpc.cpp \ CXXFLAGS := -DLOG_MODULE_ID='"terminal"' $(CXXFLAGS) diff --git a/terminal/example/basic/.gitignore b/terminal/example/tcp_rpc/.gitignore similarity index 100% rename from terminal/example/basic/.gitignore rename to terminal/example/tcp_rpc/.gitignore diff --git a/terminal/example/basic/Makefile b/terminal/example/tcp_rpc/Makefile similarity index 88% rename from terminal/example/basic/Makefile rename to terminal/example/tcp_rpc/Makefile index 3cdd0167370851ec8f878e18b33cab886feda613..bb4d9fa9c4d8c26e8130f5c7d6944622e61e9936 100644 --- a/terminal/example/basic/Makefile +++ b/terminal/example/tcp_rpc/Makefile @@ -3,7 +3,7 @@ include ../build_env.mk TARGET := demo OBJECTS := main.o CXXFLAGS += -ggdb -DLOG_MODULE_ID='"demo"' -LDFLAGS += -L.. -ltbox_terminal -ltbox_util -ltbox_network -ltbox_event -ltbox_base -levent_core -lev -lpthread +LDFLAGS += -L.. -ltbox_terminal -ltbox_util -ltbox_network -ltbox_event -ltbox_base -lpthread CXXFLAGS += -fsanitize=address -fno-omit-frame-pointer LDFLAGS += -fsanitize=address -static-libasan diff --git a/terminal/example/tcp_rpc/main.cpp b/terminal/example/tcp_rpc/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..13815ac69a658fdc2d3101fe5f0b1e0128165b75 --- /dev/null +++ b/terminal/example/tcp_rpc/main.cpp @@ -0,0 +1,147 @@ +#include + +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +using namespace tbox; +using namespace tbox::event; +using namespace tbox::terminal; + +void BuildNodes(TerminalNodes &term, Loop *wp_loop); + +int main(int argc, char **argv) +{ + std::string bind_addr = "0.0.0.0:12345"; + if (argc >= 2) + bind_addr = argv[1]; + + LogOutput_Initialize(argv[0]); + + auto sp_loop = Loop::New(); + SetScopeExitAction([sp_loop] { delete sp_loop; }); + + Terminal term; + TcpRpc rpc(sp_loop, &term); + if (!rpc.initialize(bind_addr)) { + std::cout << "Error: rpc init fail" << std::endl; + return 0; + } + + //! 注册ctrl+C停止信号 + auto *sp_stop_ev = sp_loop->newSignalEvent(); + SetScopeExitAction([sp_stop_ev] { delete sp_stop_ev; }); + sp_stop_ev->initialize(std::set{SIGINT,SIGTERM}, Event::Mode::kOneshot); + //! 指定ctrl+C时要做的事务 + sp_stop_ev->setCallback( + [&] (int) { + rpc.stop(); + sp_loop->exitLoop(); //! (3) 退出事件循环 + } + ); + sp_stop_ev->enable(); + + BuildNodes(term, sp_loop); + + rpc.start(); + + LogInfo("Start"); + sp_loop->runLoop(Loop::Mode::kForever); + LogInfo("Stoped"); + + return 0; +} + +void BuildNodes(TerminalNodes &term, Loop *wp_loop) +{ + /** + * sync_func 就是同步执行的命令函数 + * 当他被执行时,只需要调用 Session 的 send() 方法就可以输出信息到终端 + */ + Func sync_func = \ + [](const Session &s, const Args &args) { + std::stringstream ss; + ss << "This is sync_func.\r\nArgs:\r\n"; + for (size_t i = 0; i < args.size(); ++i) + ss << '[' << i << "]: " << args.at(i) << "\r\n"; + s.send(ss.str()); + }; + + /** + * async_func 是异步执行的命令函数 + * 它的结果打印会在命令函数执行完成之后, + * + * 这种情冲常用于异步事件中,比如某命令的动作是发送HTTP请求,将请求的结果打印出来 + * 执行命令时,命令只是发出请求就结束。而结果则是在后面则到返回结果,或检测到异常 + * 时才会输出。 + */ + Func async_func = \ + [=](const Session &s, const Args &args) { + if (args.size() < 2) { + s.send(std::string("Usage: ") + args[0] + " \r\n"); + return; + } + + auto name = args[1]; + //! 创建一个定时器,令其每秒打印 + auto sp_timer = wp_loop->newTimerEvent(); + sp_timer->initialize(std::chrono::seconds(1), Event::Mode::kPersist); + sp_timer->setCallback( + [=] { //! 注意:这里用的是 =,而不是 & 。用意是捕获 s 的副本,而不是引用它。 + if (!s.isValid()) { //! 可以检查 s 对应的 Session 是否有效,如果无效则可以不做任何事情 + sp_timer->disable(); + wp_loop->run([sp_timer] { delete sp_timer; }); + return; + } + s.send(std::string("timer ") + name + " timeout\r\n"); + } + ); + sp_timer->enable(); + s.send(std::string("timer ") + name + " start\r\n"); + }; + +/** +构建如下结点树: +|-- dir1 +| |-- dir1_1 +| | |-- async* +| | `-- root(R) +| `-- dir1_2 +| `-- sync* +|-- dir2 +`-- sync* +*/ + auto sync_func_token = term.createFuncNode(sync_func, "This is sync func"); + auto async_func_token = term.createFuncNode(async_func, "This is async func"); + + auto dir1_token = term.createDirNode("This is dir1"); + auto dir2_token = term.createDirNode(); + + auto dir1_1_token = term.createDirNode(); + auto dir1_2_token = term.createDirNode(); + + term.mountNode(dir1_1_token, async_func_token, "async"); + term.mountNode(dir1_2_token, sync_func_token, "sync"); + + term.mountNode(dir1_token, dir1_1_token, "dir1_1"); + term.mountNode(dir1_token, dir1_2_token, "dir1_2"); + + term.mountNode(term.rootNode(), sync_func_token, "sync"); + term.mountNode(term.rootNode(), dir1_token, "dir1"); + term.mountNode(term.rootNode(), dir2_token, "dir2"); + + term.mountNode(dir1_1_token, term.rootNode(), "root"); //! 循环引用 +} diff --git a/terminal/example/telnetd/.gitignore b/terminal/example/telnetd/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..69ff7396662e25b7bcacccf28267d6573e901777 --- /dev/null +++ b/terminal/example/telnetd/.gitignore @@ -0,0 +1 @@ +/demo diff --git a/terminal/example/telnetd/Makefile b/terminal/example/telnetd/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..bb4d9fa9c4d8c26e8130f5c7d6944622e61e9936 --- /dev/null +++ b/terminal/example/telnetd/Makefile @@ -0,0 +1,17 @@ +include ../build_env.mk + +TARGET := demo +OBJECTS := main.o +CXXFLAGS += -ggdb -DLOG_MODULE_ID='"demo"' +LDFLAGS += -L.. -ltbox_terminal -ltbox_util -ltbox_network -ltbox_event -ltbox_base -lpthread + +CXXFLAGS += -fsanitize=address -fno-omit-frame-pointer +LDFLAGS += -fsanitize=address -static-libasan + +all : $(TARGET) + +$(TARGET) : $(OBJECTS) + $(CXX) -o $@ $^ $(LDFLAGS) + +clean: + rm -rf $(OBJECTS) diff --git a/terminal/example/basic/main.cpp b/terminal/example/telnetd/main.cpp similarity index 96% rename from terminal/example/basic/main.cpp rename to terminal/example/telnetd/main.cpp index c16ac4e90efae46c1b10f944dd5eb48f029a338f..907d8faca4d8851ed3e0321dbde7ffe7f6244715 100644 --- a/terminal/example/basic/main.cpp +++ b/terminal/example/telnetd/main.cpp @@ -14,7 +14,7 @@ #include #include -#include +#include #include using namespace tbox; @@ -44,10 +44,10 @@ int main(int argc, char **argv) //! 注册ctrl+C停止信号 auto *sp_stop_ev = sp_loop->newSignalEvent(); SetScopeExitAction([sp_stop_ev] { delete sp_stop_ev; }); - sp_stop_ev->initialize(SIGINT, Event::Mode::kOneshot); + sp_stop_ev->initialize(std::set{SIGINT,SIGTERM}, Event::Mode::kOneshot); //! 指定ctrl+C时要做的事务 sp_stop_ev->setCallback( - [&] { + [&] (int) { telnetd.stop(); sp_loop->exitLoop(); //! (3) 退出事件循环 } diff --git a/terminal/impl/key_event_scanner.cpp b/terminal/impl/key_event_scanner.cpp index 731cd090f17187651070c41accd90eb2f4c61f28..8ecf8323fac531edf523f11f54103b4c45e0b783 100644 --- a/terminal/impl/key_event_scanner.cpp +++ b/terminal/impl/key_event_scanner.cpp @@ -23,6 +23,9 @@ KeyEventScanner::Status KeyEventScanner::next(uint8_t byte) } else if (byte == 0x0d) { step_ = Step::k0d; return Status::kUnsure; + } else if (byte == 0x0a) { + result_ = Result::kEnter; + return Status::kEnsure; } else if (byte == 0x1b) { step_ = Step::k1b; return Status::kUnsure; diff --git a/terminal/impl/key_event_scanner.h b/terminal/impl/key_event_scanner.h index 505b5a56e7aaa2822f7bcc9addf5796472b88d57..79c6f049aea0a47574cced3e663e9f2d954bcea3 100644 --- a/terminal/impl/key_event_scanner.h +++ b/terminal/impl/key_event_scanner.h @@ -15,6 +15,7 @@ namespace tbox::terminal { * - Backspace: 7f|08 * - ESC: 1b * - Enter: 0d [00|0a] + * 0a * * - Alt+?: 1b ? * - Ctrl+Alt+?: c2 ?+0x20 diff --git a/terminal/impl/key_event_scanner_test.cpp b/terminal/impl/key_event_scanner_test.cpp index 9f686b4c371b0cc4cc92f0a36515a5ae9307c491..ca814319e5be4b94b382649b106262be25030dc3 100644 --- a/terminal/impl/key_event_scanner_test.cpp +++ b/terminal/impl/key_event_scanner_test.cpp @@ -93,6 +93,13 @@ TEST(KeyEventScanner, Enter_3) EXPECT_EQ(ks.result(), KeyEventScanner::Result::kEnter); } +TEST(KeyEventScanner, Enter_4) +{ + KeyEventScanner ks; + EXPECT_EQ(ks.next(0x0a), KeyEventScanner::Status::kEnsure); + EXPECT_EQ(ks.result(), KeyEventScanner::Result::kEnter); +} + TEST(KeyEventScanner, Alt) { KeyEventScanner ks; diff --git a/terminal/impl/service/tcp_rpc.cpp b/terminal/impl/service/tcp_rpc.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b77d859629128922172fe6e7fde41129afc30eb6 --- /dev/null +++ b/terminal/impl/service/tcp_rpc.cpp @@ -0,0 +1,136 @@ +#include "tcp_rpc.h" + +#include +#include + +#include + +namespace tbox { +namespace terminal { + +TcpRpc::Impl::Impl(event::Loop *wp_loop, TerminalInteract *wp_terminal) : + wp_loop_(wp_loop), + wp_terminal_(wp_terminal), + sp_tcp_(new TcpServer(wp_loop)) +{ + assert(wp_loop_ != nullptr); + assert(wp_terminal_ != nullptr); + assert(sp_tcp_ != nullptr); +} + +TcpRpc::Impl::~Impl() +{ + delete sp_tcp_; +} + +bool TcpRpc::Impl::initialize(const std::string &bind_addr_str) +{ + auto bind_addr = SockAddr::FromString(bind_addr_str); + if (!sp_tcp_->initialize(bind_addr)) + return false; + + sp_tcp_->setConnectedCallback(std::bind(&Impl::onTcpConnected, this, _1)); + sp_tcp_->setReceiveCallback(std::bind(&Impl::onTcpReceived, this, _1, _2), 0); + sp_tcp_->setDisconnectedCallback(std::bind(&Impl::onTcpDisconnected, this, _1)); + return true; +} + +bool TcpRpc::Impl::start() +{ + return sp_tcp_->start(); +} + +void TcpRpc::Impl::stop() +{ + sp_tcp_->stop(); +} + +void TcpRpc::Impl::cleanup() +{ + sp_tcp_->cleanup(); +} + +bool TcpRpc::Impl::send(const SessionToken &st, const std::string &str) +{ + auto ct = session_to_client_.at(st); + if (st.isNull()) + return false; + + send(ct, str.c_str(), str.size()); + return true; +} + +bool TcpRpc::Impl::send(const SessionToken &st, char ch) +{ + auto ct = session_to_client_.at(st); + if (st.isNull()) + return false; + + send(ct, &ch, 1); + return true; +} + +bool TcpRpc::Impl::endSession(const SessionToken &st) +{ + auto ct = session_to_client_.at(st); + if (ct.isNull()) + return false; + + //! 委托执行,否则会出自我销毁的异常 + wp_loop_->run( + [this, st, ct] { + client_to_session_.erase(ct); + session_to_client_.erase(st); + sp_tcp_->disconnect(ct); + } + ); + + return true; +} + +bool TcpRpc::Impl::isValid(const SessionToken &st) const +{ + return session_to_client_.find(st) != session_to_client_.end(); +} + +void TcpRpc::Impl::onTcpConnected(const TcpServer::ClientToken &ct) +{ + cout << ct.id() << " connected" << endl; + + auto st = wp_terminal_->newSession(this); + client_to_session_[ct] = st; + session_to_client_[st] = ct; + + wp_terminal_->setOptions(st, TerminalInteract::kQuietMode); + wp_terminal_->onBegin(st); +} + +void TcpRpc::Impl::onTcpDisconnected(const TcpServer::ClientToken &ct) +{ + cout << ct.id() << " disconnected" << endl; + + auto st = client_to_session_.at(ct); + client_to_session_.erase(ct); + session_to_client_.erase(st); + wp_terminal_->deleteSession(st); +} + +bool TcpRpc::Impl::send(const TcpServer::ClientToken &ct, const void *data_ptr, size_t data_size) +{ + return sp_tcp_->send(ct, data_ptr, data_size); +} + +void TcpRpc::Impl::onTcpReceived(const TcpServer::ClientToken &ct, Buffer &buff) +{ + onRecvString(ct, std::string(reinterpret_cast(buff.readableBegin()), buff.readableSize())); + buff.hasReadAll(); +} + +void TcpRpc::Impl::onRecvString(const TcpServer::ClientToken &ct, const std::string &str) +{ + auto st = client_to_session_.at(ct); + wp_terminal_->onRecvString(st, str); +} + +} +} diff --git a/terminal/impl/service/tcp_rpc.h b/terminal/impl/service/tcp_rpc.h new file mode 100644 index 0000000000000000000000000000000000000000..3c01a9900e4eaf707cce006bd443202f66ab708b --- /dev/null +++ b/terminal/impl/service/tcp_rpc.h @@ -0,0 +1,57 @@ +#ifndef TBOX_TERMINAL_TCP_RPC_IMP_H_20220306 +#define TBOX_TERMINAL_TCP_RPC_IMP_H_20220306 + +#include + +#include + +#include "../../connection.h" +#include "../../service/tcp_rpc.h" +#include "../../terminal_interact.h" + +namespace tbox { +namespace terminal { + +using namespace std; +using namespace std::placeholders; +using namespace event; +using namespace network; + +class TcpRpc::Impl : Connection { + public: + Impl(Loop *wp_loop, TerminalInteract *wp_terminal); + virtual ~Impl(); + + public: + bool initialize(const std::string &bind_addr); + bool start(); + void stop(); + void cleanup(); + + public: + bool send(const SessionToken &st, char ch) override; + bool send(const SessionToken &st, const std::string &str) override; + bool endSession(const SessionToken &st) override; + bool isValid(const SessionToken &st) const override; + + protected: + bool send(const TcpServer::ClientToken &ct, const void *data_ptr, size_t data_size); + + void onTcpConnected(const TcpServer::ClientToken &ct); + void onTcpDisconnected(const TcpServer::ClientToken &ct); + void onTcpReceived(const TcpServer::ClientToken &ct, Buffer &buff); + void onRecvString(const TcpServer::ClientToken &ct, const std::string &str); + + private: + Loop *wp_loop_ = nullptr; + TerminalInteract *wp_terminal_ = nullptr; + TcpServer *sp_tcp_ = nullptr; + + map session_to_client_; + map client_to_session_; +}; + +} +} + +#endif //TBOX_TERMINAL_TCP_RPC_IMP_H_20220306 diff --git a/terminal/impl/telnetd.cpp b/terminal/impl/service/telnetd.cpp similarity index 95% rename from terminal/impl/telnetd.cpp rename to terminal/impl/service/telnetd.cpp index f0632f4fc44d03f408fb0301b75fde7fd37ed38c..0749ae8534e4d31384b7586e99c26b7c378075e0 100644 --- a/terminal/impl/telnetd.cpp +++ b/terminal/impl/service/telnetd.cpp @@ -6,7 +6,7 @@ #include #include -#include "../terminal_interact.h" +#include "../../terminal_interact.h" namespace tbox::terminal { @@ -225,9 +225,16 @@ void Telnetd::Impl::onRecvString(const TcpServer::ClientToken &ct, const std::st void Telnetd::Impl::onRecvNego(const TcpServer::ClientToken &ct, Cmd cmd, Opt opt) { LogTrace("cmd:%x, opt:%x", cmd, opt); + auto st = client_to_session_.at(ct); if (cmd == Cmd::kDONT) sendNego(ct, Cmd::kWONT, opt); + else if (cmd == Cmd::kDO) { + if (opt == Opt::kECHO) { + auto opts = wp_terminal_->getOptions(st); + wp_terminal_->setOptions(st, opts | TerminalInteract::kEnableEcho); + } + } } void Telnetd::Impl::onRecvCmd(const TcpServer::ClientToken &ct, Cmd cmd) diff --git a/terminal/impl/telnetd.h b/terminal/impl/service/telnetd.h similarity index 97% rename from terminal/impl/telnetd.h rename to terminal/impl/service/telnetd.h index 8f5b27a1f7f94ed3c269cda29c16bf741dcd7ccc..85b2a7ac86d318e89e09924f693a864ce8c51097 100644 --- a/terminal/impl/telnetd.h +++ b/terminal/impl/service/telnetd.h @@ -5,8 +5,8 @@ #include -#include "../telnetd.h" -#include "../connection.h" +#include "../../service/telnetd.h" +#include "../../connection.h" namespace tbox::terminal { diff --git a/terminal/impl/session_context.h b/terminal/impl/session_context.h index 9c6744f9b0e485578c1caad73135873441249659..83b9d14012328fd1a57e49426c28c99415394b2a 100644 --- a/terminal/impl/session_context.h +++ b/terminal/impl/session_context.h @@ -12,6 +12,8 @@ struct SessionContext { Connection *wp_conn = nullptr; SessionToken token; + uint32_t options = 0; + std::string curr_input; size_t cursor = 0; diff --git a/terminal/impl/terminal.cpp b/terminal/impl/terminal.cpp index e7aabe416ca3e7a3bffe53aff06ce7a31be768af..0a3967962e728ee49d5b97cea9a6cf22c8f8a0fc 100644 --- a/terminal/impl/terminal.cpp +++ b/terminal/impl/terminal.cpp @@ -54,18 +54,38 @@ bool Terminal::Impl::deleteSession(const SessionToken &st) return false; } +uint32_t Terminal::Impl::getOptions(const SessionToken &st) const +{ + auto s = sessions_.at(st); + if (s == nullptr) + return 0; + + return s->options; +} + +void Terminal::Impl::setOptions(const SessionToken &st, uint32_t options) +{ + auto s = sessions_.at(st); + if (s == nullptr) + return; + + s->options = options; +} + bool Terminal::Impl::onBegin(const SessionToken &st) { auto s = sessions_.at(st); if (s == nullptr) return false; - s->wp_conn->send(st, - "\r\n" - "Welcome to CppTBox Terminal.\r\n" - "Type 'help' for more information.\r\n" - "\r\n" - ); + if (!(s->options & kQuietMode)) { + s->wp_conn->send(st, + "\r\n" + "Welcome to CppTBox Terminal.\r\n" + "Type 'help' for more information.\r\n" + "\r\n" + ); + } printPrompt(s); return true; @@ -161,7 +181,8 @@ bool Terminal::Impl::onRecvWindowSize(const SessionToken &st, uint16_t w, uint16 void Terminal::Impl::printPrompt(SessionContext *s) { - s->wp_conn->send(s->token, "# "); + if (!(s->options & kQuietMode)) + s->wp_conn->send(s->token, "# "); } void Terminal::Impl::printHelp(SessionContext *s) @@ -182,12 +203,17 @@ void Terminal::Impl::printHelp(SessionContext *s) "- !n Run the n command of history\r\n" "- !-n Run the last n command of history\r\n" "- !! Run last command\r\n" - "\r\n" - "Besides, UP,DOWN,LEFT,RIGHT,HOME,END,DELETE keys are available.\r\n" - "Try them.\r\n" - "\r\n" - ; + "\r\n"; s->wp_conn->send(s->token, help_str); + + if (s->options & kEnableEcho) { + const char *extra_str = \ + "Besides, UP,DOWN,LEFT,RIGHT,HOME,END,DELETE keys are available.\r\n" + "Try them.\r\n" + "\r\n" + ; + s->wp_conn->send(s->token, extra_str); + } } } diff --git a/terminal/impl/terminal.h b/terminal/impl/terminal.h index aa1590a8514eb538bf1e2dd3f946e4cbe54c0c70..467d3e64a4b1de561e7b74b01588c1f35b6fd1b9 100644 --- a/terminal/impl/terminal.h +++ b/terminal/impl/terminal.h @@ -19,6 +19,9 @@ class Terminal::Impl { SessionToken newSession(Connection *wp_conn); bool deleteSession(const SessionToken &st); + uint32_t getOptions(const SessionToken &st) const; + void setOptions(const SessionToken &st, uint32_t options); + bool onBegin(const SessionToken &st); bool onExit(const SessionToken &st); @@ -48,7 +51,8 @@ class Terminal::Impl { void printPrompt(SessionContext *s); void printHelp(SessionContext *s); - bool executeCmdline(SessionContext *s); + bool execute(SessionContext *s); + bool executeCmd(SessionContext *s, const std::string &cmdline); void executeCdCmd(SessionContext *s, const Args &args); void executeHelpCmd(SessionContext *s, const Args &args); diff --git a/terminal/impl/terminal_commands.cpp b/terminal/impl/terminal_commands.cpp index 4d0354ef9aa61e34c66de2d5b7a4f4057d69fe3d..10d5cff4b4ca7e399c7c994458d8413982e535d5 100644 --- a/terminal/impl/terminal_commands.cpp +++ b/terminal/impl/terminal_commands.cpp @@ -5,6 +5,7 @@ #include #include +#include #include "session_context.h" #include "dir_node.h" @@ -15,9 +16,21 @@ namespace tbox::terminal { using namespace std; -bool Terminal::Impl::executeCmdline(SessionContext *s) +bool Terminal::Impl::execute(SessionContext *s) +{ + std::vector cmdlines; + util::string::Split(s->curr_input, ";", cmdlines); + + for (auto &cmdline : cmdlines) { + if (!executeCmd(s, cmdline)) + return false; + } + + return true; +} + +bool Terminal::Impl::executeCmd(SessionContext *s, const std::string &cmdline) { - auto cmdline = s->curr_input; if (cmdline.empty()) return false; @@ -150,7 +163,8 @@ void Terminal::Impl::executeHistoryCmd(SessionContext *s, const Args &args) void Terminal::Impl::executeExitCmd(SessionContext *s, const Args &args) { - s->wp_conn->send(s->token, "Bye!\r\n"); + if (!(s->options & kQuietMode)) + s->wp_conn->send(s->token, "Bye!\r\n"); s->wp_conn->endSession(s->token); } @@ -278,7 +292,7 @@ bool Terminal::Impl::executeRunHistoryCmd(SessionContext *s, const Args &args) string sub_cmd = args[0].substr(1); if (sub_cmd == "!") { s->curr_input = s->history.back(); - return executeCmdline(s); + return execute(s); } try { @@ -298,7 +312,7 @@ bool Terminal::Impl::executeRunHistoryCmd(SessionContext *s, const Args &args) if (is_index_valid) { s->wp_conn->send(s->token, s->curr_input + "\r\n"); - return executeCmdline(s); + return execute(s); } else s->wp_conn->send(s->token, "Error: index out of range\r\n"); } catch (const invalid_argument &e) { diff --git a/terminal/impl/terminal_key_events.cpp b/terminal/impl/terminal_key_events.cpp index 588fe81978a7f6fd22cdf9d390ced7198a2bbeea..445c3c1b34be03541e060c4223862106f1516e61 100644 --- a/terminal/impl/terminal_key_events.cpp +++ b/terminal/impl/terminal_key_events.cpp @@ -21,28 +21,31 @@ namespace { void Terminal::Impl::onChar(SessionContext *s, char ch) { - s->wp_conn->send(s->token, ch); - if (s->cursor == s->curr_input.size()) s->curr_input.push_back(ch); else s->curr_input.insert(s->cursor, 1, ch); s->cursor++; - stringstream ss; - ss << s->curr_input.substr(s->cursor) - << string((s->curr_input.size() - s->cursor), '\b'); + if (s->options & kEnableEcho) { + s->wp_conn->send(s->token, ch); + + stringstream ss; + ss << s->curr_input.substr(s->cursor) + << string((s->curr_input.size() - s->cursor), '\b'); - auto refresh_str = ss.str(); - if (!refresh_str.empty()) - s->wp_conn->send(s->token, refresh_str); + auto refresh_str = ss.str(); + if (!refresh_str.empty()) + s->wp_conn->send(s->token, refresh_str); + } } void Terminal::Impl::onEnterKey(SessionContext *s) { - s->wp_conn->send(s->token, "\r\n"); + if (s->options & kEnableEcho) + s->wp_conn->send(s->token, "\r\n"); - if (executeCmdline(s)) { + if (execute(s)) { s->history.push_back(s->curr_input); if (s->history.size() > HISTORY_MAX_SIZE) s->history.pop_front(); @@ -67,11 +70,13 @@ void Terminal::Impl::onBackspaceKey(SessionContext *s) s->cursor--; - stringstream ss; - ss << '\b' << s->curr_input.substr(s->cursor) << ' ' - << string((s->curr_input.size() - s->cursor + 1), '\b'); + if (s->options & kEnableEcho) { + stringstream ss; + ss << '\b' << s->curr_input.substr(s->cursor) << ' ' + << string((s->curr_input.size() - s->cursor + 1), '\b'); - s->wp_conn->send(s->token, ss.str()); + s->wp_conn->send(s->token, ss.str()); + } } void Terminal::Impl::onDeleteKey(SessionContext *s) @@ -81,11 +86,13 @@ void Terminal::Impl::onDeleteKey(SessionContext *s) s->curr_input.erase((s->cursor), 1); - stringstream ss; - ss << s->curr_input.substr(s->cursor) << ' ' - << string((s->curr_input.size() - s->cursor + 1), '\b'); + if (s->options & kEnableEcho) { + stringstream ss; + ss << s->curr_input.substr(s->cursor) << ' ' + << string((s->curr_input.size() - s->cursor + 1), '\b'); - s->wp_conn->send(s->token, ss.str()); + s->wp_conn->send(s->token, ss.str()); + } } void Terminal::Impl::onTabKey(SessionContext *s) diff --git a/terminal/service/tcp_rpc.cpp b/terminal/service/tcp_rpc.cpp new file mode 100644 index 0000000000000000000000000000000000000000..fda6e7b6fb5f3896cd7f31216cb6419b7287f53d --- /dev/null +++ b/terminal/service/tcp_rpc.cpp @@ -0,0 +1,39 @@ +#include "tcp_rpc.h" +#include "../impl/service/tcp_rpc.h" + +namespace tbox { +namespace terminal { + +TcpRpc::TcpRpc(event::Loop *wp_loop, TerminalInteract *wp_terminal) : + impl_(new Impl(wp_loop, wp_terminal)) +{ + assert(impl_ != nullptr); +} + +TcpRpc::~TcpRpc() +{ + delete impl_; +} + +bool TcpRpc::initialize(const std::string &bind_addr) +{ + return impl_->initialize(bind_addr); +} + +bool TcpRpc::start() +{ + return impl_->start(); +} + +void TcpRpc::stop() +{ + return impl_->stop(); +} + +void TcpRpc::cleanup() +{ + return impl_->cleanup(); +} + +} +} diff --git a/terminal/service/tcp_rpc.h b/terminal/service/tcp_rpc.h new file mode 100644 index 0000000000000000000000000000000000000000..e2776d5e860253c10a5a046eedb9078d05aec10e --- /dev/null +++ b/terminal/service/tcp_rpc.h @@ -0,0 +1,30 @@ +#ifndef TBOX_TERMINAL_TCP_RPC_H_20220306 +#define TBOX_TERMINAL_TCP_RPC_H_20220306 + +#include + +namespace tbox { +namespace terminal { + +class TerminalInteract; + +class TcpRpc { + public: + TcpRpc(event::Loop *wp_loop, TerminalInteract *wp_terminal); + ~TcpRpc(); + + public: + bool initialize(const std::string &bind_addr); + bool start(); + void stop(); + void cleanup(); + + private: + class Impl; + Impl *impl_ = nullptr; +}; + +} +} + +#endif //TBOX_TERMINAL_TCP_RPC_H_20220306 diff --git a/terminal/telnetd.cpp b/terminal/service/telnetd.cpp similarity index 93% rename from terminal/telnetd.cpp rename to terminal/service/telnetd.cpp index dfa950a009e46904ccc2c7aa9c5661e53c4e899f..df67ed849a60d3ce6934602b8484ccda29f209a4 100644 --- a/terminal/telnetd.cpp +++ b/terminal/service/telnetd.cpp @@ -1,5 +1,5 @@ #include "telnetd.h" -#include "impl/telnetd.h" +#include "../impl/service/telnetd.h" namespace tbox::terminal { diff --git a/terminal/telnetd.h b/terminal/service/telnetd.h similarity index 100% rename from terminal/telnetd.h rename to terminal/service/telnetd.h diff --git a/terminal/terminal.cpp b/terminal/terminal.cpp index 25af2997adf7e2c800d6e43faf9ce7daea10bc7f..907d20ae0dbdefa94b6e601ba7b21ed181b501bf 100644 --- a/terminal/terminal.cpp +++ b/terminal/terminal.cpp @@ -26,6 +26,16 @@ bool Terminal::deleteSession(const SessionToken &st) return impl_->deleteSession(st); } +uint32_t Terminal::getOptions(const SessionToken &st) const +{ + return impl_->getOptions(st); +} + +void Terminal::setOptions(const SessionToken &st, uint32_t options) +{ + impl_->setOptions(st, options); +} + bool Terminal::onBegin(const SessionToken &st) { return impl_->onBegin(st); diff --git a/terminal/terminal.h b/terminal/terminal.h index bec2e7bf60a6e7c3b5c711dbca6a60f638085f26..47642189e67d1bd1817e107b8cd388d2602d16cf 100644 --- a/terminal/terminal.h +++ b/terminal/terminal.h @@ -16,6 +16,9 @@ class Terminal : public TerminalInteract, SessionToken newSession(Connection *wp_conn) override; bool deleteSession(const SessionToken &st) override; + uint32_t getOptions(const SessionToken &st) const override; + void setOptions(const SessionToken &st, uint32_t options) override; + bool onBegin(const SessionToken &st) override; bool onRecvString(const SessionToken &st, const std::string &str) override; bool onRecvWindowSize(const SessionToken &st, uint16_t w, uint16_t h) override; diff --git a/terminal/terminal_interact.h b/terminal/terminal_interact.h index bd12b9130ff295638263f0330effc6732bde0478..18700fcd47f6a9c962848d67c0356dac1664e1fb 100644 --- a/terminal/terminal_interact.h +++ b/terminal/terminal_interact.h @@ -12,6 +12,13 @@ class TerminalInteract { virtual SessionToken newSession(Connection *wp_conn) = 0; virtual bool deleteSession(const SessionToken &st) = 0; + enum Option : uint32_t { + kEnableEcho = 0x01, + kQuietMode = 0x02, //! 安静模式,不主动打印提示信息 + }; + virtual uint32_t getOptions(const SessionToken &st) const = 0; + virtual void setOptions(const SessionToken &st, uint32_t options) = 0; + virtual bool onBegin(const SessionToken &st) = 0; virtual bool onExit(const SessionToken &st) = 0; virtual bool onRecvString(const SessionToken &st, const std::string &str) = 0;