├── 17-action-tree.md ├── 50-coffee-machine.md ├── .gitignore ├── examples ├── 05-two-modules │ ├── my │ │ ├── app.mk │ │ ├── module.h │ │ └── module.cpp │ ├── your │ │ ├── app.mk │ │ ├── module.h │ │ └── module.cpp │ ├── build.cpp │ ├── apps.cpp │ ├── info.cpp │ └── Makefile ├── 04-normal-app-demo │ ├── my │ │ ├── app.mk │ │ ├── module.h │ │ └── module.cpp │ ├── build.cpp │ ├── apps.cpp │ ├── info.cpp │ └── Makefile ├── 50-coffee-machine │ ├── coffee │ │ ├── app.mk │ │ ├── app.h │ │ └── app.cpp │ ├── coffee-machine │ ├── build.cpp │ ├── apps.cpp │ ├── info.cpp │ └── Makefile ├── 11-parameters │ ├── config.json │ ├── Makefile │ └── app_main.cpp ├── 01-first-module │ ├── app_main.cpp │ └── Makefile ├── 00-first-demo │ └── Makefile ├── 02-add-log-tag │ ├── Makefile │ └── app_main.cpp ├── 06-log-print │ ├── Makefile │ └── app_main.cpp ├── 07-timer-event │ ├── Makefile │ └── app_main.cpp ├── 08-fd-event │ ├── Makefile │ └── app_main.cpp ├── 13-timer-pool │ ├── Makefile │ └── app_main.cpp ├── 14-timer-fd │ ├── Makefile │ └── app_main.cpp ├── 17-work-thread │ ├── Makefile │ └── app_main.cpp ├── 18-run-in-loop │ ├── Makefile │ └── app_main.cpp ├── 03-add-app-info │ ├── Makefile │ └── app_main.cpp ├── 09-signal-event │ ├── Makefile │ └── app_main.cpp ├── 15-thread-pool-simple │ ├── Makefile │ └── app_main.cpp ├── 16-thread-pool-advance │ ├── Makefile │ └── app_main.cpp ├── 12-terminal │ ├── Makefile │ └── app_main.cpp ├── 10-tiny-http-server │ ├── Makefile │ └── app_main.cpp └── 19-traffic-light-sm │ ├── Makefile │ └── app_main.cpp ├── images ├── 010-tbox.png ├── 014-timer.png ├── 028-help.png ├── 042-rpc.png ├── 005-no-desc.png ├── 029-with-pn.png ├── 031-backlog.png ├── 000-first-demo.png ├── 006-add-code.png ├── 007-has-desc.png ├── 011-log-print.png ├── 013-log-field.png ├── 030-with-s-pn.png ├── 033-use-config.png ├── 004-compile-warn.png ├── 008-modify-apps.png ├── 009-two-modules.png ├── 015-timer-result.png ├── 016-fdevent-code.png ├── 037-use-terminal.png ├── 046-timer-fd-run.png ├── logo-with-slogan.png ├── 001-first-demo-tips.png ├── 012-log-print-code.png ├── 017-fdevent-result.png ├── 023-tiny-http-code.png ├── 026-parameters-code.png ├── 034-enable-telnetd.png ├── 036-terminal-help.png ├── 041-terminal-args.png ├── 043-terminal-show.png ├── 044-timer-pool-run.png ├── 045-timer-pool-code.png ├── 047-timer-fd-code.png ├── 054-work-thread-run.png ├── 056-run-in-loop-run.png ├── 018-signal-event-code.png ├── 024-tiny-http-result.png ├── 025-tiny-http-result.gif ├── 053-work-thread-code.png ├── 055-run-in-loop-code.png ├── 057-traffic-light-run.png ├── 002-your-first-module-1.png ├── 020-signal-event-result.png ├── 021-enable-http-module.png ├── 022-compile-http-module.png ├── 027-parameter-result-1.png ├── 032-one-set-more-params.png ├── 035-use-nc-login-telnetd.png ├── 038-http-server-terminal.png ├── 058-traffic-light-code-1.png ├── 059-traffic-light-code-2.png ├── 060-traffic-light-code-3.png ├── 061-traffic-light-code-4.png ├── 062-traffic-light-code-5.png ├── 063-traffic-light-code-6.png ├── 039-http-server-terminal-2.png ├── 049-thread-pool-simple-run.png ├── 040-http-server-terminal-code.png ├── 048-thread-pool-simple-code.png ├── 050-thread-pool-advance-code.png ├── 051-thread-pool-advance-run-1.png ├── 052-thread-pool-advance-run-2.png └── 003-your-first-module-with-log.png ├── 00-prepare.md ├── 11-signal-event.md ├── 00-first-demo.md ├── 05-fd-event.md ├── 15-run-in-loop.md ├── 04-timer-event.md ├── 06-http-server.md ├── 12-timer-pool.md ├── 10-multi-modules.md ├── LICENSE ├── 03-event-drive.md ├── 09-add-app-info.md ├── 13-timer-fd.md ├── 02-add-log-tag.md ├── README.md ├── 01-first-module.md ├── 14-thread-pool-and-work-thread.md ├── 16-state-machine.md ├── 07-parameters.md └── 08-terminal.md /17-action-tree.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /50-coffee-machine.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | demo 2 | *.o 3 | -------------------------------------------------------------------------------- /examples/05-two-modules/my/app.mk: -------------------------------------------------------------------------------- 1 | OBJECTS+=my/module.o 2 | -------------------------------------------------------------------------------- /examples/04-normal-app-demo/my/app.mk: -------------------------------------------------------------------------------- 1 | OBJECTS+=my/module.o 2 | -------------------------------------------------------------------------------- /examples/05-two-modules/your/app.mk: -------------------------------------------------------------------------------- 1 | OBJECTS+=your/module.o 2 | -------------------------------------------------------------------------------- /examples/50-coffee-machine/coffee/app.mk: -------------------------------------------------------------------------------- 1 | OBJECTS += coffee/app.o 2 | LIBS += -ltbox_flow 3 | -------------------------------------------------------------------------------- /images/010-tbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/010-tbox.png -------------------------------------------------------------------------------- /images/014-timer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/014-timer.png -------------------------------------------------------------------------------- /images/028-help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/028-help.png -------------------------------------------------------------------------------- /images/042-rpc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/042-rpc.png -------------------------------------------------------------------------------- /images/005-no-desc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/005-no-desc.png -------------------------------------------------------------------------------- /images/029-with-pn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/029-with-pn.png -------------------------------------------------------------------------------- /images/031-backlog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/031-backlog.png -------------------------------------------------------------------------------- /images/000-first-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/000-first-demo.png -------------------------------------------------------------------------------- /images/006-add-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/006-add-code.png -------------------------------------------------------------------------------- /images/007-has-desc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/007-has-desc.png -------------------------------------------------------------------------------- /images/011-log-print.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/011-log-print.png -------------------------------------------------------------------------------- /images/013-log-field.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/013-log-field.png -------------------------------------------------------------------------------- /images/030-with-s-pn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/030-with-s-pn.png -------------------------------------------------------------------------------- /images/033-use-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/033-use-config.png -------------------------------------------------------------------------------- /images/004-compile-warn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/004-compile-warn.png -------------------------------------------------------------------------------- /images/008-modify-apps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/008-modify-apps.png -------------------------------------------------------------------------------- /images/009-two-modules.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/009-two-modules.png -------------------------------------------------------------------------------- /images/015-timer-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/015-timer-result.png -------------------------------------------------------------------------------- /images/016-fdevent-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/016-fdevent-code.png -------------------------------------------------------------------------------- /images/037-use-terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/037-use-terminal.png -------------------------------------------------------------------------------- /images/046-timer-fd-run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/046-timer-fd-run.png -------------------------------------------------------------------------------- /images/logo-with-slogan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/logo-with-slogan.png -------------------------------------------------------------------------------- /images/001-first-demo-tips.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/001-first-demo-tips.png -------------------------------------------------------------------------------- /images/012-log-print-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/012-log-print-code.png -------------------------------------------------------------------------------- /images/017-fdevent-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/017-fdevent-result.png -------------------------------------------------------------------------------- /images/023-tiny-http-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/023-tiny-http-code.png -------------------------------------------------------------------------------- /images/026-parameters-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/026-parameters-code.png -------------------------------------------------------------------------------- /images/034-enable-telnetd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/034-enable-telnetd.png -------------------------------------------------------------------------------- /images/036-terminal-help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/036-terminal-help.png -------------------------------------------------------------------------------- /images/041-terminal-args.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/041-terminal-args.png -------------------------------------------------------------------------------- /images/043-terminal-show.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/043-terminal-show.png -------------------------------------------------------------------------------- /images/044-timer-pool-run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/044-timer-pool-run.png -------------------------------------------------------------------------------- /images/045-timer-pool-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/045-timer-pool-code.png -------------------------------------------------------------------------------- /images/047-timer-fd-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/047-timer-fd-code.png -------------------------------------------------------------------------------- /images/054-work-thread-run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/054-work-thread-run.png -------------------------------------------------------------------------------- /images/056-run-in-loop-run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/056-run-in-loop-run.png -------------------------------------------------------------------------------- /images/018-signal-event-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/018-signal-event-code.png -------------------------------------------------------------------------------- /images/024-tiny-http-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/024-tiny-http-result.png -------------------------------------------------------------------------------- /images/025-tiny-http-result.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/025-tiny-http-result.gif -------------------------------------------------------------------------------- /images/053-work-thread-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/053-work-thread-code.png -------------------------------------------------------------------------------- /images/055-run-in-loop-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/055-run-in-loop-code.png -------------------------------------------------------------------------------- /images/057-traffic-light-run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/057-traffic-light-run.png -------------------------------------------------------------------------------- /examples/11-parameters/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "my_http":{ 3 | "bind_addr":"0.0.0.0:22222", 4 | "backlog":99 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /images/002-your-first-module-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/002-your-first-module-1.png -------------------------------------------------------------------------------- /images/020-signal-event-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/020-signal-event-result.png -------------------------------------------------------------------------------- /images/021-enable-http-module.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/021-enable-http-module.png -------------------------------------------------------------------------------- /images/022-compile-http-module.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/022-compile-http-module.png -------------------------------------------------------------------------------- /images/027-parameter-result-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/027-parameter-result-1.png -------------------------------------------------------------------------------- /images/032-one-set-more-params.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/032-one-set-more-params.png -------------------------------------------------------------------------------- /images/035-use-nc-login-telnetd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/035-use-nc-login-telnetd.png -------------------------------------------------------------------------------- /images/038-http-server-terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/038-http-server-terminal.png -------------------------------------------------------------------------------- /images/058-traffic-light-code-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/058-traffic-light-code-1.png -------------------------------------------------------------------------------- /images/059-traffic-light-code-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/059-traffic-light-code-2.png -------------------------------------------------------------------------------- /images/060-traffic-light-code-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/060-traffic-light-code-3.png -------------------------------------------------------------------------------- /images/061-traffic-light-code-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/061-traffic-light-code-4.png -------------------------------------------------------------------------------- /images/062-traffic-light-code-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/062-traffic-light-code-5.png -------------------------------------------------------------------------------- /images/063-traffic-light-code-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/063-traffic-light-code-6.png -------------------------------------------------------------------------------- /images/039-http-server-terminal-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/039-http-server-terminal-2.png -------------------------------------------------------------------------------- /images/049-thread-pool-simple-run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/049-thread-pool-simple-run.png -------------------------------------------------------------------------------- /images/040-http-server-terminal-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/040-http-server-terminal-code.png -------------------------------------------------------------------------------- /images/048-thread-pool-simple-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/048-thread-pool-simple-code.png -------------------------------------------------------------------------------- /images/050-thread-pool-advance-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/050-thread-pool-advance-code.png -------------------------------------------------------------------------------- /images/051-thread-pool-advance-run-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/051-thread-pool-advance-run-1.png -------------------------------------------------------------------------------- /images/052-thread-pool-advance-run-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/052-thread-pool-advance-run-2.png -------------------------------------------------------------------------------- /examples/50-coffee-machine/coffee-machine: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/examples/50-coffee-machine/coffee-machine -------------------------------------------------------------------------------- /images/003-your-first-module-with-log.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-main/cpp-tbox-tutorials/HEAD/images/003-your-first-module-with-log.png -------------------------------------------------------------------------------- /examples/05-two-modules/build.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace tbox { 4 | namespace main { 5 | 6 | std::string GetAppBuildTime() { 7 | return std::string(__DATE__) + " " + __TIME__; 8 | } 9 | 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/04-normal-app-demo/build.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace tbox { 4 | namespace main { 5 | 6 | std::string GetAppBuildTime() { 7 | return std::string(__DATE__) + " " + __TIME__; 8 | } 9 | 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/50-coffee-machine/build.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace tbox { 4 | namespace main { 5 | 6 | std::string GetAppBuildTime() { 7 | return std::string(__DATE__) + " " + __TIME__; 8 | } 9 | 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/04-normal-app-demo/apps.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "my/module.h" 5 | 6 | namespace tbox { 7 | namespace main { 8 | void RegisterApps(Module &apps, Context &ctx) { 9 | apps.add(new my::Module(ctx)); 10 | } 11 | 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/50-coffee-machine/apps.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "coffee/app.h" 5 | 6 | namespace tbox { 7 | namespace main { 8 | 9 | void RegisterApps(Module &apps, Context &ctx) { 10 | apps.add(new coffee::App(ctx)); 11 | } 12 | 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /00-prepare.md: -------------------------------------------------------------------------------- 1 | # 准备工作 2 | 3 | 下载与构建 cpp-tbox: 4 | ``` 5 | git clone https://gitee.com/cpp-master/cpp-tbox.git 6 | cd cpp-tbox; 7 | make 3rd-party modules RELEASE=1 STAGING_DIR=$HOME/.tbox 8 | ``` 9 | 完成之后,cpp-tbox 库产生的头文件在 ~/.tbox/include 路径下,库文件在 ~/.tbox/lib 路径下。 10 | ![导出的文件](images/010-tbox.png) 11 | 12 | ------- 13 | [[返回主页]](README.md) 14 | -------------------------------------------------------------------------------- /examples/05-two-modules/apps.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "my/module.h" 5 | #include "your/module.h" 6 | 7 | namespace tbox { 8 | namespace main { 9 | void RegisterApps(Module &apps, Context &ctx) { 10 | apps.add(new my::Module(ctx)); 11 | apps.add(new your::Module(ctx)); 12 | } 13 | 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/01-first-module/app_main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | class MyModule : public tbox::main::Module { 4 | public: 5 | explicit MyModule(tbox::main::Context &ctx) : tbox::main::Module("my", ctx) {} 6 | }; 7 | 8 | namespace tbox { 9 | namespace main { 10 | void RegisterApps(Module &apps, Context &ctx) { 11 | apps.add(new MyModule(ctx)); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/05-two-modules/info.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace tbox { 4 | namespace main { 5 | 6 | std::string GetAppDescribe() { 7 | return "This is CppTbox Demo, writen by Hevake Lee."; 8 | } 9 | 10 | void GetAppVersion(int &major, int &minor, int &rev, int &build) { 11 | major = 1; 12 | minor = 2; 13 | rev = 3; 14 | build = 4; 15 | } 16 | 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/04-normal-app-demo/info.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace tbox { 4 | namespace main { 5 | 6 | std::string GetAppDescribe() { 7 | return "This is CppTbox Demo, writen by Hevake Lee."; 8 | } 9 | 10 | void GetAppVersion(int &major, int &minor, int &rev, int &build) { 11 | major = 1; 12 | minor = 2; 13 | rev = 3; 14 | build = 4; 15 | } 16 | 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/50-coffee-machine/info.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace tbox { 4 | namespace main { 5 | 6 | std::string GetAppDescribe() { 7 | return "This is CppTbox Demo, writen by Hevake Lee."; 8 | } 9 | 10 | void GetAppVersion(int &major, int &minor, int &rev, int &build) { 11 | major = 1; 12 | minor = 2; 13 | rev = 3; 14 | build = 4; 15 | } 16 | 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/00-first-demo/Makefile: -------------------------------------------------------------------------------- 1 | TARGET:=demo 2 | 3 | CXXFLAGS:=-I$(HOME)/.tbox/include 4 | LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic 5 | LIBS:=\ 6 | -ltbox_main \ 7 | -ltbox_coroutine \ 8 | -ltbox_trace \ 9 | -ltbox_terminal \ 10 | -ltbox_network \ 11 | -ltbox_eventx \ 12 | -ltbox_event \ 13 | -ltbox_log \ 14 | -ltbox_util \ 15 | -ltbox_base \ 16 | -lpthread -ldl 17 | 18 | $(TARGET): 19 | g++ -o $(TARGET) $(LDFLAGS) $(LIBS) 20 | -------------------------------------------------------------------------------- /examples/05-two-modules/my/module.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace my { 4 | 5 | class Module : public tbox::main::Module { 6 | public: 7 | explicit Module(tbox::main::Context &ctx); 8 | virtual ~Module(); 9 | 10 | public: 11 | virtual bool onInit(const tbox::Json &js) override; 12 | virtual bool onStart() override; 13 | virtual void onStop() override; 14 | virtual void onCleanup() override; 15 | }; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /examples/04-normal-app-demo/my/module.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace my { 4 | 5 | class Module : public tbox::main::Module { 6 | public: 7 | explicit Module(tbox::main::Context &ctx); 8 | virtual ~Module(); 9 | 10 | public: 11 | virtual bool onInit(const tbox::Json &js) override; 12 | virtual bool onStart() override; 13 | virtual void onStop() override; 14 | virtual void onCleanup() override; 15 | }; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /examples/05-two-modules/your/module.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace your { 4 | 5 | class Module : public tbox::main::Module { 6 | public: 7 | explicit Module(tbox::main::Context &ctx); 8 | virtual ~Module(); 9 | 10 | public: 11 | virtual bool onInit(const tbox::Json &js) override; 12 | virtual bool onStart() override; 13 | virtual void onStop() override; 14 | virtual void onCleanup() override; 15 | }; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /examples/01-first-module/Makefile: -------------------------------------------------------------------------------- 1 | TARGET:=demo 2 | 3 | OBJECTS:=app_main.o 4 | 5 | CXXFLAGS:=-I$(HOME)/.tbox/include -std=c++11 6 | LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic 7 | LIBS:=\ 8 | -ltbox_main \ 9 | -ltbox_coroutine \ 10 | -ltbox_trace \ 11 | -ltbox_terminal \ 12 | -ltbox_network \ 13 | -ltbox_eventx \ 14 | -ltbox_event \ 15 | -ltbox_log \ 16 | -ltbox_util \ 17 | -ltbox_base \ 18 | -lpthread -ldl 19 | 20 | $(TARGET): $(OBJECTS) 21 | g++ -o $@ $^ $(LDFLAGS) $(LIBS) 22 | -------------------------------------------------------------------------------- /11-signal-event.md: -------------------------------------------------------------------------------- 1 | # 信号事件 2 | 3 | 写了Linux程序的同学都知道,当我们在终端按下ctrl+c的时候,系统会向进程序发送一个SIGINT的信号。如果我们在程序中`signal(SIGINT, xxxx)`注册了信号处理函数,那么该函数就会被调用。这种直接调`signal()`函数注册信号回调的方法其实是很不安全的。具体原因,网上有很多文章进行过阐述。在此就不在赘述。 4 | 为了在tbox中能安全地使用信号,我为tbox中实现了另一种事件SignalEvent。使用它在tbox.main框架中注册信号回调是安全的。 5 | 6 | 接下来,为了演示信号的使用,我们在定时器示例的基础上进行修改。当程序接收到信号USR1时停止定时器,当接收到信号USR2时启用定时器。 7 | 代码如下: 8 | ![代码](images/018-signal-event-code.png) 9 | 10 | 编译执行效果: 11 | ![执行效果](images/020-signal-event-result.png) 12 | 13 | ------- 14 | [[返回主页]](README.md) 15 | -------------------------------------------------------------------------------- /examples/02-add-log-tag/Makefile: -------------------------------------------------------------------------------- 1 | TARGET:=demo 2 | 3 | OBJECTS:=app_main.o 4 | 5 | CXXFLAGS:=-I$(HOME)/.tbox/include -DMODULE_ID='"demo"' -std=c++11 6 | LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic 7 | LIBS:=\ 8 | -ltbox_main \ 9 | -ltbox_coroutine \ 10 | -ltbox_trace \ 11 | -ltbox_terminal \ 12 | -ltbox_network \ 13 | -ltbox_eventx \ 14 | -ltbox_event \ 15 | -ltbox_log \ 16 | -ltbox_util \ 17 | -ltbox_base \ 18 | -lpthread -ldl 19 | 20 | $(TARGET): $(OBJECTS) 21 | g++ -o $@ $^ $(LDFLAGS) $(LIBS) 22 | 23 | clean: 24 | rm *.o 25 | -------------------------------------------------------------------------------- /examples/06-log-print/Makefile: -------------------------------------------------------------------------------- 1 | TARGET:=demo 2 | 3 | OBJECTS:=app_main.o 4 | 5 | CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' -std=c++11 6 | LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic 7 | LIBS:=\ 8 | -ltbox_main \ 9 | -ltbox_coroutine \ 10 | -ltbox_trace \ 11 | -ltbox_terminal \ 12 | -ltbox_network \ 13 | -ltbox_eventx \ 14 | -ltbox_event \ 15 | -ltbox_log \ 16 | -ltbox_util \ 17 | -ltbox_base \ 18 | -lpthread -ldl 19 | 20 | $(TARGET): $(OBJECTS) 21 | g++ -o $@ $^ $(LDFLAGS) $(LIBS) 22 | 23 | clean: 24 | rm *.o 25 | -------------------------------------------------------------------------------- /examples/07-timer-event/Makefile: -------------------------------------------------------------------------------- 1 | TARGET:=demo 2 | 3 | OBJECTS:=app_main.o 4 | 5 | CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' -std=c++11 6 | LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic 7 | LIBS:=\ 8 | -ltbox_main \ 9 | -ltbox_coroutine \ 10 | -ltbox_trace \ 11 | -ltbox_terminal \ 12 | -ltbox_network \ 13 | -ltbox_eventx \ 14 | -ltbox_event \ 15 | -ltbox_log \ 16 | -ltbox_util \ 17 | -ltbox_base \ 18 | -lpthread -ldl 19 | 20 | $(TARGET): $(OBJECTS) 21 | g++ -o $@ $^ $(LDFLAGS) $(LIBS) 22 | 23 | clean: 24 | rm *.o 25 | -------------------------------------------------------------------------------- /examples/08-fd-event/Makefile: -------------------------------------------------------------------------------- 1 | TARGET:=demo 2 | 3 | OBJECTS:=app_main.o 4 | 5 | CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' -std=c++11 6 | LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic 7 | LIBS:=\ 8 | -ltbox_main \ 9 | -ltbox_coroutine \ 10 | -ltbox_trace \ 11 | -ltbox_terminal \ 12 | -ltbox_network \ 13 | -ltbox_eventx \ 14 | -ltbox_event \ 15 | -ltbox_log \ 16 | -ltbox_util \ 17 | -ltbox_base \ 18 | -lpthread -ldl 19 | 20 | $(TARGET): $(OBJECTS) 21 | g++ -o $@ $^ $(LDFLAGS) $(LIBS) 22 | 23 | clean: 24 | rm *.o 25 | -------------------------------------------------------------------------------- /examples/13-timer-pool/Makefile: -------------------------------------------------------------------------------- 1 | TARGET:=demo 2 | 3 | OBJECTS:=app_main.o 4 | 5 | CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' -std=c++11 6 | LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic 7 | LIBS:=\ 8 | -ltbox_main \ 9 | -ltbox_coroutine \ 10 | -ltbox_trace \ 11 | -ltbox_terminal \ 12 | -ltbox_network \ 13 | -ltbox_eventx \ 14 | -ltbox_event \ 15 | -ltbox_log \ 16 | -ltbox_util \ 17 | -ltbox_base \ 18 | -lpthread -ldl 19 | 20 | $(TARGET): $(OBJECTS) 21 | g++ -o $@ $^ $(LDFLAGS) $(LIBS) 22 | 23 | clean: 24 | rm *.o 25 | -------------------------------------------------------------------------------- /examples/14-timer-fd/Makefile: -------------------------------------------------------------------------------- 1 | TARGET:=demo 2 | 3 | OBJECTS:=app_main.o 4 | 5 | CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' -std=c++11 6 | LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic 7 | LIBS:=\ 8 | -ltbox_main \ 9 | -ltbox_coroutine \ 10 | -ltbox_trace \ 11 | -ltbox_terminal \ 12 | -ltbox_network \ 13 | -ltbox_eventx \ 14 | -ltbox_event \ 15 | -ltbox_log \ 16 | -ltbox_util \ 17 | -ltbox_base \ 18 | -lpthread -ldl 19 | 20 | $(TARGET): $(OBJECTS) 21 | g++ -o $@ $^ $(LDFLAGS) $(LIBS) 22 | 23 | clean: 24 | rm *.o 25 | -------------------------------------------------------------------------------- /examples/17-work-thread/Makefile: -------------------------------------------------------------------------------- 1 | TARGET:=demo 2 | 3 | OBJECTS:=app_main.o 4 | 5 | CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' -std=c++11 6 | LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic 7 | LIBS:=\ 8 | -ltbox_main \ 9 | -ltbox_coroutine \ 10 | -ltbox_trace \ 11 | -ltbox_terminal \ 12 | -ltbox_network \ 13 | -ltbox_eventx \ 14 | -ltbox_event \ 15 | -ltbox_log \ 16 | -ltbox_util \ 17 | -ltbox_base \ 18 | -lpthread -ldl 19 | 20 | $(TARGET): $(OBJECTS) 21 | g++ -o $@ $^ $(LDFLAGS) $(LIBS) 22 | 23 | clean: 24 | rm *.o 25 | -------------------------------------------------------------------------------- /examples/18-run-in-loop/Makefile: -------------------------------------------------------------------------------- 1 | TARGET:=demo 2 | 3 | OBJECTS:=app_main.o 4 | 5 | CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' -std=c++11 6 | LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic 7 | LIBS:=\ 8 | -ltbox_main \ 9 | -ltbox_coroutine \ 10 | -ltbox_trace \ 11 | -ltbox_terminal \ 12 | -ltbox_network \ 13 | -ltbox_eventx \ 14 | -ltbox_event \ 15 | -ltbox_log \ 16 | -ltbox_util \ 17 | -ltbox_base \ 18 | -lpthread -ldl 19 | 20 | $(TARGET): $(OBJECTS) 21 | g++ -o $@ $^ $(LDFLAGS) $(LIBS) 22 | 23 | clean: 24 | rm *.o 25 | -------------------------------------------------------------------------------- /examples/03-add-app-info/Makefile: -------------------------------------------------------------------------------- 1 | TARGET:=demo 2 | 3 | OBJECTS:=app_main.o 4 | 5 | CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' -std=c++11 6 | LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic 7 | LIBS:=\ 8 | -ltbox_main \ 9 | -ltbox_coroutine \ 10 | -ltbox_trace \ 11 | -ltbox_terminal \ 12 | -ltbox_network \ 13 | -ltbox_eventx \ 14 | -ltbox_event \ 15 | -ltbox_log \ 16 | -ltbox_util \ 17 | -ltbox_base \ 18 | -lpthread -ldl 19 | 20 | $(TARGET): $(OBJECTS) 21 | g++ -o $@ $^ $(LDFLAGS) $(LIBS) 22 | 23 | clean: 24 | rm *.o 25 | -------------------------------------------------------------------------------- /examples/09-signal-event/Makefile: -------------------------------------------------------------------------------- 1 | TARGET:=demo 2 | 3 | OBJECTS:=app_main.o 4 | 5 | CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' -std=c++11 6 | LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic 7 | LIBS:=\ 8 | -ltbox_main \ 9 | -ltbox_coroutine \ 10 | -ltbox_trace \ 11 | -ltbox_terminal \ 12 | -ltbox_network \ 13 | -ltbox_eventx \ 14 | -ltbox_event \ 15 | -ltbox_log \ 16 | -ltbox_util \ 17 | -ltbox_base \ 18 | -lpthread -ldl 19 | 20 | $(TARGET): $(OBJECTS) 21 | g++ -o $@ $^ $(LDFLAGS) $(LIBS) 22 | 23 | clean: 24 | rm *.o 25 | -------------------------------------------------------------------------------- /examples/05-two-modules/my/module.cpp: -------------------------------------------------------------------------------- 1 | #include "module.h" 2 | #include 3 | 4 | namespace my { 5 | 6 | Module::Module(tbox::main::Context &ctx) 7 | : tbox::main::Module("my", ctx) 8 | { } 9 | 10 | Module::~Module() { } 11 | 12 | bool Module::onInit(const tbox::Json &js) { 13 | LogTag(); 14 | return true; 15 | } 16 | 17 | bool Module::onStart() { 18 | LogTag(); 19 | return true; 20 | } 21 | 22 | void Module::onStop() { 23 | LogTag(); 24 | } 25 | 26 | void Module::onCleanup() { 27 | LogTag(); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /examples/15-thread-pool-simple/Makefile: -------------------------------------------------------------------------------- 1 | TARGET:=demo 2 | 3 | OBJECTS:=app_main.o 4 | 5 | CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' -std=c++11 6 | LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic 7 | LIBS:=\ 8 | -ltbox_main \ 9 | -ltbox_coroutine \ 10 | -ltbox_trace \ 11 | -ltbox_terminal \ 12 | -ltbox_network \ 13 | -ltbox_eventx \ 14 | -ltbox_event \ 15 | -ltbox_log \ 16 | -ltbox_util \ 17 | -ltbox_base \ 18 | -lpthread -ldl 19 | 20 | $(TARGET): $(OBJECTS) 21 | g++ -o $@ $^ $(LDFLAGS) $(LIBS) 22 | 23 | clean: 24 | rm *.o 25 | -------------------------------------------------------------------------------- /examples/16-thread-pool-advance/Makefile: -------------------------------------------------------------------------------- 1 | TARGET:=demo 2 | 3 | OBJECTS:=app_main.o 4 | 5 | CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' -std=c++11 6 | LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic 7 | LIBS:=\ 8 | -ltbox_main \ 9 | -ltbox_coroutine \ 10 | -ltbox_trace \ 11 | -ltbox_terminal \ 12 | -ltbox_network \ 13 | -ltbox_eventx \ 14 | -ltbox_event \ 15 | -ltbox_log \ 16 | -ltbox_util \ 17 | -ltbox_base \ 18 | -lpthread -ldl 19 | 20 | $(TARGET): $(OBJECTS) 21 | g++ -o $@ $^ $(LDFLAGS) $(LIBS) 22 | 23 | clean: 24 | rm *.o 25 | -------------------------------------------------------------------------------- /examples/04-normal-app-demo/my/module.cpp: -------------------------------------------------------------------------------- 1 | #include "module.h" 2 | #include 3 | 4 | namespace my { 5 | 6 | Module::Module(tbox::main::Context &ctx) 7 | : tbox::main::Module("my", ctx) 8 | { } 9 | 10 | Module::~Module() { } 11 | 12 | bool Module::onInit(const tbox::Json &js) { 13 | LogTag(); 14 | return true; 15 | } 16 | 17 | bool Module::onStart() { 18 | LogTag(); 19 | return true; 20 | } 21 | 22 | void Module::onStop() { 23 | LogTag(); 24 | } 25 | 26 | void Module::onCleanup() { 27 | LogTag(); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /examples/05-two-modules/your/module.cpp: -------------------------------------------------------------------------------- 1 | #include "module.h" 2 | #include 3 | 4 | namespace your { 5 | 6 | Module::Module(tbox::main::Context &ctx) 7 | : tbox::main::Module("your", ctx) 8 | { } 9 | 10 | Module::~Module() { } 11 | 12 | bool Module::onInit(const tbox::Json &js) { 13 | LogTag(); 14 | return true; 15 | } 16 | 17 | bool Module::onStart() { 18 | LogTag(); 19 | return true; 20 | } 21 | 22 | void Module::onStop() { 23 | LogTag(); 24 | } 25 | 26 | void Module::onCleanup() { 27 | LogTag(); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /examples/11-parameters/Makefile: -------------------------------------------------------------------------------- 1 | TARGET:=demo 2 | 3 | OBJECTS:=app_main.o 4 | 5 | CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' -std=c++11 6 | LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic 7 | LIBS:=\ 8 | -ltbox_http \ 9 | -ltbox_main \ 10 | -ltbox_coroutine \ 11 | -ltbox_trace \ 12 | -ltbox_terminal \ 13 | -ltbox_network \ 14 | -ltbox_eventx \ 15 | -ltbox_event \ 16 | -ltbox_log \ 17 | -ltbox_util \ 18 | -ltbox_base \ 19 | -lpthread -ldl 20 | 21 | $(TARGET): $(OBJECTS) 22 | g++ -o $@ $^ $(LDFLAGS) $(LIBS) 23 | 24 | clean: 25 | rm *.o 26 | -------------------------------------------------------------------------------- /examples/12-terminal/Makefile: -------------------------------------------------------------------------------- 1 | TARGET:=demo 2 | 3 | OBJECTS:=app_main.o 4 | 5 | CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' -std=c++11 6 | LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic 7 | LIBS:=\ 8 | -ltbox_http \ 9 | -ltbox_main \ 10 | -ltbox_coroutine \ 11 | -ltbox_trace \ 12 | -ltbox_terminal \ 13 | -ltbox_network \ 14 | -ltbox_eventx \ 15 | -ltbox_event \ 16 | -ltbox_log \ 17 | -ltbox_util \ 18 | -ltbox_base \ 19 | -lpthread -ldl 20 | 21 | $(TARGET): $(OBJECTS) 22 | g++ -o $@ $^ $(LDFLAGS) $(LIBS) 23 | 24 | clean: 25 | rm *.o 26 | -------------------------------------------------------------------------------- /examples/10-tiny-http-server/Makefile: -------------------------------------------------------------------------------- 1 | TARGET:=demo 2 | 3 | OBJECTS:=app_main.o 4 | 5 | CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' -std=c++11 6 | LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic 7 | LIBS:=\ 8 | -ltbox_http \ 9 | -ltbox_main \ 10 | -ltbox_coroutine \ 11 | -ltbox_trace \ 12 | -ltbox_terminal \ 13 | -ltbox_network \ 14 | -ltbox_eventx \ 15 | -ltbox_event \ 16 | -ltbox_log \ 17 | -ltbox_util \ 18 | -ltbox_base \ 19 | -lpthread -ldl 20 | 21 | $(TARGET): $(OBJECTS) 22 | g++ -o $@ $^ $(LDFLAGS) $(LIBS) 23 | 24 | clean: 25 | rm *.o 26 | -------------------------------------------------------------------------------- /examples/19-traffic-light-sm/Makefile: -------------------------------------------------------------------------------- 1 | TARGET:=demo 2 | 3 | OBJECTS:=app_main.o 4 | 5 | CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' -std=c++11 6 | LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic 7 | LIBS:=\ 8 | -ltbox_flow \ 9 | -ltbox_main \ 10 | -ltbox_coroutine \ 11 | -ltbox_trace \ 12 | -ltbox_terminal \ 13 | -ltbox_network \ 14 | -ltbox_eventx \ 15 | -ltbox_event \ 16 | -ltbox_log \ 17 | -ltbox_util \ 18 | -ltbox_base \ 19 | -lpthread -ldl 20 | 21 | $(TARGET): $(OBJECTS) 22 | g++ -o $@ $^ $(LDFLAGS) $(LIBS) 23 | 24 | clean: 25 | rm *.o 26 | -------------------------------------------------------------------------------- /examples/02-add-log-tag/app_main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | class MyModule : public tbox::main::Module { 5 | public: 6 | explicit MyModule(tbox::main::Context &ctx) : tbox::main::Module("my", ctx) { } 7 | virtual ~MyModule() { } 8 | 9 | public: 10 | virtual bool onInit(const tbox::Json &js) override { LogTag(); return true; } 11 | virtual bool onStart() override { LogTag(); return true; } 12 | virtual void onStop() override { LogTag(); } 13 | virtual void onCleanup() override { LogTag(); } 14 | }; 15 | 16 | namespace tbox { 17 | namespace main { 18 | void RegisterApps(Module &apps, Context &ctx) { 19 | apps.add(new MyModule(ctx)); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/04-normal-app-demo/Makefile: -------------------------------------------------------------------------------- 1 | TARGET:=demo 2 | 3 | OBJECTS:= \ 4 | apps.o \ 5 | build.o \ 6 | info.o 7 | 8 | CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' -std=c++11 9 | LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic 10 | LIBS:=\ 11 | -ltbox_main \ 12 | -ltbox_coroutine \ 13 | -ltbox_trace \ 14 | -ltbox_terminal \ 15 | -ltbox_network \ 16 | -ltbox_eventx \ 17 | -ltbox_event \ 18 | -ltbox_log \ 19 | -ltbox_util \ 20 | -ltbox_base \ 21 | -lpthread -ldl 22 | 23 | include my/app.mk 24 | 25 | .PHONY: all clean distclean build.cpp 26 | 27 | all: $(TARGET) 28 | 29 | $(TARGET): $(OBJECTS) 30 | g++ -o $@ $^ $(LDFLAGS) $(LIBS) 31 | 32 | clean: 33 | rm -f $(OBJECTS) 34 | 35 | distclean: clean 36 | rm -f $(TARGET) 37 | -------------------------------------------------------------------------------- /00-first-demo.md: -------------------------------------------------------------------------------- 1 | # 第一个程序 2 | 3 | 创建自己的工程目录 00-first-demo,再在该目录下创建 Makefile 文件,内容如下: 4 | ```Makefile 5 | TARGET:=demo 6 | 7 | CXXFLAGS:=-I$(HOME)/.tbox/include 8 | LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic 9 | LIBS:=-ltbox_main -ltbox_coroutine -ltbox_trace -ltbox_terminal -ltbox_network -ltbox_eventx -ltbox_event -ltbox_log -ltbox_util -ltbox_base -lpthread -ldl 10 | 11 | $(TARGET): 12 | g++ -o $(TARGET) $(LDFLAGS) $(LIBS) 13 | ``` 14 | 15 | [示例工程目录](examples/00-first-demo) 16 | 17 | 然后执行 `make && ./demo`,效果: 18 | ![执行效果](images/000-first-demo.png) 19 | 20 | 看到上面的显示,说明 ./demo 已经正常运行了。按 ctrl+c 可以正常退出程序。 21 | 我们可以看到,我们的第一个程序就这么运行起来了。虽然我们什么代码都没有写,但它 tbox.main 框架自身是可以运行的。就像是一个只有火车头,没有车厢的火车一样。 22 | 23 | ------- 24 | [[返回主页]](README.md) 25 | -------------------------------------------------------------------------------- /examples/05-two-modules/Makefile: -------------------------------------------------------------------------------- 1 | TARGET:=demo 2 | 3 | OBJECTS:= \ 4 | apps.o \ 5 | build.o \ 6 | info.o 7 | 8 | CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' -std=c++11 9 | LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic 10 | LIBS:=\ 11 | -ltbox_main \ 12 | -ltbox_coroutine \ 13 | -ltbox_trace \ 14 | -ltbox_terminal \ 15 | -ltbox_network \ 16 | -ltbox_eventx \ 17 | -ltbox_event \ 18 | -ltbox_log \ 19 | -ltbox_util \ 20 | -ltbox_base \ 21 | -lpthread -ldl 22 | 23 | include my/app.mk 24 | include your/app.mk 25 | 26 | .PHONY: all clean distclean build.cpp 27 | 28 | all: $(TARGET) 29 | 30 | $(TARGET): $(OBJECTS) 31 | g++ -o $@ $^ $(LDFLAGS) $(LIBS) 32 | 33 | clean: 34 | rm -f $(OBJECTS) 35 | 36 | distclean: clean 37 | rm -f $(TARGET) 38 | -------------------------------------------------------------------------------- /examples/50-coffee-machine/Makefile: -------------------------------------------------------------------------------- 1 | TARGET:=coffee-machine 2 | 3 | OBJECTS:= \ 4 | apps.o \ 5 | build.o \ 6 | info.o 7 | 8 | CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"coffee"' -std=c++11 -ggdb 9 | LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic 10 | 11 | include coffee/app.mk 12 | 13 | LIBS +=\ 14 | -ltbox_main \ 15 | -ltbox_coroutine \ 16 | -ltbox_trace \ 17 | -ltbox_terminal \ 18 | -ltbox_network \ 19 | -ltbox_eventx \ 20 | -ltbox_event \ 21 | -ltbox_log \ 22 | -ltbox_util \ 23 | -ltbox_base \ 24 | -lpthread -ldl 25 | 26 | .PHONY: all clean distclean build.cpp 27 | 28 | all: $(TARGET) 29 | 30 | $(TARGET): $(OBJECTS) 31 | g++ -o $@ $^ $(LDFLAGS) $(LIBS) 32 | 33 | clean: 34 | rm -f $(OBJECTS) 35 | 36 | distclean: clean 37 | rm -f $(TARGET) 38 | -------------------------------------------------------------------------------- /05-fd-event.md: -------------------------------------------------------------------------------- 1 | # IO事件 2 | 3 | 在基于事件的开发中,除了定期触发的事件,更多的是当外部发生了某件事件从而引发的事件。这些事件通常是通过文件描述符的可读可写时事件触发的。 4 | 在 tbox 中,除了 TimerEvent,还有更重要的一种事件:FdEvent。它用于监听某个文件描述符的可读可写事件。 5 | 6 | 下面,我们对上面的定时器时示例添加新的功能:当我们在终端上输入"off"时关闭定时器,当我们输入"on"打开定时器。 7 | 为了实现这个功能,首先我们要实现从终端获取输入的数据。 8 | 问:使用 `std::cin` 可以吗?这可是传统的方式。答:不可以,因为它是阻塞的。还记得我们前面提过 **不要阻塞** 的要求吗?所以不行。 9 | 问:那开一个线程专门负责读终端的终入,将去控制定时器行不行呢?答:这也不是不行,只是这样就需要额外开一个线程了。 10 | 这里,我们采用另一种更优的方案:创建一个stdin的文件描述符的`FdEvent`,令其监听stdin的可读事件。当stdin可读时,就调用`read(0,xx,yy)`将数据读出来,再处理。这样就不会阻塞了。 11 | 如下更改: 12 | ![代码](images/016-fdevent-code.png) 13 | 在(9)处定义了接收到stdin输入的处理过程。首先,检查一下是不是可读事件,如果是才处理。然后读0文件描述符,即stdin。将数据读到`buff`缓冲中。最后,根据输入的内容进行处理。 14 | 15 | [示例工程目录](examples/08-fd-event) 16 | 17 | 编译执行效果: 18 | ![结果](images/017-fdevent-result.png) 19 | 20 | ------- 21 | [[返回主页]](README.md) 22 | -------------------------------------------------------------------------------- /15-run-in-loop.md: -------------------------------------------------------------------------------- 1 | # runInLoop 2 | 3 | 前面学会了使用线程池与工作线程,在Loop线程中向子线程委派任务。反过来,子线程也应该可以向Loop线程委派任务。 4 | 5 | 这种场景非常多。比如:我们使用一个第三方的通信库,这个库内部有自己的子线程,在特定的情况下会回调我们预先设置的回调函数。 6 | 以ROS为例,它自己内部会为Topic创建多个消息接收线程。在收到消息的时候,其中的子线程会调用我们注册的回调函数,让我们处理消息。由于这是ROS内部的子线程调用的,我们不能直接让它在不加锁的情况下访问公共的数据,否则会导致多线程访问共同资源竞态,引起不可预知的后果。为了在无锁的情况下进行数据,我们常规的做法是委派给Loop线程来执行后面的操作。 7 | 8 | 本节通过一个简单的示例来引导大家掌握`runInLoop()`的使用方法。 9 | 10 | ![](images/055-run-in-loop-code.png) 11 | 本示例很简单: 12 | - 在`onStart()`中,启动了一个子线程。这个子线程循环调用`ctx().loop()->runInLoop(...)`; 13 | - 分析在L21与L24打印日志。 14 | 15 | [示例源码](examples/18-run-in-loop/app_main.cpp) 16 | [示例工程目录](examples/18-run-in-loop) 17 | 18 | 编译后,执行效果: 19 | ![](images/056-run-in-loop-run.png) 20 | 注意观察日志的打印中的线程号,可以看到"thread index" 是由子线程执行的,而"loop thread"则是由Loop线程的。 21 | 这说明达到我们期望的效果。 22 | 23 | ------- 24 | [[返回主页]](README.md) 25 | -------------------------------------------------------------------------------- /04-timer-event.md: -------------------------------------------------------------------------------- 1 | # 定时器事件 2 | 3 | 在基于事件的编程中,sleep() 或 delay() 这类阻塞性的函数是不能使用的。替而代之的是定时器。 4 | 定时器,是基于事件编程中常见的组件。它可以实现: 5 | 6 | - 每间隔指定时间执行指定的动作; 7 | - 指定时间后执行指定的动作; 8 | 9 | 在 tbox.main 框架中,我们自己的模块可以通过构造通传入的 `ctx` 获取 `event::Loop` 对象。而 `event::Loop` 对象可以创建定时器事件、IO事件、信号事件。 10 | 11 | 接下来,我们实现一个定时器的功能: 12 | ![](images/014-timer.png) 13 | 14 | 实现步骤: 15 | (1) 包含头文件; 16 | (2) 添加成员变量 `tick_timer_` 用于存放定时器对象; 17 | (3) 在构造函数中实例化定时器对象; 18 | (4) 在析构函数中释放定时器对象; 19 | (5) 在 `onInit()` 中对定时器对象进行初始化,定时间隔1秒,持续触发; 20 | (6) 设置定时器的回调函数; 21 | (7) 实现 `onTick()` 函数; 22 | (8) 在 `onStart()` 中启动定时器; 23 | (9) 在 `onStop()` 中停止定时器; 24 | 25 | [示例工程目录](examples/07-timer-event) 26 | 27 | 编译后,执行效果: 28 | ![](images/015-timer-result.png) 29 | 可以看到定时器有定时触发。 30 | 31 | 如果想实现单次定时,将 L17 `initialize()` 的最后一个参数的 `kPersist` 改成 `kOneshot` 即可。 32 | 33 | ------- 34 | [[返回主页]](README.md) 35 | -------------------------------------------------------------------------------- /06-http-server.md: -------------------------------------------------------------------------------- 1 | # HTTP服务 2 | 3 | 上面,我们分别演示了定时器事件`TimerEvent`、文件描述符事件`FdEvent`,以及信号事件`SignalEvent`的使用方式。然而,在真实的开发中,我们会优先使用tbox内部为我们实现的模块来实现我们的功能,比如:`TcpServer`, `TcpClient`, `Uart`, `BufferedFd`。这些模块的本质是在内部封装了对`TimerEvent`,`FdEvent`,`SignalEvent`的操作。 4 | 下面,我们使用`http`模块来实现一个简单的http服务器。 5 | 6 | 由于tbox中的`http`模块不是默认开启的。我们需要通过修改`config.mk`文件打开它,并构建: 7 | ![打开http模块](images/021-enable-http-module.png) 8 | 然后再执行命令:`make 3rd-party modules RELEASE=1 STAGING_DIR=$HOME/.tbox`,进行构建: 9 | ![构建http模块](images/022-compile-http-module.png) 10 | 看到上面的输出,表示构建完成。接下来就可以使用 http 模块了。 11 | 12 | 为了演示,我们写一个非常简单的http服务程序,当我们访问它的时候,显示hello页面。 13 | ![http代码](images/023-tiny-http-code.png) 14 | 15 | [示例程序目录](examples/10-tiny-http-server) 16 | 17 | 编译执行,使用 curl 测试,结果如下: 18 | ![http结果](images/024-tiny-http-result.png) 19 | ![浏览器访问](images/025-tiny-http-result.gif) 20 | 21 | ------- 22 | [[返回主页]](README.md) 23 | -------------------------------------------------------------------------------- /12-timer-pool.md: -------------------------------------------------------------------------------- 1 | # 定时器池 2 | 3 | 在 [定时器](04-timer-event.md) 这一节中,我们学习了TimerEvent的实用,对定时器有了一定的了解。 4 | 5 | 在实际的项目中,我们会发现`TimerEvent`使用起来不是很方便。为了使用它,我们需要调`ctx().loop()->newTimerEvent()`返回一个定时器对象的指针。为保证资源不泄漏,我们需要在当将Module的析构函数中进行销毁。有时我们只是临时需要一个单次定时器而已。这样的生命期操控不方便,很繁琐。 6 | 为此,我们引入了`TimerPool`这个模块。 7 | TimerPool模块帮我们管理定时器的生命期。我们需要使用定时器的时候,调它的`doAfter()`或者`doEvent()`即可,非常方便。 8 | 9 | 在 tbox.main 框架中,直带定时器池。使用时只需要:`ctx().timer_pool()` 就可以得到`TimerPool`对象的指针。 10 | 下面,我将使用一个示例程序进行展示: 11 | 12 | ![](images/045-timer-pool-code.png) 13 | 14 | (1) 在`onStart()` 中,分别启动了两个定时器。一个是用的`doEvery()`,另一个是`doAfter()`; 15 | (2) 在`onStop()` 中,取消了这两个定时器; 16 | 17 | [示例工程目录](examples/13-timer-pool) 18 | 19 | 编译后,执行效果: 20 | ![](images/044-timer-pool-run.png) 21 | 可以看到周期性定时器有每秒触发。在第5秒时,单次定时器也触发了。 22 | 23 | **注意**: 24 | 虽然创建定时器方便了,但一定要注意生命期倒挂风险。 25 | 所谓“生命期倒挂”具体表示为:所创建定时器时传入了一个生命期比较短的对象。在定时器触发时,这个对象的生命期已经提前结束了,进而导致不可预期的程序崩溃问题。 26 | 27 | ------- 28 | [[返回主页]](README.md) 29 | -------------------------------------------------------------------------------- /10-multi-modules.md: -------------------------------------------------------------------------------- 1 | # 多个Module的工程结构 2 | 3 | 之前有提过:tbox.main 就像一个火车头。它可以像第一个示例所展示的那样,不带负载运行,也可以像上面一个示例所展示的那样,带一个负载运行。 4 | 本小节将向你展示的是:它可以同时带多个负载运行。也就是说:一个 tbox.main 框架,可以同时将多个毫不相关的业务Module运行在同一个进程里,就像是一个火车头可同时拉多节车相一样。 5 | 6 | 接下来,我们在上一小节的基础上,再新增一个叫 "your" 的 Module,使之与之前叫 "my" 的 Module 一起运行在是一个进程里。 7 | 步骤: 8 | 1. 将 my 目录复制为 your; 9 | 2. 进入 your,修改 module.cpp 与 module.h 将命名空间由 `my` 改成 `your`; 10 | 3. 打开 your/app.mk,将所有的 `my` 改成 `your`; 11 | 4. 打开 Makefile,在 `include my/app.mk` 下面添加 `include your/app.mk`; 12 | 5. 打开 apps.cpp,将所有`my`相关的都复制一份命名为`your`。 13 | ![对apps.cpp的修改](images/008-modify-apps.png) 14 | 15 | 最后工程文件结构如下: 16 | ``` 17 | ├── apps.cpp 18 | ├── build.cpp 19 | ├── info.cpp 20 | ├── Makefile 21 | ├── my 22 | │ ├── app.mk 23 | │ ├── module.cpp 24 | │ └── module.h 25 | └── your 26 | ├── app.mk 27 | ├── module.cpp 28 | └── module.h 29 | ``` 30 | 31 | [示例工程目录](examples/05-two-modules) 32 | 33 | 构建后运行: 34 | ![多Module在同一进程运行效果](images/009-two-modules.png) 35 | 36 | ------- 37 | [[返回主页]](README.md) 38 | -------------------------------------------------------------------------------- /examples/03-add-app-info/app_main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | class MyModule : public tbox::main::Module { 5 | public: 6 | explicit MyModule(tbox::main::Context &ctx) : tbox::main::Module("my", ctx) { } 7 | virtual ~MyModule() { } 8 | 9 | public: 10 | virtual bool onInit(const tbox::Json &js) override { LogTag(); return true; } 11 | virtual bool onStart() override { LogTag(); return true; } 12 | virtual void onStop() override { LogTag(); } 13 | virtual void onCleanup() override { LogTag(); } 14 | }; 15 | 16 | namespace tbox { 17 | namespace main { 18 | void RegisterApps(Module &apps, Context &ctx) { 19 | apps.add(new MyModule(ctx)); 20 | } 21 | 22 | std::string GetAppDescribe() { 23 | return "This is CppTbox Demo, writen by Hevake Lee."; 24 | } 25 | 26 | void GetAppVersion(int &major, int &minor, int &rev, int &build) { 27 | major = 1; 28 | minor = 2; 29 | rev = 3; 30 | build = 4; 31 | } 32 | 33 | std::string GetAppBuildTime() { 34 | return std::string(__DATE__) + " " + __TIME__; 35 | } 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/15-thread-pool-simple/app_main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | //! CPU密集运算 5 | uint64_t Fibonacci(int n) { 6 | if (n <= 1) return n; 7 | return Fibonacci(n - 1) + Fibonacci(n - 2); 8 | } 9 | 10 | class MyModule : public tbox::main::Module { 11 | public: 12 | explicit MyModule(tbox::main::Context &ctx) 13 | : tbox::main::Module("my", ctx) 14 | { } 15 | 16 | public: 17 | virtual bool onStart() { 18 | int n = 40; 19 | LogTrace("start caculate fibonacci %d", n); 20 | //! 委托线程池执行任务 21 | ctx().thread_pool()->execute( 22 | [n] { 23 | //! 在线程池中执行 24 | LogTrace("fibonacci %d calculating", n); 25 | auto result = Fibonacci(n); //! 进行CPU密集的运算 26 | LogTrace("caculate fibonacci %d result: %llu", n, result); 27 | } 28 | ); 29 | 30 | LogTag(); 31 | return true; 32 | } 33 | }; 34 | 35 | namespace tbox { 36 | namespace main { 37 | void RegisterApps(Module &apps, Context &ctx) { 38 | apps.add(new MyModule(ctx)); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/06-log-print/app_main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | class MyModule : public tbox::main::Module { 5 | public: 6 | explicit MyModule(tbox::main::Context &ctx) : tbox::main::Module("my", ctx) { } 7 | virtual ~MyModule() { } 8 | 9 | public: 10 | virtual bool onInit(const tbox::Json &js) override { 11 | LogFatal("this is fatal log"); 12 | LogErr("this is error log"); 13 | LogWarn("this is warn log"); 14 | LogNotice("this is notice log"); 15 | LogImportant("this is important log"); 16 | LogInfo("this is info log"); 17 | LogDbg("this is debug log"); 18 | LogTrace("this is trace log"); 19 | LogUndo(); 20 | LogTag(); 21 | 22 | LogTrace("format, %d, %s, %.3f", 12, "hello, cpp-tbox!", 0.2345); 23 | LogPrintf(LOG_LEVEL_NOTICE, "%s", "LogPrintf()"); 24 | LogPuts(LOG_LEVEL_INFO, "LogPuts()"); 25 | return true; 26 | } 27 | }; 28 | 29 | namespace tbox { 30 | namespace main { 31 | void RegisterApps(Module &apps, Context &ctx) { 32 | apps.add(new MyModule(ctx)); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Hevake Lee 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/17-work-thread/app_main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | //! CPU密集运算 6 | uint64_t Fibonacci(int n) { 7 | if (n <= 1) return n; 8 | return Fibonacci(n - 1) + Fibonacci(n - 2); 9 | } 10 | 11 | class MyModule : public tbox::main::Module { 12 | public: 13 | explicit MyModule(tbox::main::Context &ctx) 14 | : tbox::main::Module("my", ctx) 15 | { } 16 | 17 | public: 18 | virtual bool onStart() { 19 | int n = 40; 20 | LogTrace("start caculate fibonacci %d", n); 21 | //! 委托线程池执行任务 22 | worker_.execute( 23 | [n] { 24 | //! 在线程池中执行 25 | LogTrace("fibonacci %d calculating", n); 26 | auto result = Fibonacci(n); //! 进行CPU密集的运算 27 | LogTrace("caculate fibonacci %d result: %llu", n, result); 28 | } 29 | ); 30 | 31 | LogTag(); 32 | return true; 33 | } 34 | private: 35 | tbox::eventx::WorkThread worker_; 36 | }; 37 | 38 | namespace tbox { 39 | namespace main { 40 | void RegisterApps(Module &apps, Context &ctx) { 41 | apps.add(new MyModule(ctx)); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /examples/13-timer-pool/app_main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | class MyModule : public tbox::main::Module { 5 | public: 6 | explicit MyModule(tbox::main::Context &ctx) 7 | : tbox::main::Module("my", ctx) 8 | { } 9 | 10 | public: 11 | virtual bool onStart() { 12 | //! 启动周期性定时 13 | repeat_timer_token_ = ctx().timer_pool()->doEvery(std::chrono::seconds(1), [this] { onTick(); }); 14 | //! 启动单次性定时 15 | oneshot_timer_token_ = ctx().timer_pool()->doAfter(std::chrono::seconds(5), [] { LogTag(); }); 16 | return true; 17 | } 18 | 19 | virtual void onStop() { 20 | //! 退出时要记得cancel所有的定时器,以防生命期倒挂问题 21 | ctx().timer_pool()->cancel(repeat_timer_token_); 22 | ctx().timer_pool()->cancel(oneshot_timer_token_); 23 | } 24 | 25 | private: 26 | void onTick() { 27 | ++count_; 28 | LogDbg("count:%d", count_); 29 | } 30 | 31 | private: 32 | tbox::eventx::TimerPool::TimerToken repeat_timer_token_; 33 | tbox::eventx::TimerPool::TimerToken oneshot_timer_token_; 34 | int count_ = 0; 35 | }; 36 | 37 | namespace tbox { 38 | namespace main { 39 | void RegisterApps(Module &apps, Context &ctx) { 40 | apps.add(new MyModule(ctx)); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /examples/18-run-in-loop/app_main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | class MyModule : public tbox::main::Module { 6 | public: 7 | explicit MyModule(tbox::main::Context &ctx) 8 | : tbox::main::Module("my", ctx) 9 | { } 10 | 11 | public: 12 | virtual bool onStart() override { 13 | //! 创建子线程,令其周期性调用runInLoop() 14 | thread_ = std::thread([this] { 15 | int index = 0; 16 | while (keep_running_) { 17 | //! 延时1秒 18 | std::this_thread::sleep_for(std::chrono::seconds(1)); 19 | ++index; 20 | 21 | LogDbg("thread index: %d", index); 22 | //! 委托给Loop线程 23 | ctx().loop()->runInLoop([index] { 24 | LogDbg("loop index: %d", index); 25 | }); 26 | } 27 | }); 28 | return true; 29 | } 30 | 31 | virtual void onStop() override { keep_running_ = false; } 32 | virtual void onCleanup() override { thread_.join(); } 33 | 34 | private: 35 | bool keep_running_ = true; 36 | std::thread thread_; 37 | }; 38 | 39 | namespace tbox { 40 | namespace main { 41 | void RegisterApps(Module &apps, Context &ctx) { 42 | apps.add(new MyModule(ctx)); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /examples/50-coffee-machine/coffee/app.h: -------------------------------------------------------------------------------- 1 | #ifndef COFFEE_APP_H 2 | #define COFFEE_APP_H 3 | 4 | #include 5 | #include 6 | 7 | namespace coffee { 8 | 9 | class App : public tbox::main::Module { 10 | public: 11 | explicit App(tbox::main::Context &ctx); 12 | virtual ~App(); 13 | 14 | public: 15 | virtual bool onInit(const tbox::Json &js) override; 16 | virtual bool onStart() override; 17 | virtual void onStop() override; 18 | virtual void onCleanup() override; 19 | 20 | protected: 21 | enum class StateId { 22 | kTerm = 0, 23 | kCheck, //!< 自检中 24 | kIdle, //!< 空闲中 25 | kWorking, //!< 工作中 26 | kFault, //!< 故障中 27 | kMax 28 | }; 29 | std::string ToString(StateId id); 30 | 31 | enum class EventId { 32 | kAny = 0, 33 | kMake, //!< 制作 34 | kSucc, //!< 成功 35 | kFail, //!< 失败 36 | kStop, //!< 停止 37 | kCheck, //!< 自检 38 | kMax 39 | }; 40 | std::string ToString(EventId id); 41 | 42 | void initSm(); 43 | void initShell(); 44 | 45 | void onTimerTick(); 46 | 47 | private: 48 | tbox::event::TimerEvent *timer_ = nullptr; 49 | tbox::flow::StateMachine sm_; 50 | 51 | int remain_sec_ = 0; 52 | }; 53 | 54 | } 55 | 56 | #endif //COFFEE_APP_H 57 | -------------------------------------------------------------------------------- /03-event-drive.md: -------------------------------------------------------------------------------- 1 | # 事件驱动模式的编程 2 | 3 | 在上面的教程中,我们创建了第一个自己模块`MyModule`,我们只需要重写它的`onInit()`,`onStart()`,`onStop()`,`onCleanup()`就可以实现所有的业务功能。这对第一次接触网络编程的同学会比较陌生。 4 | 5 | 在传统的开发中,我们是需要创建很多线程来实现与外部交互的功能的。 6 | 比如:读写串口,我们要创建两个线程。其中一个专门负责读串口设备文件,将读到的数据写入到一个读队列里,再由业务线程去处理。再创建一个线程,负责从发送队列里取出要发送数据,并写到串口设备文件中。如果同时管理N个串口,那么就需要创建2N个线程。如果是一个网络通信的服务程序,要为每一个socket创建一个读线程。 7 | 这无疑增加了内存资源与CPU资源的消耗,同时要管理多线程之间的资源访问,避免竞态。如果在加锁上出现纰漏,多线程出现访问竞态,会出现很多偶发性bug,很不好查。采用这样的编程模式会很令人心身疲惫。 8 | 9 | tbox采用的是传统的网络编程模式:Reactor。关于 Reactor 我们可以在网上找到很多资料,笔者就不再在此赘述了。 10 | tbox.main 框架主要借鉴了 [Node.js](https://nodejs.org/zh-cn) 的思想。采用:**Reactor线程 + 线程池** 的结构。 11 | 12 | - Reactor线程:采用多路复用模型,处理所有的事件; 13 | - 线程池:辅助执行阻塞性的函数调用、与大运算; 14 | 15 | Reactor线程就像是一个银行的办事柜台。如果遇到很轻松就能完成的事务,比如查询余额,柜台工作人员就立即处理了。如果遇到的是比较繁重的工作,比如大额的取款,柜台工作人员便令后面的工作人员进行操作,让顾客在休息区等待。柜台工作人员则继续接待其它的顾客。等后面的工作人员取出了大金额的现金后,柜台工作人员呼叫取钱的顾客,并将现金给到该顾客。 16 | 这个过程中,柜台工作人员就是Reactor线程,后面的工作人员就是线程池的工作线程。查询余额则是非阻塞性任务,取大额现金则是阻塞性的任务。 17 | 18 | 在 tbox.main 框架编程中,一切都是基于事件驱动的。具体操作就是:向Reactor注册某个事件的回调函数。当该事件发生了,Reactor就会回调之前注册的函数。 19 | 这种模型对注册的回调函数有三个基本的要求:**不要阻塞!不要阻塞!不要阻塞!**。 20 | 大家可以拜读一下 Node.js 的官方文档《[不要阻塞事件循环](https://nodejs.org/zh-cn/docs/guides/dont-block-the-event-loop)》,咱们的 cpp-tbox 与 Node.js 的机制是一样的,原理也是相通的。 21 | 22 | 基于这个要求: 23 | 24 | - 所有的回调事务要尽快完成,时间消耗要限制在1ms以内; 25 | - 不要执行读写文件的操作,如果需要,移交给线程池去做; 26 | - 不要进行大规模运算,如果需要,移交给线程池去做。 27 | 28 | ------- 29 | [[返回主页]](README.md) 30 | -------------------------------------------------------------------------------- /examples/07-timer-event/app_main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include /// 导入TimerEvent类 4 | 5 | class MyModule : public tbox::main::Module { 6 | public: 7 | explicit MyModule(tbox::main::Context &ctx) 8 | : tbox::main::Module("my", ctx) 9 | , tick_timer_(ctx.loop()->newTimerEvent()) /// 实例化定时器对象 10 | { } 11 | 12 | virtual ~MyModule() { delete tick_timer_; } /// 释放定时器对象 13 | 14 | public: 15 | virtual bool onInit(const tbox::Json &js) override { 16 | /// 初始化,设置定时间隔为1s,持续触发 17 | tick_timer_->initialize(std::chrono::seconds(1), tbox::event::Event::Mode::kPersist); 18 | /// 设置定时器触发时的回调 19 | tick_timer_->setCallback([this] { onTick(); }); 20 | return true; 21 | } 22 | /// 启动 23 | virtual bool onStart() { 24 | tick_timer_->enable(); 25 | return true; 26 | } 27 | /// 停止 28 | virtual void onStop() { 29 | tick_timer_->disable(); 30 | } 31 | 32 | private: 33 | void onTick() { 34 | ++count_; 35 | LogDbg("count:%d", count_); 36 | } 37 | 38 | private: 39 | tbox::event::TimerEvent *tick_timer_; 40 | int count_ = 0; 41 | }; 42 | 43 | namespace tbox { 44 | namespace main { 45 | void RegisterApps(Module &apps, Context &ctx) { 46 | apps.add(new MyModule(ctx)); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /09-add-app-info.md: -------------------------------------------------------------------------------- 1 | # 完善应用信息 2 | 3 | 上面,我们通过定义一个`MyModule`类,并重写其虚函数,实现了我们一个简单的应用。但仅仅是这么做是不完整的。 4 | 当我们执行:`./demo -h` 时,当我们执行:`./demo -v` 或 `./demo --version` 时看到: 5 | ![无应用信息](images/005-no-desc.png) 6 | 这是因为,我们并没有对当前程序的描述、版本进行描述。我们可以通过在 app\_main.cpp 文件中加如下红框中的代码完善它: 7 | ![添加代码](images/006-add-code.png) 8 | 上面定义了三个函数:`GetAppDescribe()`、`GetAppVersion()`、`GetAppBuildTime()`,分析用于告析 tbox.main 框架当前应用的描述、版本号、编译时间点。 9 | 10 | [完整示例代码](examples/03-add-app-info) 11 | 12 | 重新构建,再次执行 `./demo -v; ./demo -h` 效果: 13 | ![](images/007-has-desc.png) 14 | 15 | ## 推荐的工程结构 16 | 在上面的示例中,我在 app_main.cpp 实现了所有的代码。而在真正的项目开发中,是不推荐这么做的。 17 | 我们应该对 `app_main.cpp` 进行简单地拆分: 18 | 19 | - 将 `GetAppBuildTime()` 函数放到单独的文件中 build.cpp,并修改 Makefile 保证每次构建时,它都会被重新编译,以确保这个函数返回的时间与构建的时间一致; 20 | - 将 `GetAppVersion()`, `GetAppDescribe()` 放置在 `info.cpp` 中,只要更新版本或应用描述才会改到它; 21 | - 将 `RegisterApps()` 放置在 `apps.cpp` 中,以后基本不会再改它了; 22 | - 创建你自己的应用目录,存放自定义的 `Module` 的实现,所有的业务相关的代码,都在这个目录下实现。 23 | 24 | 最终产生文件结构: 25 | ``` 26 | . 27 | ├── apps.cpp # 定义 RegisterApps() 28 | ├── build.cpp # 定义 GetAppBuildTime() 29 | ├── info.cpp # 定义 GetAppVersion(), GetAppDescribe() 30 | ├── Makefile 31 | └── my # 业务相关Module实现 32 | ├── app.mk 33 | ├── module.cpp 34 | └── module.h 35 | ``` 36 | 37 | [示例工程目录](examples/04-normal-app-demo) 38 | 39 | 建议:在 my/ 目录下的所有代码,均以 `my` 作为顶层命名空间,以防在下一节的多Module情况下,命名空间污染。 40 | 41 | ------- 42 | [[返回主页]](README.md) 43 | -------------------------------------------------------------------------------- /examples/10-tiny-http-server/app_main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include //! 导入Http服务 4 | 5 | class MyModule : public tbox::main::Module { 6 | public: 7 | explicit MyModule(tbox::main::Context &ctx) 8 | : tbox::main::Module("my_http", ctx) 9 | , http_server_(ctx.loop()) //! 实例化Http服务对象 10 | { } 11 | 12 | public: 13 | virtual bool onInit(const tbox::Json &js) override { 14 | //! 初始化Http服务,绑定 0.0.0.0:12345 地址,backlog为2 15 | if (!http_server_.initialize(tbox::network::SockAddr::FromString("0.0.0.0:12345"), 2)) 16 | return false; 17 | 18 | //! 指定Http服务的请求的处理方法 19 | http_server_.use( 20 | [] (tbox::http::server::ContextSptr http_ctx, 21 | const tbox::http::server::NextFunc &next_func) { 22 | //! 不管请求的是什么,直接指定回复的body内容 23 | http_ctx->res().body = R"(

Welcome to CppTbox.
This is http example.

)"; 24 | } 25 | ); 26 | 27 | return true; 28 | } 29 | virtual bool onStart() override { 30 | http_server_.start(); //! 启动Http服务 31 | return true; 32 | } 33 | virtual void onStop() override { 34 | http_server_.stop(); //! 停止Http服务 35 | } 36 | 37 | private: 38 | tbox::http::server::Server http_server_; //! 定义Http服务对象 39 | }; 40 | 41 | namespace tbox { 42 | namespace main { 43 | void RegisterApps(Module &apps, Context &ctx) { 44 | apps.add(new MyModule(ctx)); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /examples/14-timer-fd/app_main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | class MyModule : public tbox::main::Module { 6 | public: 7 | explicit MyModule(tbox::main::Context &ctx) 8 | : tbox::main::Module("my", ctx) 9 | , oneshot_timer_(ctx.loop()) 10 | , repeat_timer_(ctx.loop()) 11 | { } 12 | 13 | public: 14 | virtual bool onInit(const tbox::Json &) { 15 | //! 只设置一个时间,表示单次定时5秒 16 | oneshot_timer_.initialize(std::chrono::seconds(5)); 17 | oneshot_timer_.setCallback([] { LogDbg("oneshot"); }); 18 | 19 | //! 设置了两个时间,第一个时间表示首次触发为100ms,第二个时间表示后续间隔1秒触发 20 | repeat_timer_.initialize(std::chrono::milliseconds(100), std::chrono::seconds(1)); 21 | repeat_timer_.setCallback([] { LogDbg("repeat"); }); 22 | 23 | return true; 24 | } 25 | 26 | virtual bool onStart() { 27 | oneshot_timer_.enable(); 28 | repeat_timer_.enable(); 29 | return true; 30 | } 31 | 32 | virtual void onStop() { 33 | repeat_timer_.disable(); 34 | oneshot_timer_.disable(); 35 | } 36 | 37 | virtual void onCleanup() { 38 | repeat_timer_.cleanup(); 39 | oneshot_timer_.cleanup(); 40 | } 41 | 42 | private: 43 | tbox::eventx::TimerFd oneshot_timer_; 44 | tbox::eventx::TimerFd repeat_timer_; 45 | }; 46 | 47 | namespace tbox { 48 | namespace main { 49 | void RegisterApps(Module &apps, Context &ctx) { 50 | apps.add(new MyModule(ctx)); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /13-timer-fd.md: -------------------------------------------------------------------------------- 1 | # TimerFd 2 | 3 | 在 [定时器](04-timer-event.md) 这一节中,我们提及到`TimerEvent`的实现。本质上,它是通过`epoll()`或`select()`中阻塞的超时参数来实现定时任务的。`TimerEvent`在实际运用中的会有约400us的误差。如果想要精度更好的定时器,则需要使用本节介绍的`TimerFd`。 4 | `TimerFd` 定义在 `eventx/timer_fd.h` 中,是基于Linux内核提供的`timer_fd`实现的。具体的原理可阅读相关文章《[Linux fd 系列 — 定时器 timerfd 是什么?](https://zhuanlan.zhihu.com/p/409434419)》 5 | 6 | 本节通过一个示例来引导大家掌握`TimerFd`的使用方法。 7 | 8 | ![](images/047-timer-fd-code.png) 9 | 10 | 1) 与`TimerEvent`不同的是`TimerFd`不属于`Loop`的一部分,也没有被纳入到框架。要使用它,都需要`#include ` 11 | 2) 在`MyModule`中分别实列化两个`TimerFd`对象:`oneshot_timer_`与`repeat_timer_`; 12 | 3) 在`MyModule`的初始化列表中对`oneshot_timer_`与`repeat_timer_`进行构造。注意,它的初始化需要`ctx().loop()`获取的`Loop`对象指针; 13 | 4) 在`onInit()`中对它们进行初始化,只传一个时间的表示单次触发;传两个时间的表示单次触发之后,还需要重复触发; 14 | 5) 在`onStart()`中使能它们。在工程中,不是一定要在`onStart()`中使能它们,根据业务需要我们也可以在需要的时间再使能它们; 15 | 6) 在`onStop()`中关闭它们; 16 | 7) 在`onCleanup()`中清理它们; 17 | 18 | [示例源码](examples/14-timer-fd/app_main.cpp) 19 | [示例工程目录](examples/14-timer-fd) 20 | 21 | 编译后,执行效果: 22 | ![](images/046-timer-fd-run.png) 23 | 我们注意到,它的定时精确误差在5us以内,非常可观。 24 | 25 | 思考:为什么使用`TimerFd`的精准度会比`TimerEvent`高这么多? 26 | 猜想:由于`TimerFd`是基于Linux内核中的`timer_fd`实现的。在`timer_fd`触发超时时,内核立即让`timer_fd`的文件描述符变成可读,鉴于定时器的实时性要求,Linux内核会立即唤醒对应的线程并执行。而基于`epoll()`与`select()`超时参数的`TimerEvent`则得不到Linux内核的特殊照顾。当`epoll()`或`select()`等待超时后,Linux内核只将对应的线程设置为READY状态并排队等待CPU资源,以致于执行的时间点存在较大的误差。 27 | 28 | 问题:`TimerEvent`,`TimerPool`,`TimerFd` 这三种实现方式如何抉择? 29 | 回答: 30 | - 对于日常对精度要求不太高(如1ms精度可接受)的定时执行场景,可从`TimerEvent`与`TimerPool`中任选一种。如果是对精度要求高,要保证100us以内误差的,使用`TimerFd`; 31 | - 遇到首次触发与后续触发间隔不一致的,采用`TimerFd`。 32 | 33 | ------- 34 | [[返回主页]](README.md) 35 | -------------------------------------------------------------------------------- /02-add-log-tag.md: -------------------------------------------------------------------------------- 1 | # 日志的打印 2 | 3 | 调试日志是程序中一个比较重要的一部分。通常,我们在开发程序的时候,会直接使用 `printf()` 或 `std::cout`,`std::cerr` 在终端上打印日志。但这样打印,有很多不足:1)面日志格式混乱;2)能提供的调试信息不够充分;3)输出的方式太过单一;4)没有日志等级筛选功能。 4 | 因此,我们会去寻找开源的日志系统库,如:spdlog, glog, log4cxx,来满足日志打印需求。 5 | 6 | 好在,当你使用 tbox.main 框架时,你根本就不需要为日志打印而发愁,因为它自带日志打印系统。你直接用就可以了,其它的不需要你关心。 7 | 8 | 日志等级有: 9 | |值|等级|名称|显示颜色| 10 | |:-|:-|:-|:-| 11 | |0|FATAL|代码错误|红底黑字| 12 | |1|ERROR|错误|红| 13 | |2|WARN|警告|黄底黑字| 14 | |3|NOTICE|注意|黄| 15 | |4|IMPORTANT|重要|绿底黑字| 16 | |5|INFO|信息|绿| 17 | |6|DEBUG|调试|淡蓝| 18 | |7|TRACE|跟踪|紫| 19 | 20 | 日志打印函数有: 21 | |函数|等级|用途| 22 | |:-|:-|:-| 23 | |`LogFatal(fmt,...)`|FATAL|打印程序代码级的错误,比如:程序崩馈,通常不会使用到| 24 | |`LogErr(fmt,...)`|ERROR|打印导致业务完全不可用的严重错误。区别于FATAL,指的是非程序级错误,比如配置文件打不开| 25 | |`LogWarn(fmt,...)`|WARN|打印影响部分功能的错误。区别于ERROR,这种错误不整响主要功能| 26 | |`LogNotice(fmt,...)`|NOTICE|打印外部因素引起的轻微异常,这种异常不会影响功能,但需要注意。如对方的协议格式错误、版本不一致| 27 | |`LogImportant(fmt,...)`|IMPORTANT|打印与外界交互中非常重要的信息,尽量少用,以突显其稀有性| 28 | |`LogInfo(fmt,...)`|INFO|打印与外界交互的普通信息,用于鉴别问题是外部的,还是内部的| 29 | |`LogDbg(fmt,...)`|DEBUG|打印内部模块之间的信息,用于鉴别问题是属于内部的哪个模块的,以及处理方式| 30 | |`LogTrace(fmt,...)`|TRACE|打印临时查问题所需的日志信息| 31 | |`LogUndo()`|NOTICE|标记有未实现的功能,通用创建一个空函数时,就会放置一个LogUndo()| 32 | |`LogTag()`|TRACE|用于打印运行标记,观察程序有没有运行过标记位置| 33 | |`LogPrintf(level,fmt,...)`||在参数中指定等级打印格式化日志| 34 | |`LogPuts(level,text)`||在参数中指定等级打印字符串日志| 35 | 36 | 详见 [log.h](https://gitee.com/cpp-master/cpp-tbox/blob/master/modules/base/log.h) 37 | 38 | 下面,我们来实际操作一下,在MyModule的onInit()尝试所有的日志打印函数: 39 | ![](images/012-log-print-code.png) 40 | 41 | [示例工程目录](examples/06-log-print/) 42 | 43 | 编译执行效果: 44 | ![日志打印效果](images/011-log-print.png) 45 | 46 | 只要运行,我们就能在终端上就可以看到日志打印了。 47 | 与常规的日志打印一样,一条日志记录打印一行。每一条记录都有以下信息: 48 | ![日志字段](images/013-log-field.png) 49 | 日志的时间点精确到微秒,有线程号,函数名、文件及行号,对调试非常有帮助。 50 | 51 | Q: 日志除了打印到终端,还能输出到别的地方吗? 52 | A: 日志系统有三种输出方式:stdout(终端)、syslog、file。在不指定的情况下,默认只输出到stdout。如果需要,可以通过参数分别对这三种输出方式进行配置。这个后面讲参数的时候详细介绍。 53 | 54 | ------- 55 | [[返回主页]](README.md) 56 | -------------------------------------------------------------------------------- /examples/11-parameters/app_main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include //! 引入Json的实现 5 | #include //! 引入GetField()函数,方便操作Json 6 | 7 | class MyModule : public tbox::main::Module { 8 | public: 9 | explicit MyModule(tbox::main::Context &ctx) 10 | : tbox::main::Module("my_http", ctx) 11 | , http_server_(ctx.loop()) 12 | { } 13 | 14 | public: 15 | //! 指定默认的参数值 16 | virtual void onFillDefaultConfig(tbox::Json &js) override { 17 | js["bind_addr"] = "0.0.0.0:12345"; 18 | js["backlog"] = 2; 19 | } 20 | virtual bool onInit(const tbox::Json &js) override { 21 | std::string bind_addr; 22 | int backlog = 0; 23 | //! 从js中获取bind_addr与backlog的值 24 | if (!tbox::util::json::GetField(js, "bind_addr", bind_addr) || //! 从js中提取"bind_addr"字段存入bind_addr 25 | !tbox::util::json::GetField(js, "backlog", backlog)) //! 从js中提取"backlog"字段存入backlog 26 | return false; 27 | 28 | //! 在初始化中,使用bind_addr与backlog 29 | if (!http_server_.initialize(tbox::network::SockAddr::FromString(bind_addr), backlog)) 30 | return false; 31 | 32 | http_server_.use( 33 | [] (tbox::http::server::ContextSptr http_ctx, 34 | const tbox::http::server::NextFunc &next_func) { 35 | http_ctx->res().body = R"(

Welcome to CppTbox.
This is http example.

)"; 36 | } 37 | ); 38 | return true; 39 | } 40 | virtual bool onStart() override { 41 | http_server_.start(); 42 | return true; 43 | } 44 | virtual void onStop() override { 45 | http_server_.stop(); 46 | } 47 | 48 | private: 49 | tbox::http::server::Server http_server_; 50 | }; 51 | 52 | namespace tbox { 53 | namespace main { 54 | void RegisterApps(Module &apps, Context &ctx) { 55 | apps.add(new MyModule(ctx)); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /examples/09-signal-event/app_main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include //! 导入SignalEvent 6 | 7 | using namespace std::placeholders; 8 | 9 | class MyModule : public tbox::main::Module { 10 | public: 11 | explicit MyModule(tbox::main::Context &ctx) 12 | : tbox::main::Module("my", ctx) 13 | , tick_timer_(ctx.loop()->newTimerEvent()) 14 | , signal_event_(ctx.loop()->newSignalEvent()) //! 实例化SignalEvent对象 15 | { } 16 | 17 | virtual ~MyModule() { 18 | delete signal_event_; //! 释放SignalEvent对象 19 | delete tick_timer_; 20 | } 21 | 22 | public: 23 | virtual bool onInit(const tbox::Json &js) override { 24 | tick_timer_->initialize(std::chrono::seconds(1), tbox::event::Event::Mode::kPersist); 25 | tick_timer_->setCallback([this] { onTick(); }); 26 | 27 | //! 初始化SignalEvent,令其同时监听SIGUSR1,SIGUSR2信号,且是持续有效 28 | signal_event_->initialize({SIGUSR1, SIGUSR2}, tbox::event::Event::Mode::kPersist); 29 | //! 设置回调函数,均指向onSignalEvent() 30 | signal_event_->setCallback(std::bind(&MyModule::onSignalEvent, this, _1)); 31 | return true; 32 | } 33 | virtual bool onStart() { 34 | tick_timer_->enable(); 35 | signal_event_->enable(); //! 使能SignalEvent 36 | return true; 37 | } 38 | virtual void onStop() { 39 | signal_event_->disable(); 40 | tick_timer_->disable(); //! 关闭SignalEvent 41 | } 42 | 43 | private: 44 | void onTick() { 45 | ++count_; 46 | LogDbg("count:%d", count_); 47 | } 48 | 49 | //! 信号事件时的处理 50 | void onSignalEvent(int signo) { 51 | LogInfo("got signal: %d", signo); 52 | if (signo == SIGUSR1) 53 | tick_timer_->disable(); //! 关闭定时器 54 | else if (signo == SIGUSR2) 55 | tick_timer_->enable(); //! 打开定时器 56 | } 57 | 58 | private: 59 | tbox::event::TimerEvent *tick_timer_; 60 | tbox::event::SignalEvent *signal_event_; //!< 定义SignalEvent对象 61 | int count_ = 0; 62 | }; 63 | 64 | namespace tbox { 65 | namespace main { 66 | void RegisterApps(Module &apps, Context &ctx) { 67 | apps.add(new MyModule(ctx)); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![cpp-tbox-logo](images/logo-with-slogan.png) 2 | 3 | # CppTbox 的入门教程 4 | 5 | 本项目为 [cpp-tbox](https://github.com/cpp-main/cpp-tbox) 的入门教程。 6 | 您可以通过下面一个个的教程逐步掌握 cpp-tbox 的使用。 7 | 8 | ## 准备工作 9 | 在开始学习之后,我们需要安装必要的工具,并将cpp-tbox构建并安装好。 10 | [[点击前往]](00-prepare.md) 11 | 12 | ## 第一个程序 13 | 与学习一门新的编程语言类似,我们来尝试创建一个什么功能都没有的空程序,看看工具与环境是否已安装好。 14 | [[点击前往]](00-first-demo.md) 15 | 16 | ## 写一个自己的 Module 17 | 往空程序里加一点自己的功能。 18 | [[点击前往]](01-first-module.md) 19 | 20 | ## 日志的打印 21 | 打印日志是开发中必须要会的技能。接下来,我们学习如何打印日志。 22 | [[点击前往]](02-add-log-tag.md) 23 | 24 | ## 事件驱动模式的编程 25 | 在使用cpp-tbox进行开发之前,必须得理解什么是基于事件驱动的编程,以及什么是Reactor模型。 26 | [[点击前往]](03-event-drive.md) 27 | 28 | ## 定时器事件 29 | 实现一个定时打印日志的功能,掌握定时器的使用。 30 | [[点击前往]](04-timer-event.md) 31 | 32 | ## IO事件 33 | 掌握对文件描述符的读写事件监听技能。 34 | [[点击前往]](05-fd-event.md) 35 | 36 | ## Signal事件 37 | 掌握捕获信号的技能。 38 | [[点击前往]](11-signal-event.md) 39 | 40 | ## HTTP服务 41 | 使用cpp-tbox中自带的http模块实现一个简单的http服务。 42 | [[点击前往]](06-http-server.md) 43 | 44 | ## 配置系统 45 | 掌握如何使用参数,将外部的配置数据传递到程序中。 46 | [[点击前往]](07-parameters.md) 47 | 48 | ## 命令终端 49 | 掌握如何使用终端进行调试。 50 | [[点击前往]](08-terminal.md) 51 | 52 | ## 日志输出 53 | 54 | ## 线程池与工作线程的使用 55 | 由于cpp-tbox是基于事件驱动的编程模型,对于事件的处理要求不能阻塞。然而实际的工作中,多多少少会遇到一些需要阻塞的事务。比如说:大运算、调用第三方库的阻塞性接口等。 56 | 这时,就得需要借助`ThreadPool`与`WorkThread`来解决问题。 57 | [[点击前往]](14-thread-pool-and-work-thread.md) 58 | 59 | ## 子线程向主线程委派任务 60 | 上面学会了Loop线程往线程池与工作线程单向委派任务。反过来,子线程也可以向Loop线程委派任务。 61 | [[点击前往]](15-run-in-loop.md) 62 | 63 | ## 定时器池的使用 64 | 前面了解了定时器的使用,这里来了解一种创建定时器更方便的方式。 65 | [[点击前往]](12-timer-pool.md) 66 | 67 | ## TimerFd的使用 68 | 学习另一种定时更精准的定时器。 69 | [[点击前往]](13-timer-fd.md) 70 | 71 | ## 运行时异常捕获功能 72 | 73 | ## 多层级Module 74 | 75 | ## 使用TcpServer模块写一个echo服务 76 | 77 | ## 使用TcpClient模块写一个客户端 78 | 79 | ## 使用TcpAcceptor + TcpConnection 实现echo服务 80 | 81 | ## 使用TcpConnector + TcpConnection 实现客户端 82 | 83 | ## 串口使用 84 | ### 写一个串口与终端的连接服务 85 | ### 写一个两个串口的连接服务 86 | ### 写一个串口转TCP的服务 87 | 88 | ## 完善应用信息 89 | [[点击前往]](09-add-app-info.md) 90 | 91 | ## 多个Module的工程结构 92 | [[点击前往]](10-multi-modules.md) 93 | 94 | ## 模块插件化 95 | 96 | ## 状态机的使用 97 | 通过交通信号灯的示例,带你掌握状态机的使用。 98 | [[点击前往]](16-state-machine.md) 99 | 100 | ## 动作树的使用 101 | [[点击前往]](17-action-tree.md) 102 | 103 | ## 实战 104 | ### 打造一个咖啡机 105 | 学习状态机、行为树的使用 106 | [[点击前往]](50-coffee-machine.md) 107 | -------------------------------------------------------------------------------- /examples/12-terminal/app_main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | class MyModule : public tbox::main::Module { 7 | public: 8 | explicit MyModule(tbox::main::Context &ctx) 9 | : tbox::main::Module("my_http", ctx) 10 | , http_server_(ctx.loop()) 11 | { } 12 | 13 | public: 14 | virtual bool onInit(const tbox::Json &js) override { 15 | if (!http_server_.initialize(tbox::network::SockAddr::FromString("0.0.0.0:12345"), 2)) 16 | return false; 17 | 18 | http_server_.use( 19 | [this] (tbox::http::server::ContextSptr http_ctx, 20 | const tbox::http::server::NextFunc &next_func) { 21 | http_ctx->res().body = content_; 22 | } 23 | ); 24 | 25 | initShell(); 26 | return true; 27 | } 28 | virtual bool onStart() override { 29 | http_server_.start(); 30 | return true; 31 | } 32 | virtual void onStop() override { 33 | http_server_.stop(); 34 | } 35 | 36 | protected: 37 | void initShell() { 38 | using namespace tbox::terminal; 39 | auto shell = ctx().terminal(); 40 | auto dir_node = shell->createDirNode(); 41 | shell->mountNode(shell->findNode("/"), dir_node, "my_http"); 42 | 43 | { 44 | auto func_node = shell->createFuncNode( 45 | [this] (const Session &s, const Args &) { 46 | s.send(content_ + "\r\n"); 47 | }, 48 | "print content_ value" 49 | ); 50 | shell->mountNode(dir_node, func_node, "print_content"); 51 | } 52 | { 53 | auto func_node = shell->createFuncNode( 54 | [this] (const Session &s, const Args &a) { 55 | if (a.size() != 2) { 56 | s.send("Usage: " + a[0] + " 'new_content'\r\n"); 57 | } else { 58 | content_ = a[1]; 59 | s.send("done\r\n"); 60 | } 61 | }, 62 | "set content_ value" 63 | ); 64 | shell->mountNode(dir_node, func_node, "set_content"); 65 | } 66 | } 67 | 68 | private: 69 | tbox::http::server::Server http_server_; 70 | std::string content_ = R"(

hello terminal

)"; 71 | }; 72 | 73 | namespace tbox { 74 | namespace main { 75 | void RegisterApps(Module &apps, Context &ctx) { 76 | apps.add(new MyModule(ctx)); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /examples/08-fd-event/app_main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include //! 导入FdEvent 6 | 7 | using namespace std::placeholders; 8 | 9 | class MyModule : public tbox::main::Module { 10 | public: 11 | explicit MyModule(tbox::main::Context &ctx) 12 | : tbox::main::Module("my", ctx) 13 | , tick_timer_(ctx.loop()->newTimerEvent()) 14 | , fd_event_(ctx.loop()->newFdEvent()) //! 实例化FdEvent对象 15 | { } 16 | 17 | virtual ~MyModule() { 18 | delete fd_event_; //! 释放FdEvent对象 19 | delete tick_timer_; 20 | } 21 | 22 | public: 23 | virtual bool onInit(const tbox::Json &js) override { 24 | tick_timer_->initialize(std::chrono::seconds(1), tbox::event::Event::Mode::kPersist); 25 | tick_timer_->setCallback([this] { onTick(); }); 26 | 27 | //! 初始化 FdEvent,指定为 stdin 的可读事件,且是持续有效 28 | fd_event_->initialize(0, 29 | tbox::event::FdEvent::kReadEvent, 30 | tbox::event::Event::Mode::kPersist); 31 | //! 设置FdEvent的回调函数 32 | fd_event_->setCallback(std::bind(&MyModule::onFdEvent, this, _1)); 33 | return true; 34 | } 35 | virtual bool onStart() { 36 | tick_timer_->enable(); 37 | fd_event_->enable(); //! 使能FdEvent 38 | return true; 39 | } 40 | virtual void onStop() { 41 | fd_event_->disable(); 42 | tick_timer_->disable(); //! 关闭FdEvent 43 | } 44 | 45 | private: 46 | void onTick() { 47 | ++count_; 48 | LogDbg("count:%d", count_); 49 | } 50 | 51 | //! stdin有输入事件时的处理 52 | void onFdEvent(short event) { 53 | //! 如果是可读事件 54 | if (event & tbox::event::FdEvent::kReadEvent) { 55 | char input[100] = {0}; 56 | //! 从 stdin 中读取数据 57 | auto rsize = ::read(0, input, sizeof(input)); 58 | 59 | std::string cmd(input); 60 | cmd.pop_back(); //! 丢弃尾部的'\n' 61 | LogDbg("cmd: '%s'", cmd.c_str()); 62 | 63 | if (cmd == "on") 64 | tick_timer_->enable(); //! 开定时器 65 | else if (cmd == "off") 66 | tick_timer_->disable(); //! 关定时器 67 | } 68 | } 69 | 70 | private: 71 | tbox::event::TimerEvent *tick_timer_; 72 | tbox::event::FdEvent *fd_event_; //!< 定义FdEvent对象 73 | int count_ = 0; 74 | }; 75 | 76 | namespace tbox { 77 | namespace main { 78 | void RegisterApps(Module &apps, Context &ctx) { 79 | apps.add(new MyModule(ctx)); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /examples/16-thread-pool-advance/app_main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | //! CPU密集运算 8 | uint64_t Fibonacci(int n) { 9 | if (n <= 1) return n; 10 | return Fibonacci(n - 1) + Fibonacci(n - 2); 11 | } 12 | 13 | class MyModule : public tbox::main::Module { 14 | public: 15 | explicit MyModule(tbox::main::Context &ctx) 16 | : tbox::main::Module("my", ctx) 17 | { } 18 | 19 | public: 20 | virtual bool onStart() { 21 | startCalculateTask(45); 22 | startCalculateTask(40); 23 | startCalculateTask(43); 24 | startCalculateTask(42); 25 | return true; 26 | } 27 | 28 | //! 启动计算任务 29 | void startCalculateTask(int n) { 30 | struct Tmp { 31 | int n = 0; 32 | uint64_t result = 0; 33 | }; 34 | auto tmp = std::make_shared(); 35 | tmp->n = n; 36 | unfinished_task_.insert(n); 37 | 38 | LogTrace("start caculate fibonacci %d", n); 39 | ctx().thread_pool()->execute( 40 | [tmp] { 41 | //! 工作线程中执行,要避免让它访问到公共资源 42 | LogTrace("fibonacci %d underway", tmp->n); 43 | tmp->result = Fibonacci(tmp->n); 44 | }, 45 | [this, tmp] { 46 | //! Loop线程中执行,允许访问公共资源 47 | onCaculateTaskFinished(tmp->n, tmp->result); 48 | } 49 | ); 50 | //! Q: 为什么不能在工作线程中直接调用onCaculateTaskFinished()? 51 | //! A: 因为onCaculateTaskFinished()函数会访问到公共变量unfinished_task_,result_,这是非常危险的事情。 52 | //! Loop线程与工作线程都去访问就会导致变量竞态,如果没有加锁就会引起不可预知的异常。 53 | //! 为此,onCaculateTaskFinished()必须在工作线程做完事情后的后续处理函数中调用,后续处理函数是由 54 | //! Loop线程进执行,所以是安全的。 55 | //! 由于工作线程不能直接访问公共数据,那么它正常工作需要的数据与执行的结果都需要通过Tmp这个结构体 56 | //! 进行传递。由于tmp所指的对象被Loop线程与工作线程的访问在时间上是隔离的,所以是安全的。 57 | } 58 | 59 | //! 计算任务完成后的处理 60 | void onCaculateTaskFinished(int n, uint64_t result) { 61 | LogTrace("caculate fibonacci %d result: %llu", n, result); 62 | unfinished_task_.erase(n); //! 从未完成任务中移除n 63 | results_[n] = result; //! 将结果存放到result_中 64 | 65 | if (unfinished_task_.empty()) { 66 | LogTrace("=== all task finished ==="); 67 | for (auto item : results_) { 68 | std::cout << item.first << " --> " << item.second << std::endl; 69 | } 70 | } 71 | } 72 | 73 | private: 74 | std::set unfinished_task_; 75 | std::map results_; 76 | }; 77 | 78 | namespace tbox { 79 | namespace main { 80 | void RegisterApps(Module &apps, Context &ctx) { 81 | apps.add(new MyModule(ctx)); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /01-first-module.md: -------------------------------------------------------------------------------- 1 | # 写一个自己的 Module 2 | 3 | 如果你很细心,你会发现上面的程序在运行之前有一堆提示: 4 | ![](images/001-first-demo-tips.png) 5 | 这是 tbox.main 框架在运行时,发现它没有任何可以运行的负载,向开发者打印的提示。 6 | 它希望开发者自己去定义一个自己的模块,如 `YourApp`(名字开发者自己取),然后按上面的方式注册到 tbox.main 框架上。 7 | 接下来,我们按第一个课程的提示,编写自己的 `Module`。 8 | 9 | 创建 app\_main.cpp 文件,内容如下: 10 | ```c++ 11 | #include 12 | 13 | class MyModule : public tbox::main::Module { 14 | public: 15 | explicit MyModule(tbox::main::Context &ctx) : tbox::main::Module("my", ctx) {} 16 | }; 17 | 18 | namespace tbox { 19 | namespace main { 20 | void RegisterApps(Module &apps, Context &ctx) { 21 | apps.add(new MyModule(ctx)); 22 | } 23 | } 24 | } 25 | ``` 26 | 然后再修改 Makefile,将 app\_main.cpp 加入到源文件中。 27 | 见:[Makefile](examples/01-first-module/Makefile) 28 | 29 | [示例工程目录](examples/01-first-module) 30 | 31 | 编译执行:`make && ./demo`,运行结果: 32 | ![运行效果图](images/002-your-first-module-1.png) 33 | 这次可以看到,它没有再打印之前的提示了。说明我们写的`MyModule`已经生效了。 34 | 35 | 但是,单从日志上看,我们并不能看出我们写的 MyModule 有真的运行起来。 36 | 接下来,我们再往 `MyModule` 中添加自定义的功能。让它在运行的过程中打印一点日志。 37 | 38 | ```c++ 39 | class MyModule : public tbox::main::Module { 40 | public: 41 | explicit MyModule(tbox::main::Context &ctx) : tbox::main::Module("my", ctx) { } 42 | 43 | public: 44 | virtual bool onInit(const tbox::Json &js) override { LogTag(); return true; } 45 | virtual bool onStart() override { LogTag(); return true; } 46 | virtual void onStop() override { LogTag(); } 47 | virtual void onCleanup() override { LogTag(); } 48 | }; 49 | ``` 50 | 51 | 我们重写了`MyModule`类的四个虚函数:`onInit()`,`onStart()`,`onStop()`,`onCleanup()`,并在每个虚函数中都添加了`LogTag()`日志打印。 52 | 为了能使用`LogTag()`日志打印函数,我们还需要添加`#include `。 53 | 完成之后执行 `make`。在编译的时侯,我们看到了一条编译警告: 54 | ![没有指定LOG\_MODULE\_ID警告](images/004-compile-warn.png) 55 | 它是在说我们程序没有指定日志的模块名。这仅是一条警告,我们可以忽略它。不过,我建议你在 Makefile 的`CXXFLAGS`定义中添加`-DMODULE_ID='"demo"'` 进行定义。 56 | 57 | [示例工程目录](examples/02-add-log-tag) 58 | 59 | 编译后执行,然后按 ctrl+c 退出程序,完整的日志打印效果: 60 | ![运行效果图](images/003-your-first-module-with-log.png) 61 | 62 | 可以看到,上面的出现了几条紫色的日志,这正是 LogTag() 打印的。 63 | 在程序启动的时候,我们看到依次执行了`onInit()`与`onStart()`。在我们按 ctrl+c 时,程序退出时也依次执行了`onStop()`与`onCleanup()`。 64 | 在真实的项目中,我们就通过重写 `tbox::main::Module` 中定义的虚函数与构造函数、析构函数来实现模块的功能的。 65 | 我们需要在这些函数里实现什么功能呢? 66 | 67 | | 函数 | 要实现的行为 | 注意事项 | 68 | |:-|:-|:-| 69 | | 构造函数 | 初始化自身的成员变量,包括new对象 | 不要做可能失败的行为。
如果有,放到 `onInit()` 去做 | 70 | | `onFillDefaultConfig()` | 往js对象中填写内容,填写本模块所需的默认参数 | 仅对 js 进行设置,不要做其它的事务 | 71 | | `onInit()` | 根据js对象传入的参数,进行初始化本模块、与其它的模块建立关系、加载文件等 | 72 | | `onStart()` | 令模块开始工作 | | 73 | | `onStop()` | 令模块停止工作,是 `onStart()` 的逆过程 | | 74 | | `onCleanup()` | 解除与其它模块之间的联系、保存文件,是 `onInit()` 的逆过程 | | 75 | | 析构函数 | 释放资源、delete对象,是构造的逆过程 | 不要做有可能失败的行为。
如果有,放到 `onCleanup()` 去做 | 76 | 77 | 至于为什么要设计这四种虚函数,以及它们之间的差别,详见 [cpp-tbox/module/main/module.h](https://gitee.com/cpp-master/cpp-tbox/blob/master/modules/main/module.h) 中的解析。 78 | 79 | Q:我看到上面有 `new MyModule(ctx)`,但我没有看到有对它的`delete`语句,是忘了写吗? 80 | A:tbox.main 架框会自己管理已注册`tbox::main::Module`派生类的生命期,一旦它被`add()`上去了,它的生命期就不需要开发者操心。 81 | 82 | ------- 83 | [[返回主页]](README.md) 84 | -------------------------------------------------------------------------------- /14-thread-pool-and-work-thread.md: -------------------------------------------------------------------------------- 1 | # 线程池与工作线程 2 | 由于cpp-tbox是基于事件驱动的编程模型,对于事件的处理要求不能阻塞。然而实际的工作中,多多少少会遇到一些需要阻塞的事务。比如说:大运算、调用第三方库的阻塞性接口等。 3 | 这时,就得需要借助`ThreadPool`与`WorkThread`来解决问题。 4 | 5 | ## ThreadPool 6 | 所谓线程池,就是预先创建指定个数的线程,让他们等待队列传入的任务。当队列中有新的任务后,其中一个线程就能抢到任务,并开始执行。如果同时来多个任务,那么这些空闲的线程便从逐一从队列中领取任务。工作线程在执行完任务后,会继续检查任务队列中是否还有其它的任务。如果有,会继续领取任务并执行。否则,观察当前任务数是否大于预设个数,如果是则结束线程,不是则处理空闲等待状态。 7 | 有了线程池,我们在开发中就不需要临时为阻塞性的函数调用创建线程,而是很方便地将这些阻塞性的函数调用委托给线程池,由线程池中的工作线程去执行。 8 | 9 | ### 基础用法 10 | 本节通过一个简单的示例来引导大家掌握`ThreadPool`的基础使用方法。 11 | 12 | ![](images/048-thread-pool-simple-code.png) 13 | 14 | L21,在`onStart()`函数中直接使用`ctx().thread_pool()->execute(...)`委派任务给线程池执行。这是因为在tbox.main框架中,ctx 自带一个线程池对象。我们只需要通过`ctx().thread_pool()`即可获取并使用。 15 | L22~27,在工作线程中打印日志并执行`Fibonacci()`函数模拟CPU密集的运算。 16 | 17 | 执行结果: 18 | ![](images/049-thread-pool-simple-run.png) 19 | 20 | 注意观察上面日志中的线程ID,9983是Loop线程,9984是工作线程,9985是监控线程。 21 | (2)与(3)都是在工作线程中打印的。 22 | 在最后黄框标记为线程池任务执行的耗时报告:"cost 201 + 722190",表示从任务派发到执行的等待时长为201us,而任务的执行花了722190us。 23 | 24 | [示例工程目录](examples/15-thread-pool-simple) 25 | 26 | ### 进阶用法 27 | 上面对线程池的使用仅仅是让工作线程做了一件事情,不需要后续的处理。然而实际业务中往往不是这样的,比如:解析请求 --> 写数据库 --> 回复;其中"写数据库"这步操作是阻塞的。我们需要在让线程池写完数据后之后再在Loop线程中执行回复的动作。 28 | 这就引入`execute()`的第二个参数: 29 | ``` 30 | TaskToken execute(NonReturnFunc &&backend_task, NonReturnFunc &&main_cb, int prio); 31 | ``` 32 | 这个参数传入的是一个函数对象,即工作线程执行完后,由Loop线程执行的后续处理。 33 | 34 | 接下来,我用另一个较为复杂一点的示例向大家展示它的使用场景: 35 | ![](images/050-thread-pool-advance-code.png) 36 | 37 | 这个示例也是让程序计算`Fibonacci()`。不同的是,让它一次性计算多个数值,而且Loop线程要在所有的计算都完成之后打印计算结果。 38 | 实现的方式大致为:Loop线程向线程池委派计算任务,在每一个工作线程完成了任务之后,让Loop线程记录计算结果,检查是否所有的计算都已完成。如果已完成,Loop线程打印所有的结果。 39 | 40 | 具体的实现: 41 | (1) 定义两个成员变量`unfinished_task_`,用于记录还有哪些任务没有完成;再定义`results_`记录不同数值计算所得的结果; 42 | (2) 在`onStart()`中调4次`startCalculateTask()`,启4个计算任务; 43 | (3) 在`startCalculateTask()`中,将需要计算的数值写入到`unfinished_task_`集合中。还需要定义`Tmp`结构体用于传递数据。注意,这里是Loop线程在执行,操作成员变量是安全的;并创建了`Tmp`结构体作为与子线程进行数据交换的媒介; 44 | (4) 这里由工作线程在进行计算。在这里,是从`tmp->n`中取值进行操作,完成之后也是将结果记录到`tmp->result`中。整个过程没有操作成员变量,不需要加锁; 45 | (5) 这里是由Loop线程执行的匿名函数,它会在工作线程执行完成之后被Loop线程调用。它的职责是处理`tmp`中的结果。 46 | (6)(7) 这是在做计算结果的处理。它有被Loop线程调用的,直接访问成员变量是安全的,无需加锁。 47 | 48 | 49 | 编译执行的效果: 50 | ![](images/051-thread-pool-advance-run-1.png) 51 | 注意观察可见: 52 | 1. 确实是子线程在进行计算; 53 | 2. 最后的结果打印是主线程; 54 | 3. 所有的任务都只被一个工作线程执行; 55 | 56 | Q: 为什么只有一个工作线程呢? 57 | A: 因为在tbox.main的框架配置中,线程池的默认配置为:最小0个,最大1个线程; 58 | 59 | 如果想看到并行运算的效果,那要将最大线程数调大一点。在运行时指定`thread_pool.max`参数的值即可: 60 | ``` 61 | ./demo -s 'thread_pool.max=5' 62 | ``` 63 | 运行效果: 64 | ![](images/052-thread-pool-advance-run-2.png) 65 | 从上可以看到,程序在启动时就为每一个任务创建了一个工作线程。 66 | 67 | [示例工程目录](examples/16-thread-pool-advance) 68 | 69 | ## WorkThread 70 | 工作线程与线程池的接口很像,区别在于`WorkThread`内部只有一个工作线程,相当于简化版本的`ThreadPool`。由于`WorkThread`只有一个工作线程,提交给它的任务是按顺执行的。为此,我们会使用它来实现对执行顺序有要求的场景。 71 | 72 | 本节将用示例展示`WorkThread`的使用方法。如下图所示: 73 | ![](images/053-work-thread-code.png) 74 | (1) 首先,需要`#include `; 75 | (2) 要在定义成员变量`worker_`; 76 | (3) 将上一节课中的`ctx().thread_pool()->`替代为`worker_.`即可; 77 | 78 | 运行效果: 79 | ![](images/054-work-thread-run.png) 80 | 可以看到,与线程池很相像; 81 | 82 | [示例工程目录](examples/17-work-thread) 83 | 84 | ------- 85 | [[返回主页]](README.md) 86 | 87 | -------------------------------------------------------------------------------- /examples/19-traffic-light-sm/app_main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | class MyModule : public tbox::main::Module { 6 | public: 7 | explicit MyModule(tbox::main::Context &ctx) 8 | : tbox::main::Module("my", ctx) 9 | { } 10 | 11 | public: 12 | virtual bool onInit(const tbox::Json &js) { 13 | initSm(); 14 | return true; 15 | } 16 | 17 | virtual bool onStart() override { 18 | sm_.start(); 19 | return true; 20 | } 21 | 22 | virtual void onStop() override { 23 | ctx().timer_pool()->cancel(timer_token_); 24 | sm_.stop(); 25 | } 26 | 27 | protected: 28 | enum class StateId { 29 | kTerm = 0, 30 | kGreen, //! 绿灯 31 | kYellow, //! 黄灯 32 | kRed, //! 红灯 33 | }; 34 | 35 | enum class EventId { 36 | kAny = 0, 37 | kTimeout, //! 时间到了 38 | }; 39 | 40 | void initSm() { 41 | using Event = tbox::flow::Event; 42 | 43 | auto do_enter_green = [this] (Event) { setGreenLightStatus(true); startTimer(20); }; 44 | auto do_exit_green = [this] (Event) { setGreenLightStatus(false); }; 45 | auto do_enter_yellow = [this] (Event) { setYellowLightStatus(true); startTimer(5); }; 46 | auto do_exit_yellow = [this] (Event) { setYellowLightStatus(false); }; 47 | auto do_enter_red = [this] (Event) { setRedLightStatus(true); startTimer(15); }; 48 | auto do_exit_red = [this] (Event) { setRedLightStatus(false); }; 49 | 50 | sm_.newState(StateId::kGreen, do_enter_green, do_exit_green, "Green"); 51 | sm_.newState(StateId::kYellow, do_enter_yellow, do_exit_yellow, "Yellow"); 52 | sm_.newState(StateId::kRed, do_enter_red, do_exit_red, "Red"); 53 | 54 | sm_.setInitState(StateId::kGreen); //! 如果不明确指定,那么第一个被创建的状态就是起始状态 55 | 56 | sm_.addRoute(StateId::kGreen, EventId::kTimeout, StateId::kYellow, nullptr, nullptr, "Timeout"); 57 | sm_.addRoute(StateId::kYellow, EventId::kTimeout, StateId::kRed, nullptr, nullptr, "Timeout"); 58 | sm_.addRoute(StateId::kRed, EventId::kTimeout, StateId::kGreen, nullptr, nullptr, "Timeout"); 59 | } 60 | 61 | void startTimer(int sec) { 62 | ctx().timer_pool()->cancel(timer_token_); 63 | timer_token_ = ctx().timer_pool()->doAfter( 64 | std::chrono::seconds(sec), 65 | [this] { sm_.run(EventId::kTimeout); } 66 | ); 67 | } 68 | 69 | void setGreenLightStatus(bool is_on) { 70 | LogPrintf(is_on ? LOG_LEVEL_IMPORTANT : LOG_LEVEL_INFO, "green light: %s", is_on ? "on" : "off"); 71 | } 72 | void setYellowLightStatus(bool is_on) { 73 | LogPrintf(is_on ? LOG_LEVEL_WARN: LOG_LEVEL_NOTICE, "yellow light: %s", is_on ? "on" : "off"); 74 | } 75 | void setRedLightStatus(bool is_on) { 76 | LogPrintf(is_on ? LOG_LEVEL_FATAL: LOG_LEVEL_ERROR, "red light: %s", is_on ? "on" : "off"); 77 | } 78 | 79 | private: 80 | tbox::flow::StateMachine sm_; 81 | tbox::eventx::TimerPool::TimerToken timer_token_; 82 | }; 83 | 84 | namespace tbox { 85 | namespace main { 86 | void RegisterApps(Module &apps, Context &ctx) { 87 | apps.add(new MyModule(ctx)); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /16-state-machine.md: -------------------------------------------------------------------------------- 1 | # 状态机 2 | 3 | ## 什么是状态机 4 | 状态机(State Machine)是一种行为设计模式,它允许对象在其内部状态发生变化时改变其行为。这种模式将状态封装成独立的类,并将动作委托给当前状态对象。 5 | 状态机在处理复杂的条件逻辑时特别有用,例如游戏开发、工作流引擎、编译器设计、网络协议实现等场景。 6 | 7 | 状态机的核心概念: 8 | - 状态(State):对象在其生命周期中可能处于的特定条件或模式。每个状态定义了对象在该状态下的行为。 9 | - 事件(Event):触发状态变化的外部或内部事件。 10 | - 跳转(Route):当特定事件发生时,状态机从一个状态转换到另一个状态的过程。 11 | - 动作(Action):状态转换过程中执行的操作,可以在进入状态、退出状态或转换期间执行。 12 | 13 | 参考资料: 14 | - [什么是状态机?](https://zhuanlan.zhihu.com/p/47434856) 15 | 16 | 在设计模式中有“状态机模式”,见网上的文章:《[状态机模式](https://mycroftcooper.github.io/2021/09/01/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-%E7%8A%B6%E6%80%81%E6%A8%A1%E5%BC%8F/)》。 17 | 由于cpp-tbox不是这么实现的,所以不再赘述。有兴趣的朋友可以自行查资料。 18 | 19 | 为什么作者本人不采用这种方式呢?因为本人觉得这种实现比较繁琐与拖沓。在实际的工程实际中,用这种方式实现状态机会让我们反反复复通过继承的方式定义很多状态类,不方便维护。在cpp-tbox里采用的是状态表的实现方式。 20 | 21 | 状态机的本质,就是先创建几个状态(State),再为这些状态添加遇到不同事件(Event)的跳转(Route)。当然,这些跳转并不同一发生事件就跳转的,有些也需要满足一定的条件。这可以用一张状态表进行描述,也是我们实现状态机的思想。 22 | 23 | 具体的代码实现见源码:[state\_machine.h](https://gitee.com/cpp-master/cpp-tbox/blob/master/modules/flow/state_machine.h) 与 [state\_machine.cpp](https://gitee.com/cpp-master/cpp-tbox/blob/master/modules/flow/state_machine.cpp) 24 | 25 | ## 实现个交通信号灯 26 | 接下来我们通过模拟红绿灯的状态,来向大家展示cpp-tbox中状态机的使用; 27 | 28 | ### 方案设计 29 | 为方便学习,我们抛开细节,简化一下红绿灯的功能: 30 | - 最开始是绿灯; 31 | - 绿灯亮20秒,转黄灯; 32 | - 黄灯亮5秒,转红灯; 33 | - 红灯亮15秒,转绿灯; 34 | 35 | 首先,我们一起罗列一下有哪些状态,以及进入与退出该状态的动作: 36 | | 状态 | 进入动作 | 退出动作 | 37 | |:----:|:----:|:----:| 38 | | 绿灯 | 开始计时20秒,亮绿灯 | 灭绿灯 | 39 | | 黄灯 | 开始计时5秒,亮黄灯 | 灭黄灯 | 40 | | 红灯 | 开始计时15秒,亮红灯 | 灭红灯 | 41 | 42 | 初始状态:绿灯 43 | 终止状态:无 44 | 45 | 接下来,我们罗列各个状态之间的跳转 46 | | 源状态 | 事件 | 目标状态 | 跳转条件 | 执行动作 | 47 | |:----:|:----:|:----:|:----:|:----:| 48 | | 绿灯 | 时间到 | 黄灯 | 无 | 无 | 49 | | 黄灯 | 时间到 | 红灯 | 无 | 无 | 50 | | 红灯 | 时间到 | 绿灯 | 无 | 无 | 51 | 52 | 至于计时,我们采用之前在《[定时器池](12-timer-pool.md)》中学到的`ctx().timer_pool()->doAfter(...)`; 53 | 54 | ### 代码实现 55 | 首先,我们需要包含状态机的头文件:`tbox/flow/state_machine.h`,如下: 56 | ![代码](images/058-traffic-light-code-1.png) 57 | 58 | 然后,在类中定义状态机对象`sm_`与后面定时要用到的`timer_token_`: 59 | ![定义状态机变量](images/059-traffic-light-code-2.png) 60 | 61 | 定义状态枚举类型与事件枚举类型: 62 | ![定义状态与事件枚举](images/060-traffic-light-code-3.png) 63 | 如上,我定义了三种状态:绿灯、黄灯、红灯。一定要注意:**状态枚举中的0是保留的,表示终止状态**。所以上面的代码中定义了`kTerm = 0`,预留了0值。这点大家一定要记住! 64 | 我们还定义了事件。目前只有一种事件,就是超时。一定要注意:**事件中的0也是保留的,表示任何事件**。正常我们定义的事件不能使用0。为此,我们定义了`kAny = 0`预留了它。 65 | 66 | 接下来,实现打开定时器与开关信号灯的函数: 67 | ![实时启动定时器与开关灯的函数](images/061-traffic-light-code-4.png) 68 | 我们先看`startTimer()`的实现: 69 | L62,为防止重复开定时器,在开定时器之前先取消之前创建的定时器。如果`timer_token_`为空,或者定时器不存在,也不会有什么操作; 70 | L63~66,这里使用定时器池创建了一个新的定时器,它将在`sec`秒后触发。执行的内容便是往状态机`sm_`中投喂`kTimerout`事件。 71 | 72 | L69~77,定义了`setXXXXLightStatus()`三个函数是分别操作绿、黄、红三种信号灯开关。这里为了演示效果,就直接令其打印日志即可。 73 | 74 | 重点了来了。接下来,我们要实现`initSm()`函数。它负责对状态机的初始化: 75 | ![initSm函数实现](images/062-traffic-light-code-5.png) 76 | L41,为了方便,将 `tbox::flow::Event` 重命名为 `Event`,将在L43~48中使用; 77 | L43~48,定义每种状态进入与退出时的动作。以绿灯状态为例,进入时要将绿灯打开,并启动20秒的定时器;退出则要关闭绿灯。黄灯、红灯一样的操作。 78 | L50~52,分别创建三种状态,并为每个状态指定进入与退出时的动作; 79 | L54,指定起始状态。这步不是必须的,如果不明确指定则创建的第一个状态就是起始状态; 80 | L56~58,添加状态之间的跳转。目前业务比较简单,每种状态都只在遇到`kTimeout`事件时跳转到下一个状态。 81 | 82 | 最后,在`onInit()`中调`initSm()`初始化状态机;在`onStart()`中调`sm_.start()`启动状态机;在`onStop()`中调`sm_.stop()`停止状态机: 83 | ![启动状态机](images/063-traffic-light-code-6.png) 84 | 85 | 完成! 86 | 87 | [示例源码](examples/19-traffic-light-sm/app_main.cpp) 88 | [示例工程目录](examples/19-traffic-light-sm) 89 | 90 | ### 执行效果 91 | 92 | 编译后,执行效果: 93 | ![执行效果](images/057-traffic-light-run.png) 94 | 可以看到: 95 | - 它是按绿、黄、红循环亮灯的; 96 | - 在最后程序退出的时候,正在亮着的红灯是会被关闭的; 97 | 98 | 达到预期效果! 99 | 100 | ## 总结 101 | 102 | 首先,要梳理状态机的需求。明确有哪些状态,哪些事件,状态之间的跳转。 103 | 然后,将需求翻译成代码,通过`newState()`创建状态,通过`addRoute()`添加状态之间的跳转; 104 | 最后,将事件通过`run(...)`喂给状态机; 105 | 106 | 注意点: 107 | - 0状态保留为终止状态,不要使用; 108 | - 1事件保留为任务事件,不要使用; 109 | - 不要在状态动作函数中再调`run(...)`函数,会失败。 110 | 用`ctx().loop().runInLoop([this] { sm_.run(XXXX); });` 包起来; 111 | 112 | ------- 113 | [[返回主页]](README.md) 114 | -------------------------------------------------------------------------------- /examples/50-coffee-machine/coffee/app.cpp: -------------------------------------------------------------------------------- 1 | #include "app.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace coffee { 13 | 14 | using SM = tbox::flow::StateMachine; 15 | using Event = tbox::flow::Event; 16 | 17 | App::App(tbox::main::Context &ctx) 18 | : tbox::main::Module("coffee", ctx) 19 | , timer_(ctx.loop()->newTimerEvent()) 20 | { } 21 | 22 | App::~App() { 23 | CHECK_DELETE_RESET_OBJ(timer_); 24 | } 25 | 26 | bool App::onInit(const tbox::Json &js) { 27 | initSm(); 28 | initShell(); 29 | 30 | timer_->initialize(std::chrono::seconds(1), tbox::event::Event::Mode::kPersist); 31 | timer_->setCallback([this] { onTimerTick(); }); 32 | 33 | return true; 34 | } 35 | 36 | bool App::onStart() { 37 | sm_.start(); 38 | return true; 39 | } 40 | 41 | void App::onStop() { 42 | sm_.stop(); 43 | } 44 | 45 | void App::onCleanup() { } 46 | 47 | void App::initSm() { 48 | auto do_enter_check = [this] (Event) { 49 | LogDbg("start check"); 50 | remain_sec_ = 3; 51 | timer_->enable(); 52 | }; 53 | auto do_exit_check = [this] (Event) { 54 | timer_->disable(); 55 | }; 56 | 57 | auto do_enter_working = [this] (Event) { 58 | LogDbg("start make"); 59 | remain_sec_ = 10; 60 | timer_->enable(); 61 | }; 62 | auto do_exit_working = [this] (Event) { 63 | timer_->disable(); 64 | }; 65 | 66 | //! 创建状态 67 | sm_.newState(StateId::kCheck, do_enter_check, nullptr, "Check"); 68 | sm_.newState(StateId::kIdle, nullptr, nullptr, "Idle"); 69 | sm_.newState(StateId::kWorking, do_enter_working, do_exit_working, "Working"); 70 | sm_.newState(StateId::kFault, nullptr, nullptr, "Fault"); 71 | 72 | //! 建立状态间的跳转链路 73 | sm_.addRoute(StateId::kCheck, EventId::kSucc, StateId::kIdle, nullptr, nullptr, "Succ"); 74 | sm_.addRoute(StateId::kCheck, EventId::kFail, StateId::kFault, nullptr, nullptr, "Fault"); 75 | 76 | sm_.addRoute(StateId::kIdle, EventId::kMake, StateId::kWorking, nullptr, nullptr, "Make"); 77 | 78 | sm_.addRoute(StateId::kWorking, EventId::kSucc, StateId::kIdle, nullptr, nullptr, "Succ"); 79 | sm_.addRoute(StateId::kWorking, EventId::kFail, StateId::kFault, nullptr, nullptr, "Fail"); 80 | sm_.addRoute(StateId::kWorking, EventId::kStop, StateId::kIdle, nullptr, nullptr, "Stop"); 81 | 82 | sm_.addRoute(StateId::kFault, EventId::kCheck, StateId::kCheck, nullptr, nullptr, "Check"); 83 | 84 | //! 注册状态跳转打印 85 | sm_.setStateChangedCallback( 86 | [this] (SM::StateID from, SM::StateID to, Event event) { 87 | LogDbg("state changed: %s --(%s)-> %s", 88 | ToString(static_cast(from)).c_str(), 89 | ToString(static_cast(event.id)).c_str(), 90 | ToString(static_cast(to)).c_str() 91 | ); 92 | } 93 | ); 94 | } 95 | 96 | bool RamdomFail() { 97 | std::default_random_engine e; 98 | std::uniform_int_distribution u(0, 100); 99 | 100 | e.seed(tbox::util::GetUtcMilliseconds()); 101 | return u(e) < 10; 102 | } 103 | 104 | void App::onTimerTick() { 105 | if (remain_sec_ > 0) { 106 | --remain_sec_; 107 | if (RamdomFail()) { 108 | sm_.run(EventId::kFail); 109 | } 110 | 111 | } else { 112 | sm_.run(EventId::kSucc); 113 | } 114 | } 115 | 116 | void App::initShell() { 117 | auto &shell = *ctx().terminal(); 118 | auto dir_node = shell.createDirNode(); 119 | 120 | shell.mountNode(shell.rootNode(), dir_node, name()); 121 | 122 | tbox::terminal::AddFuncNode(shell, dir_node, "make", [this] { sm_.run(EventId::kMake); }); 123 | tbox::terminal::AddFuncNode(shell, dir_node, "check", [this] { sm_.run(EventId::kCheck); }); 124 | tbox::terminal::AddFuncNode(shell, dir_node, "stop", [this] { sm_.run(EventId::kStop); }); 125 | } 126 | 127 | std::string App::ToString(StateId id) { 128 | const char* tbl[] = { 129 | "Term", 130 | "Check", 131 | "Idle", 132 | "Working", 133 | "Fault" 134 | }; 135 | 136 | static_assert(NUMBER_OF_ARRAY(tbl) == static_cast(StateId::kMax)); 137 | auto index = static_cast(id); 138 | if (index >= 0 && index < NUMBER_OF_ARRAY(tbl)) 139 | return tbl[index]; 140 | 141 | return "unknown " + std::to_string(index); 142 | } 143 | 144 | std::string App::ToString(EventId id) { 145 | const char* tbl[] = { 146 | "Any", 147 | "Make", 148 | "Succ", 149 | "Fail", 150 | "Stop", 151 | "Check", 152 | }; 153 | 154 | static_assert(NUMBER_OF_ARRAY(tbl) == static_cast(EventId::kMax)); 155 | auto index = static_cast(id); 156 | if (index >= 0 && index < NUMBER_OF_ARRAY(tbl)) 157 | return tbl[index]; 158 | 159 | return "unknown " + std::to_string(index); 160 | 161 | } 162 | 163 | 164 | } 165 | -------------------------------------------------------------------------------- /07-parameters.md: -------------------------------------------------------------------------------- 1 | # 配置系统 2 | 3 | 配置可以用于不修改代码的情况下改变程序的行为。常规的配置传入有两种方式:(1)环境变量;(2)命令参数。而命令参数是用得最多的。 4 | 在常规的开发中,我们可能这样传入命令参数的: 5 | ```c 6 | int main(int argc, char **argv) { 7 | if (argc >= 2) { 8 | ... /// 解析 argc 与 argv 9 | } 10 | } 11 | ``` 12 | 如果直接这么写会很麻烦,通常我们会使用 `getopt()` 或是 `getopt_long()` 来辅助解析。不过这还是会比较繁琐。每个要传入的参数都得写解析代码,而每个程序要传入的参数还各不相同。这样一来,每开发一个程序都要重写一次参数解析模块,每添加或删除参数也要修改,也比较繁琐。 13 | 好在我们的tbox.main框架中内置了一套开放性的配置系统。它允许程序传入任何类型的参数(字串、数值、数组、对象),而且还支持将配置以文件的形式进行导入,让配置非常简单。本节我们将通过一系列的例子学习它的使用。 14 | 15 | ## 实现自定义配置 16 | 在上面的http示例中,我们在 `onInit()` 中对http_server_对象进行了初始化,令其固定绑定 0.0.0.0 地址与12345端口,且backlog指定为2。将这些初始化参数写死在程序里不是一个好的主意。因为在真实的项目中,这些常常会变更。那能不能在运行的时候,通过配置进行指定呢?当然可以的。 17 | 接下来,我们将使用配置系统,将绑定地址与端口,还有backlog通过配置传入。 18 | 19 | 对 app_main.cpp 进行如下修改: 20 | ![parametes示例代码](images/026-parameters-code.png) 21 | 22 | (1) 由于接下来要对 JSON 进行操作,需要包含JSON相关的头文件; 23 | (2) 实现 `onFillDefaultConfig()` 函数。这个函数是用于让用户指定本模块运行的默认配置。可以看到,我们添加了两个字段"bind_addr"与"backlog"。这两个字段,在`onInit()` 会被使用到; 24 | (3) 在 `onInit()` 初始化函数中从 js 对象中提取出"bind_addr"与"backlog"; 25 | (4) 进行使用; 26 | 27 | 编译后,不带参数执行。发现与上面节的没有什么变化,依旧绑定在 0.0.0.0:12345 端口上。 28 | 接下来,我们给这个程序带上参数进行执行:`./demo -s 'my_http.bind_addr="0.0.0.0:44444"'`,执行效果: 29 | ![parametes示例执行效果](images/027-parameter-result-1.png) 30 | 可见,HTTP服务的绑定端口变了,由之前的 12345 变成了 44444。这就说明我们设置的参数生效了。 31 | 32 | 接下来,我们来了解一下 tbox.main 框架配置系统的原理。 33 | 34 | ## 原理介绍 35 | 由于不同的应用的配置是不同的,tbox.main 框架的配置系统摒弃了传统linux命令的传参方式,采用了JSON的方式。实现过程: 36 | 37 | 在模块开始运行之前,tbox.main框架会创建一个叫 `js_conf` 的 JSON 对象。 38 | ```c++ 39 | Json js_conf; 40 | ``` 41 | 42 | 然后为每个要运行的模块,以模块名在 `js_conf` 下创建一个子JSON `js_module`。 43 | 并调用该模块的 `fillDefaultConfig(Json &js)` 方法,让模块根据自身功能的需要对 `js_module` 进行填充。 44 | ```c++ 45 | for (auto &m : modules) { 46 | Json &js_module = js_conf[m.name()]; 47 | m.fillDefaultConfig(js_module); 48 | } 49 | ``` 50 | 51 | 然后,开始对参数进行逐一解析。 52 | 53 | 如遇到 `-s 'a.b.c=xxxx'` 的参数,则将参数转换成这样的JSON: 54 | ```json 55 | "a":{ 56 | "b":{ 57 | "c":xxxx 58 | } 59 | } 60 | ``` 61 | 将它合并到 `js_conf` 中。 62 | ```c++ 63 | Json js_tmp = Parse(kv_string); 64 | js_conf.merge_patch(js_tmp); 65 | ``` 66 | 67 | 如遇到 `-c jsonfile.js` 的参数,则从 jsonfile.js 文件中导入JSON对象。 68 | 然后,也合并到 `js_conf` 中。 69 | ```c++ 70 | Json js_tmp = Load(config_filename); 71 | js_conf.merge_patch(js_tmp); 72 | ``` 73 | 74 | 在解析完所有的参数后,就得到了一个能过参数修订后的 `js_conf` JSON对象。 75 | 76 | 在对模块进行初始化的阶段,框架在初始化某个模块时,就会从js_conf取出对应模块名的js_module,在调用模块的`onInit()`时作为参数传入: 77 | ```c++ 78 | for (auto &m : modules) { 79 | const Json &js_module = js_conf[m.name()]; 80 | m.onInit(js_module); 81 | } 82 | ``` 83 | 于是,我们的各自模块在 `onInit()` 虚函数中,就得到了一个 JSON 对象的引用。通过获取其中的字段,我们就可以从中获得该模块的配置。 84 | 85 | 上面的描述为了方便理解,本人刻意简化了其中的细节。具体实现,请阅读源码: 86 | 87 | - [module/main/args.cpp](https://gitee.com/cpp-master/cpp-tbox/blob/master/modules/main/args.cpp) 88 | - [module/main/run_in_frontend.cpp](https://gitee.com/cpp-master/cpp-tbox/blob/master/modules/main/run_in_frontend.cpp) 89 | 90 | ## 查看help 91 | 我可以通过执行 `./demo --help` 或 `./demo -h` 查看参数说明: 92 | ![help](images/028-help.png) 93 | 94 | - `-s 'KEY=VALUE'`,指定参数中某个字段的值; 95 | - `-c FILE`,指定要导入参数的JSON文件; 96 | - `-p`,打印配置数据,用于检查配置参数; 97 | - `-n`,不运行,通常配置 `-p` 使用; 98 | 99 | 从上可以得知:如果我们想给名为"my_http"的模块指定参数,只需要使用 -s 或 -c 在`js_conf`中构建出以下的内容: 100 | ```json 101 | { 102 | "my_http":{ ... } 103 | } 104 | ``` 105 | 106 | 明白了其中的原理并了解了参数之后,我们就能很好地理解上面示例的参数内容了。 107 | 参数 `-s 'my_http.bind_addr="0.0.0.0:44444"'` 表示:设置 my_http.bind_addr 的参数值为 "0.0.0.0:44444"。于是,在 `MyModule::onInit()` 函数被执行时,所传入的js对象中存在"bind_addr"字段。它的值就是我们在执行参数中指定的 "0.0.0.0:44444"。 108 | 那为什么是 "my_http.bind_addr" 而不是 "abc.bind_addr"?因为在 L10 构造中,指定了 MyModule 的名称为 "my_http"。当然,我们可以取其它名称。 109 | 110 | ## 打印当前的配置 111 | 我们还可以通过在参数中 `-p`,让程序在执行前打印当前的配置。如果是仅仅看配置不运行程序,则直接加 `-pn`。 112 | 尝试执行命令:`./demo -pn` 看到效果: 113 | ![print-json](images/029-with-pn.png) 114 | 这些就是执行 `./demo` 什么参数都不带的运行参数真实的内容。红色虚框标记处为 `my_http` 模块的参数。之所以他们存在,是因为我们为该模块实现了 `onFillDefaultConfig()` 方法,并在其中指定了默认参数。 115 | 当然,我们还看到其它的参数项,如 `thread_pool`, `log`,这些我们在后面其它章节再讲述。 116 | 117 | 就这样,我们常常使用 `-pn` 参数来检查我们的配置内容。如果有误,我们可以在正式运行之前对配置进行修正。修正之后,再去除 `-pn` 正式运行。 118 | 后面的例子,为了更直观地看参数对配置的修改效果,在执行时都带上 `-pn` 参数。 119 | 120 | 我们尝试执行命令:`./demo -s 'my_http.backlog=64' -pn` 再看看效果: 121 | ![print-json](images/030-with-s-pn.png) 122 | 可见,`backlog`的值发生了变更。如果不再使用参数`-pn`那么我们可以看到它真正的运行效果: 123 | ![](images/031-backlog.png) 124 | 可见,`backlog`的值在运行中生效了。 125 | 126 | ## 一个`-s`设置多个参数 127 | 如果每设置一个参数都使用一个`-s`会使得参数很长,`-s`的数量很多。 128 | 也可以多个字段一起传,例如:`./demo -s 'my_http={"bind_addr":"0.0.0.0:44444","backlog":88}' -pn` 129 | ![](images/032-one-set-more-params.png) 130 | 131 | ## 导入配置文件 132 | 如果每次程序启动时都带这么长串的参数,是很繁琐的,也不直观。可以`-s`,我们可以采用`-c`导入配置文件。 133 | 将参数写入到配置文件中,如 config.json 134 | ```json 135 | { 136 | "my_http":{ 137 | "bind_addr":"0.0.0.0:22222", 138 | "backlog":99 139 | } 140 | } 141 | ``` 142 | 然后在运行的时候使用 '-c config.json' 从配置文件中直接导入。 143 | 执行命令:`./demo -c config.json -pn`,看看效果: 144 | ![](images/033-use-config.png) 145 | 146 | 这样就可以很方便地一致性导入大批的参数,简洁,也简单。 147 | 通常,对于固定不变的参数,我们使用导入配置文件的形式加载。对于临时需要调整的,就采用`-s 'a.b.c=xxx'`的形式。 148 | 149 | ## 总结 150 | 151 | - 使用 `-c` 导入配置文件; 152 | - 使用 `-s` 修改配置项; 153 | - 使用 `-pn` 打印配置项; 154 | 155 | ------- 156 | [[返回主页]](README.md) 157 | -------------------------------------------------------------------------------- /08-terminal.md: -------------------------------------------------------------------------------- 1 | # 命令终端 2 | 3 | 我们的程序是持续运行着的。如果在它运行过程中出了点故障,通常只能通过日志分析大概现象。如果日志打印不充分,那么很难对问题进行精确的判定。如果有一个什么办法,可以在不中断程序的前提下,让程序把内部的成员变量通通都打印出来该多好啊。 4 | 除了上面的场景,还有其它的痛点: 5 | - 希望在程序运行过程中临时调整日志对某个模块的打印等级; 6 | - 希望在程序运行过程中,让程序模拟外部的某个事件,进行调试; 7 | 8 | 总结下来,我们需要有一个方法,让程序在运行过程中执行某个特定的函数,并将执行的结果输出来。 9 | 为此,我们想过很多办法: 10 | 方法一:注册信号,让程序执行某个函数。这种方法简单,但一个信号只能指定执行一个函数; 11 | 方法二:创建一个TCP或UDP的服务,接收字串并解释,执行命令对应的函数,并将结果直接返回; 12 | 方法三:创建HTTP服务,接收POST请求,执行某个函数,将结果以回复的形式返回; 13 | 14 | 在没有 terminal 之前,我使用最多的是方法二。每写一个程序,我都得建一张 cmd --> function 的表。一个命令对应一个函数。每当TCP接收到字串后,就从字串中提取出cmd与参数。再通过cmd从表中找出对应的 function,去执行对应的函数。这样的交互并不那么友好,也不具备很强的通用性。于是,我参考了 telnet 的交互协议,开发了 terminal 模块,并集成到了 tbox.main 框架中。从此 tbox.main 具备了调试终端的功能。 15 | 效果如下: 16 | ![](images/043-terminal-show.png) 17 | 18 | ## 使能终端 19 | 默认情况下,命令终端并未启用,需要我们通过配置将其打开:`./demo -s 'telnetd.bind="0.0.0.0:10000"'` 20 | 参数 `-s 'telnetd.bind="0.0.0.0:10000"'` 的意思是,将telnetd服务socket的绑定地址指定为 IP:0.0.0.0,端口:1000。于是 telnetd 服务就启用了: 21 | ![](images/034-enable-telnetd.png) 22 | 如上,我们在另一个窗口上执行 `telnet localhost 10000` 就能登陆上去。 23 | 如果机器上没有 telnet 命令,还可以使用 nc 命令替代。主要功能一样,仅仅在体验上没有上下切换历史命令的功能罢了。 24 | ![](images/035-use-nc-login-telnetd.png) 25 | 26 | ## 常规操作 27 | 在 terminal 中有几个内置的命令,执行 help 命令可以看到: 28 | ![](images/036-terminal-help.png) 29 | 这些指令的功能与Linux对应的命令类似。最常用的有:ls, cd, tree, exit。大家可以逐一尝试执行一下。 30 | ![](images/037-use-terminal.png) 31 | 它非常像Linux的Shell,符合了Linux的开发者的习惯。 32 | 33 | ## 实现自定义命令 34 | 接下来,我们尝试在模块中添加自己的命令。 35 | 还是回到之前 HTTP 的示例上来。我们现在想实现HTTP返回的内容可通过终端进行查看与修改的功能。 36 | 具体效果为:在终端的根目录下创建 my_http 目录,再在这个子目录下创建两个命令:print_content 与 set_content,功能顾名思义。像这样: 37 | ``` 38 | `-- my_http 39 | |-- print_content 40 | `-- set_content 41 | ``` 42 | 43 | OK,我们将http的示例copy过来,在其原有基础上进行修改: 44 | ![](images/040-http-server-terminal-code.png) 45 | 46 | (1) 引入与终端相关的头文件; 47 | (2) 添加 `content_` 成员变量; 48 | (3) 修改 http 的响应函数,使之将 `content_` 作为回复内容; 49 | (4) 调用 `initShell()` 函数,对终端进行初始化; 50 | (5) 实现 `initShell()` 函数的内容; 51 | 52 | 在 `initShell()` 中进行了以下步骤: 53 | (1) 创建一个DirNode,并将它挂载到 "/" 目录上,命名为 "my_http"; 54 | (2) 创建一个FuncNode,并将它挂载到 "/my_http" 目录下,命名为 "print_content"; 55 | (3) 创建一个FuncNode,并将它挂载到 "/my_http" 目录下,命名为 "set_content"; 56 | 57 | 其中在执行 `createFuncNode()` 时,需要传入一个函数对象。该函数对像需要传入两个参数: 58 | ![](images/041-terminal-args.png) 59 | 60 | - `const Session &s`,会话对象,每个连接都是独立的。我们可以使用它的 `send(const std::string &txt)` 方法可以向终端回复内容; 61 | - `const Args &a`,参数列表,本质上就是 `std::vector`。需要说明的是:`a[0]` 永远是命令本身,后之才是参数内容。 62 | 63 | [示例工程目录](examples/12-terminal) 64 | 65 | 编译后运行,再使用 telnet 登陆上去进行操作: 66 | ![](images/038-http-server-terminal.png) 67 | 使用浏览器也可以访问到: 68 | ![](images/039-http-server-terminal-2.png) 69 | 70 | ## 内置命令介绍 71 | 为了方便使用,tbox.main 已内置了一些常用的命令,方便使用者在运行时进行操控。 72 | 我们在根目录下执行 `tree` 命令,终端会打印命令的树形结构。下面为本人在打印内容的基础上加了部分备注的内容: 73 | ``` 74 | # tree 75 | |-- ctx # ctx 相关 76 | | |-- loop # 主事件循环相关 77 | | | |-- stat # 统计 78 | | | | |-- print # 打印统计 79 | | | | `-- reset # 重新开始统计 80 | | | `-- wl # 警告水位线(用于检查事件循环中是否存在阻塞) 81 | | | |-- event_cb_cost # 事件回调耗时阈值 82 | | | |-- loop_cost # Loop处理耗时阈值 83 | | | |-- run_cb_cost # 执行runNext(),runInLoop()与run()耗时阈值 84 | | | |-- run_in_loop_delay # runInLoop() 执行延迟阈值 85 | | | |-- run_in_loop_queue_size # runInLoop() 最大排队等待长度 86 | | | |-- run_next_delay # runNext() 执行延迟阈值 87 | | | |-- run_next_queue_size # runNext() 最大排队等待长度 88 | | | |-- timer_delay # 定时器延迟阈值 89 | | | `-- wake_delay # 唤配延迟阈值 90 | | |-- running_time # 打印进程运行时长 91 | | |-- start_time # 打印进程启动时间点 92 | | `-- thread_pool # 线程池相关 93 | | `-- snapshot # 状态快照 94 | |-- info # 信息打印 95 | | |-- app_ver # 打印App版本信息,根据 GetAppVersion() 96 | | |-- build_time # 打印编译时间,根据 GetAppBuildTime() 97 | | |-- tbox_ver # 打印Tbox版本信息,根据 GetTboxVersion() 98 | | `-- what # 打印App描述信息,根据 GetAppDescribe() 99 | `-- log # 日志输出相关 100 | |-- file # 打印到文件的日志 101 | | |-- enable # 是否启用 102 | | |-- enable_color # 是否根据日志等级渲染颜色 103 | | |-- enable_sync # 是否强制写入到存储器(部分文件系统会在内核中缓存,导致突然断电出现日志丢失情况) 104 | | |-- set_level # 指定模块的输出等级 105 | | |-- set_max_size # 指定日志文件最大大小 106 | | |-- set_path # 指定日志文件的创建路径 107 | | |-- set_prefix # 指定日志文件的前缀 108 | | `-- unset_level # 与set_level对应,删除指定模块的日志等级 109 | |-- stdout # 打印到标准终端的日志 110 | | |-- enable # 是否启用 111 | | |-- enable_color # 是否根据日志等级渲染颜色 112 | | |-- set_level # 指定模块的输出等级 113 | | `-- unset_level # 与set_level对应,删除指定模块的日志等级 114 | `-- syslog # 打印到syslog的日志 115 | |-- enable # 是否启用 116 | |-- enable_color # 是否根据日志等级渲染颜色 117 | |-- set_level # 指定模块的输出等级 118 | `-- unset_level # 与set_level对应,删除指定模块的日志等级 119 | ``` 120 | 121 | ## RPC接口 122 | 有时候,我们希望能在脚本中执行上面的这些终端命令,比如:自动化测试。 123 | 为些,我还实现了 RPC 的功能。该功能可称为去除了打印欢迎词与提示符的功能,仅仅实现了解析命令,返回执行结果的功能。 124 | 125 | 默认该功能未开启。与telnetd一样,可以通过配置开始。只需要在参数中加 `-s 'tcp_rpc.bind="xxxx"'` 即可,不需要对代码做任何的修改。 126 | 我们尝试开启上面 http 服务的 RPC 功能。执行命令:`./demo -s 'tcp_rpc.bind="/tmp/myhttp_rpc.sock"'` 127 | 128 | ![](images/042-rpc.png) 129 | ``` 130 | echo "/my_http/print_content; quit" | nc -U /tmp/myhttp_rpc.sock 131 | echo "cd ctx; tree; quit" | nc -U /tmp/myhttp_rpc.sock 132 | ``` 133 | 134 | 需要注意的是: 135 | - 命令之间以分号(;)进行分隔; 136 | - 所有的命令都需要以"quit"结束,否则socket不会断开; 137 | - 使用 echo 命令时,不要加 `-n`。 138 | 139 | 有了这个工具,就可以让脚本替我们输命令了。这个工具解放了我们的双手。 140 | 141 | ------- 142 | [[返回主页]](README.md) 143 | --------------------------------------------------------------------------------