diff --git a/00-first-demo.md b/00-first-demo.md index 45c01ba..f57b812 100644 --- a/00-first-demo.md +++ b/00-first-demo.md @@ -6,13 +6,13 @@ TARGET:=demo CXXFLAGS:=-I$(HOME)/.tbox/include LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic -LIBS:=-ltbox_main -ltbox_terminal -ltbox_network -ltbox_eventx -ltbox_event -ltbox_log -ltbox_util -ltbox_base -lpthread -ldl +LIBS:=-ltbox_main -ltbox_coroutine -ltbox_trace -ltbox_terminal -ltbox_network -ltbox_eventx -ltbox_event -ltbox_log -ltbox_util -ltbox_base -lpthread -ldl $(TARGET): g++ -o $(TARGET) $(LDFLAGS) $(LIBS) ``` -[示例工程目录](00-first-demo) +[示例工程目录](examples/00-first-demo) 然后执行 `make && ./demo`,效果: ![执行效果](images/000-first-demo.png) diff --git a/01-first-module.md b/01-first-module.md index 629aa24..2ccf4f9 100644 --- a/01-first-module.md +++ b/01-first-module.md @@ -24,9 +24,9 @@ void RegisterApps(Module &apps, Context &ctx) { } ``` 然后再修改 Makefile,将 app\_main.cpp 加入到源文件中。 -见:[Makefile](01-first-module/Makefile) +见:[Makefile](examples/01-first-module/Makefile) -[示例工程目录](01-first-module) +[示例工程目录](examples/01-first-module) 编译执行:`make && ./demo`,运行结果: ![运行效果图](images/002-your-first-module-1.png) @@ -35,16 +35,14 @@ void RegisterApps(Module &apps, Context &ctx) { 但是,单从日志上看,我们并不能看出我们写的 MyModule 有真的运行起来。 接下来,我们再往 `MyModule` 中添加自定义的功能。让它在运行的过程中打印一点日志。 -[示例工程目录](02-first-module) - ```c++ class MyModule : public tbox::main::Module { public: explicit MyModule(tbox::main::Context &ctx) : tbox::main::Module("my", ctx) { } - virtual ~MyModule() { } - virtual bool onInit() override { LogTag(); } - virtual bool onStart() override { LogTag(); } + public: + virtual bool onInit(const tbox::Json &js) override { LogTag(); return true; } + virtual bool onStart() override { LogTag(); return true; } virtual void onStop() override { LogTag(); } virtual void onCleanup() override { LogTag(); } }; @@ -54,7 +52,9 @@ class MyModule : public tbox::main::Module { 为了能使用`LogTag()`日志打印函数,我们还需要添加`#include `。 完成之后执行 `make`。在编译的时侯,我们看到了一条编译警告: ![没有指定LOG\_MODULE\_ID警告](images/004-compile-warn.png) -它是在说我们程序没有指定日志的模块名。这仅是一条警告,我们可以忽略它。不过,我建议你在 Makefile 的`CXXFLAGS`定义中添加`-DLOG_MODULE_ID='"demo"'` 进行定义。 +它是在说我们程序没有指定日志的模块名。这仅是一条警告,我们可以忽略它。不过,我建议你在 Makefile 的`CXXFLAGS`定义中添加`-DMODULE_ID='"demo"'` 进行定义。 + +[示例工程目录](examples/02-add-log-tag) 编译后执行,然后按 ctrl+c 退出程序,完整的日志打印效果: ![运行效果图](images/003-your-first-module-with-log.png) diff --git a/01-first-module/Makefile b/01-first-module/Makefile deleted file mode 100644 index dde5755..0000000 --- a/01-first-module/Makefile +++ /dev/null @@ -1,19 +0,0 @@ -TARGET:=demo - -OBJECTS:=app_main.o - -CXXFLAGS:=-I$(HOME)/.tbox/include -LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic -LIBS:=\ - -ltbox_main \ - -ltbox_terminal \ - -ltbox_network \ - -ltbox_eventx \ - -ltbox_event \ - -ltbox_log \ - -ltbox_util \ - -ltbox_base \ - -lpthread -ldl - -$(TARGET): $(OBJECTS) - g++ -o $@ $^ $(LDFLAGS) $(LIBS) diff --git a/02-add-log-tag.md b/02-add-log-tag.md index c215cc1..c0f87a8 100644 --- a/02-add-log-tag.md +++ b/02-add-log-tag.md @@ -3,18 +3,19 @@ 调试日志是程序中一个比较重要的一部分。通常,我们在开发程序的时候,会直接使用 `printf()` 或 `std::cout`,`std::cerr` 在终端上打印日志。但这样打印,有很多不足:1)面日志格式混乱;2)能提供的调试信息不够充分;3)输出的方式太过单一;4)没有日志等级筛选功能。 因此,我们会去寻找开源的日志系统库,如:spdlog, glog, log4cxx,来满足日志打印需求。 -好在,当你使用 tbox.main 框架时,你根本就不需要为日志打印而发愁,因为它自带日志打印系统。你直接用就可以了。其它的不需要你关心。 +好在,当你使用 tbox.main 框架时,你根本就不需要为日志打印而发愁,因为它自带日志打印系统。你直接用就可以了,其它的不需要你关心。 日志等级有: |值|等级|名称|显示颜色| |:-|:-|:-|:-| -|0|FATAL|代码错误|暗红| +|0|FATAL|代码错误|红底黑字| |1|ERROR|错误|红| -|2|WARN|警告|黄| -|3|NOTICE|注意|淡黄| -|4|INFO|信息|绿| -|5|DEBUG|调试|淡蓝| -|6|TRACE|跟踪|紫| +|2|WARN|警告|黄底黑字| +|3|NOTICE|注意|黄| +|4|IMPORTANT|重要|绿底黑字| +|5|INFO|信息|绿| +|6|DEBUG|调试|淡蓝| +|7|TRACE|跟踪|紫| 日志打印函数有: |函数|等级|用途| @@ -23,8 +24,9 @@ |`LogErr(fmt,...)`|ERROR|打印导致业务完全不可用的严重错误。区别于FATAL,指的是非程序级错误,比如配置文件打不开| |`LogWarn(fmt,...)`|WARN|打印影响部分功能的错误。区别于ERROR,这种错误不整响主要功能| |`LogNotice(fmt,...)`|NOTICE|打印外部因素引起的轻微异常,这种异常不会影响功能,但需要注意。如对方的协议格式错误、版本不一致| -|`LogInfo(fmt,...)`|INFO|打印与外部交互的信息,用于鉴别问题是外部的,还是内部的| -|`LogDbg(fmt,...)`|DEBUG|打印内部模块之间的信息,用于鉴别问题是属于内部的哪个模块的| +|`LogImportant(fmt,...)`|IMPORTANT|打印与外界交互中非常重要的信息,尽量少用,以突显其稀有性| +|`LogInfo(fmt,...)`|INFO|打印与外界交互的普通信息,用于鉴别问题是外部的,还是内部的| +|`LogDbg(fmt,...)`|DEBUG|打印内部模块之间的信息,用于鉴别问题是属于内部的哪个模块的,以及处理方式| |`LogTrace(fmt,...)`|TRACE|打印临时查问题所需的日志信息| |`LogUndo()`|NOTICE|标记有未实现的功能,通用创建一个空函数时,就会放置一个LogUndo()| |`LogTag()`|TRACE|用于打印运行标记,观察程序有没有运行过标记位置| @@ -36,7 +38,7 @@ 下面,我们来实际操作一下,在MyModule的onInit()尝试所有的日志打印函数: ![](images/012-log-print-code.png) -[示例工程目录](06-log-print/) +[示例工程目录](examples/06-log-print/) 编译执行效果: ![日志打印效果](images/011-log-print.png) diff --git a/04-timer-event.md b/04-timer-event.md index b3243f0..4c12550 100644 --- a/04-timer-event.md +++ b/04-timer-event.md @@ -22,7 +22,7 @@ (8) 在 `onStart()` 中启动定时器; (9) 在 `onStop()` 中停止定时器; -[示例工程目录](07-timer-event) +[示例工程目录](examples/07-timer-event) 编译后,执行效果: ![](images/015-timer-result.png) diff --git a/05-fd-event.md b/05-fd-event.md index bcf2e93..d0453b0 100644 --- a/05-fd-event.md +++ b/05-fd-event.md @@ -12,7 +12,7 @@ ![代码](images/016-fdevent-code.png) 在(9)处定义了接收到stdin输入的处理过程。首先,检查一下是不是可读事件,如果是才处理。然后读0文件描述符,即stdin。将数据读到`buff`缓冲中。最后,根据输入的内容进行处理。 -[示例工程目录](08-fd-event) +[示例工程目录](examples/08-fd-event) 编译执行效果: ![结果](images/017-fdevent-result.png) diff --git a/06-http-server.md b/06-http-server.md index 97e3700..43f94b5 100644 --- a/06-http-server.md +++ b/06-http-server.md @@ -12,7 +12,7 @@ 为了演示,我们写一个非常简单的http服务程序,当我们访问它的时候,显示hello页面。 ![http代码](images/023-tiny-http-code.png) -[示例程序目录](10-tiny-http-server) +[示例程序目录](examples/10-tiny-http-server) 编译执行,使用 curl 测试,结果如下: ![http结果](images/024-tiny-http-result.png) diff --git a/08-fd-event/Makefile b/08-fd-event/Makefile deleted file mode 100644 index 15b10e8..0000000 --- a/08-fd-event/Makefile +++ /dev/null @@ -1,22 +0,0 @@ -TARGET:=demo - -OBJECTS:=app_main.o - -CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' -LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic -LIBS:=\ - -ltbox_main \ - -ltbox_terminal \ - -ltbox_network \ - -ltbox_eventx \ - -ltbox_event \ - -ltbox_log \ - -ltbox_util \ - -ltbox_base \ - -lpthread -ldl - -$(TARGET): $(OBJECTS) - g++ -o $@ $^ $(LDFLAGS) $(LIBS) - -clean: - rm *.o diff --git a/08-terminal.md b/08-terminal.md index f3d9345..10890e6 100644 --- a/08-terminal.md +++ b/08-terminal.md @@ -13,7 +13,7 @@ 在没有 terminal 之前,我使用最多的是方法二。每写一个程序,我都得建一张 cmd --> function 的表。一个命令对应一个函数。每当TCP接收到字串后,就从字串中提取出cmd与参数。再通过cmd从表中找出对应的 function,去执行对应的函数。这样的交互并不那么友好,也不具备很强的通用性。于是,我参考了 telnet 的交互协议,开发了 terminal 模块,并集成到了 tbox.main 框架中。从此 tbox.main 具备了调试终端的功能。 效果如下: -![](images/034-terminal-show.gif) +![](images/043-terminal-show.png) ## 使能终端 默认情况下,命令终端并未启用,需要我们通过配置将其打开:`./demo -s 'telnetd.bind="0.0.0.0:10000"'` @@ -41,7 +41,7 @@ ``` OK,我们将http的示例copy过来,在其原有基础上进行修改: -![](040-http-server-terminal-code.png) +![](images/040-http-server-terminal-code.png) (1) 引入与终端相关的头文件; (2) 添加 `content_` 成员变量; @@ -60,10 +60,12 @@ OK,我们将http的示例copy过来,在其原有基础上进行修改: - `const Session &s`,会话对象,每个连接都是独立的。我们可以使用它的 `send(const std::string &txt)` 方法可以向终端回复内容; - `const Args &a`,参数列表,本质上就是 `std::vector`。需要说明的是:`a[0]` 永远是命令本身,后之才是参数内容。 +[示例工程目录](examples/12-terminal) + 编译后运行,再使用 telnet 登陆上去进行操作: -![](038-http-server-terminal.png) +![](images/038-http-server-terminal.png) 使用浏览器也可以访问到: -![](039-http-server-terminal-2.png) +![](images/039-http-server-terminal-2.png) ## 内置命令介绍 为了方便使用,tbox.main 已内置了一些常用的命令,方便使用者在运行时进行操控。 diff --git a/09-add-app-info.md b/09-add-app-info.md index d7a4e06..2d73eb0 100644 --- a/09-add-app-info.md +++ b/09-add-app-info.md @@ -7,7 +7,7 @@ ![添加代码](images/006-add-code.png) 上面定义了三个函数:`GetAppDescribe()`、`GetAppVersion()`、`GetAppBuildTime()`,分析用于告析 tbox.main 框架当前应用的描述、版本号、编译时间点。 -[完整示例代码](03-add-app-info) +[完整示例代码](examples/03-add-app-info) 重新构建,再次执行 `./demo -v; ./demo -h` 效果: ![](images/007-has-desc.png) @@ -34,7 +34,7 @@ └── module.h ``` -[示例工程目录](04-normal-app-demo) +[示例工程目录](examples/04-normal-app-demo) 建议:在 my/ 目录下的所有代码,均以 `my` 作为顶层命名空间,以防在下一节的多Module情况下,命名空间污染。 diff --git a/09-signal-event/Makefile b/09-signal-event/Makefile deleted file mode 100644 index 15b10e8..0000000 --- a/09-signal-event/Makefile +++ /dev/null @@ -1,22 +0,0 @@ -TARGET:=demo - -OBJECTS:=app_main.o - -CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' -LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic -LIBS:=\ - -ltbox_main \ - -ltbox_terminal \ - -ltbox_network \ - -ltbox_eventx \ - -ltbox_event \ - -ltbox_log \ - -ltbox_util \ - -ltbox_base \ - -lpthread -ldl - -$(TARGET): $(OBJECTS) - g++ -o $@ $^ $(LDFLAGS) $(LIBS) - -clean: - rm *.o diff --git a/10-multi-modules.md b/10-multi-modules.md index 2802dd0..81704f0 100644 --- a/10-multi-modules.md +++ b/10-multi-modules.md @@ -28,7 +28,7 @@ └── module.h ``` -[示例工程目录](05-two-modules) +[示例工程目录](examples/05-two-modules) 构建后运行: ![多Module在同一进程运行效果](images/009-two-modules.png) diff --git a/12-timer-pool.md b/12-timer-pool.md new file mode 100644 index 0000000..89784bd --- /dev/null +++ b/12-timer-pool.md @@ -0,0 +1,28 @@ +# 定时器池 + +在 [定时器](04-timer-event.md) 这一节中,我们学习了TimerEvent的实用,对定时器有了一定的了解。 + +在实际的项目中,我们会发现`TimerEvent`使用起来不是很方便。为了使用它,我们需要调`ctx().loop()->newTimerEvent()`返回一个定时器对象的指针。为保证资源不泄漏,我们需要在当将Module的析构函数中进行销毁。有时我们只是临时需要一个单次定时器而已。这样的生命期操控不方便,很繁琐。 +为此,我们引入了`TimerPool`这个模块。 +TimerPool模块帮我们管理定时器的生命期。我们需要使用定时器的时候,调它的`doAfter()`或者`doEvent()`即可,非常方便。 + +在 tbox.main 框架中,直带定时器池。使用时只需要:`ctx().timer_pool()` 就可以得到`TimerPool`对象的指针。 +下面,我将使用一个示例程序进行展示: + +![](images/045-timer-pool-code.png) + +(1) 在`onStart()` 中,分别启动了两个定时器。一个是用的`doEvery()`,另一个是`doAfter()`; +(2) 在`onStop()` 中,取消了这两个定时器; + +[示例工程目录](examples/13-timer-pool) + +编译后,执行效果: +![](images/044-timer-pool-run.png) +可以看到周期性定时器有每秒触发。在第5秒时,单次定时器也触发了。 + +**注意**: +虽然创建定时器方便了,但一定要注意生命期倒挂风险。 +所谓“生命期倒挂”具体表示为:所创建定时器时传入了一个生命期比较短的对象。在定时器触发时,这个对象的生命期已经提前结束了,进而导致不可预期的程序崩溃问题。 + +------- +[[返回主页]](README.md) diff --git a/13-timer-fd.md b/13-timer-fd.md new file mode 100644 index 0000000..1895d3d --- /dev/null +++ b/13-timer-fd.md @@ -0,0 +1,34 @@ +# TimerFd + +在 [定时器](04-timer-event.md) 这一节中,我们提及到`TimerEvent`的实现。本质上,它是通过`epoll()`或`select()`中阻塞的超时参数来实现定时任务的。`TimerEvent`在实际运用中的会有约400us的误差。如果想要精度更好的定时器,则需要使用本节介绍的`TimerFd`。 +`TimerFd` 定义在 `eventx/timer_fd.h` 中,是基于Linux内核提供的`timer_fd`实现的。具体的原理可阅读相关文章《[Linux fd 系列 — 定时器 timerfd 是什么?](https://zhuanlan.zhihu.com/p/409434419)》 + +本节通过一个示例来引导大家掌握`TimerFd`的使用方法。 + +![](images/047-timer-fd-code.png) + +1) 与`TimerEvent`不同的是`TimerFd`不属于`Loop`的一部分,也没有被纳入到框架。要使用它,都需要`#include ` +2) 在`MyModule`中分别实列化两个`TimerFd`对象:`oneshot_timer_`与`repeat_timer_`; +3) 在`MyModule`的初始化列表中对`oneshot_timer_`与`repeat_timer_`进行构造。注意,它的初始化需要`ctx().loop()`获取的`Loop`对象指针; +4) 在`onInit()`中对它们进行初始化,只传一个时间的表示单次触发;传两个时间的表示单次触发之后,还需要重复触发; +5) 在`onStart()`中使能它们。在工程中,不是一定要在`onStart()`中使能它们,根据业务需要我们也可以在需要的时间再使能它们; +6) 在`onStop()`中关闭它们; +7) 在`onCleanup()`中清理它们; + +[示例源码](examples/14-timer-fd/app_main.cpp) +[示例工程目录](examples/14-timer-fd) + +编译后,执行效果: +![](images/046-timer-fd-run.png) +我们注意到,它的定时精确误差在5us以内,非常可观。 + +思考:为什么使用`TimerFd`的精准度会比`TimerEvent`高这么多? +猜想:由于`TimerFd`是基于Linux内核中的`timer_fd`实现的。在`timer_fd`触发超时时,内核立即让`timer_fd`的文件描述符变成可读,鉴于定时器的实时性要求,Linux内核会立即唤醒对应的线程并执行。而基于`epoll()`与`select()`超时参数的`TimerEvent`则得不到Linux内核的特殊照顾。当`epoll()`或`select()`等待超时后,Linux内核只将对应的线程设置为READY状态并排队等待CPU资源,以致于执行的时间点存在较大的误差。 + +问题:`TimerEvent`,`TimerPool`,`TimerFd` 这三种实现方式如何抉择? +回答: +- 对于日常对精度要求不太高(如1ms精度可接受)的定时执行场景,可从`TimerEvent`与`TimerPool`中任选一种。如果是对精度要求高,要保证100us以内误差的,使用`TimerFd`; +- 遇到首次触发与后续触发间隔不一致的,采用`TimerFd`。 + +------- +[[返回主页]](README.md) diff --git a/14-thread-pool-and-work-thread.md b/14-thread-pool-and-work-thread.md new file mode 100644 index 0000000..8e63707 --- /dev/null +++ b/14-thread-pool-and-work-thread.md @@ -0,0 +1,86 @@ +# 线程池与工作线程 +由于cpp-tbox是基于事件驱动的编程模型,对于事件的处理要求不能阻塞。然而实际的工作中,多多少少会遇到一些需要阻塞的事务。比如说:大运算、调用第三方库的阻塞性接口等。 +这时,就得需要借助`ThreadPool`与`WorkThread`来解决问题。 + +## ThreadPool +所谓线程池,就是预先创建指定个数的线程,让他们等待队列传入的任务。当队列中有新的任务后,其中一个线程就能抢到任务,并开始执行。如果同时来多个任务,那么这些空闲的线程便从逐一从队列中领取任务。工作线程在执行完任务后,会继续检查任务队列中是否还有其它的任务。如果有,会继续领取任务并执行。否则,观察当前任务数是否大于预设个数,如果是则结束线程,不是则处理空闲等待状态。 +有了线程池,我们在开发中就不需要临时为阻塞性的函数调用创建线程,而是很方便地将这些阻塞性的函数调用委托给线程池,由线程池中的工作线程去执行。 + +### 基础用法 +本节通过一个简单的示例来引导大家掌握`ThreadPool`的基础使用方法。 + +![](images/048-thread-pool-simple-code.png) + +L21,在`onStart()`函数中直接使用`ctx().thread_pool()->execute(...)`委派任务给线程池执行。这是因为在tbox.main框架中,ctx 自带一个线程池对象。我们只需要通过`ctx().thread_pool()`即可获取并使用。 +L22~27,在工作线程中打印日志并执行`Fibonacci()`函数模拟CPU密集的运算。 + +执行结果: +![](images/049-thread-pool-simple-run.png) + +注意观察上面日志中的线程ID,9983是Loop线程,9984是工作线程,9985是监控线程。 +(2)与(3)都是在工作线程中打印的。 +在最后黄框标记为线程池任务执行的耗时报告:"cost 201 + 722190",表示从任务派发到执行的等待时长为201us,而任务的执行花了722190us。 + +[示例工程目录](examples/15-thread-pool-simple) + +### 进阶用法 +上面对线程池的使用仅仅是让工作线程做了一件事情,不需要后续的处理。然而实际业务中往往不是这样的,比如:解析请求 --> 写数据库 --> 回复;其中"写数据库"这步操作是阻塞的。我们需要在让线程池写完数据后之后再在Loop线程中执行回复的动作。 +这就引入`execute()`的第二个参数: +``` +TaskToken execute(NonReturnFunc &&backend_task, NonReturnFunc &&main_cb, int prio); +``` +这个参数传入的是一个函数对象,即工作线程执行完后,由Loop线程执行的后续处理。 + +接下来,我用另一个较为复杂一点的示例向大家展示它的使用场景: +![](images/050-thread-pool-advance-code.png) + +这个示例也是让程序计算`Fibonacci()`。不同的是,让它一次性计算多个数值,而且Loop线程要在所有的计算都完成之后打印计算结果。 +实现的方式大致为:Loop线程向线程池委派计算任务,在每一个工作线程完成了任务之后,让Loop线程记录计算结果,检查是否所有的计算都已完成。如果已完成,Loop线程打印所有的结果。 + +具体的实现: +(1) 定义两个成员变量`unfinished_task_`,用于记录还有哪些任务没有完成;再定义`results_`记录不同数值计算所得的结果; +(2) 在`onStart()`中调4次`startCalculateTask()`,启4个计算任务; +(3) 在`startCalculateTask()`中,将需要计算的数值写入到`unfinished_task_`集合中。还需要定义`Tmp`结构体用于传递数据。注意,这里是Loop线程在执行,操作成员变量是安全的;并创建了`Tmp`结构体作为与子线程进行数据交换的媒介; +(4) 这里由工作线程在进行计算。在这里,是从`tmp->n`中取值进行操作,完成之后也是将结果记录到`tmp->result`中。整个过程没有操作成员变量,不需要加锁; +(5) 这里是由Loop线程执行的匿名函数,它会在工作线程执行完成之后被Loop线程调用。它的职责是处理`tmp`中的结果。 +(6)(7) 这是在做计算结果的处理。它有被Loop线程调用的,直接访问成员变量是安全的,无需加锁。 + + +编译执行的效果: +![](images/051-thread-pool-advance-run-1.png) +注意观察可见: +1. 确实是子线程在进行计算; +2. 最后的结果打印是主线程; +3. 所有的任务都只被一个工作线程执行; + +Q: 为什么只有一个工作线程呢? +A: 因为在tbox.main的框架配置中,线程池的默认配置为:最小0个,最大1个线程; + +如果想看到并行运算的效果,那要将最大线程数调大一点。在运行时指定`thread_pool.max`参数的值即可: +``` +./demo -s 'thread_pool.max=5' +``` +运行效果: +![](images/052-thread-pool-advance-run-2.png) +从上可以看到,程序在启动时就为每一个任务创建了一个工作线程。 + +[示例工程目录](examples/16-thread-pool-advance) + +## WorkThread +工作线程与线程池的接口很像,区别在于`WorkThread`内部只有一个工作线程,相当于简化版本的`ThreadPool`。由于`WorkThread`只有一个工作线程,提交给它的任务是按顺执行的。为此,我们会使用它来实现对执行顺序有要求的场景。 + +本节将用示例展示`WorkThread`的使用方法。如下图所示: +![](images/053-work-thread-code.png) +(1) 首先,需要`#include `; +(2) 要在定义成员变量`worker_`; +(3) 将上一节课中的`ctx().thread_pool()->`替代为`worker_.`即可; + +运行效果: +![](images/054-work-thread-run.png) +可以看到,与线程池很相像; + +[示例工程目录](examples/17-work-thread) + +------- +[[返回主页]](README.md) + diff --git a/15-run-in-loop.md b/15-run-in-loop.md new file mode 100644 index 0000000..cce602b --- /dev/null +++ b/15-run-in-loop.md @@ -0,0 +1,24 @@ +# runInLoop + +前面学会了使用线程池与工作线程,在Loop线程中向子线程委派任务。反过来,子线程也应该可以向Loop线程委派任务。 + +这种场景非常多。比如:我们使用一个第三方的通信库,这个库内部有自己的子线程,在特定的情况下会回调我们预先设置的回调函数。 +以ROS为例,它自己内部会为Topic创建多个消息接收线程。在收到消息的时候,其中的子线程会调用我们注册的回调函数,让我们处理消息。由于这是ROS内部的子线程调用的,我们不能直接让它在不加锁的情况下访问公共的数据,否则会导致多线程访问共同资源竞态,引起不可预知的后果。为了在无锁的情况下进行数据,我们常规的做法是委派给Loop线程来执行后面的操作。 + +本节通过一个简单的示例来引导大家掌握`runInLoop()`的使用方法。 + +![](images/055-run-in-loop-code.png) +本示例很简单: +- 在`onStart()`中,启动了一个子线程。这个子线程循环调用`ctx().loop()->runInLoop(...)`; +- 分析在L21与L24打印日志。 + +[示例源码](examples/18-run-in-loop/app_main.cpp) +[示例工程目录](examples/18-run-in-loop) + +编译后,执行效果: +![](images/056-run-in-loop-run.png) +注意观察日志的打印中的线程号,可以看到"thread index" 是由子线程执行的,而"loop thread"则是由Loop线程的。 +这说明达到我们期望的效果。 + +------- +[[返回主页]](README.md) diff --git a/16-state-machine.md b/16-state-machine.md new file mode 100644 index 0000000..2e42edf --- /dev/null +++ b/16-state-machine.md @@ -0,0 +1,113 @@ +# 状态机 + +## 什么是状态机 +状态机(State Machine)是一种行为设计模式,它允许对象在其内部状态发生变化时改变其行为。这种模式将状态封装成独立的类,并将动作委托给当前状态对象。 +状态机在处理复杂的条件逻辑时特别有用,例如游戏开发、工作流引擎、编译器设计、网络协议实现等场景。 + +状态机的核心概念: +- 状态(State):对象在其生命周期中可能处于的特定条件或模式。每个状态定义了对象在该状态下的行为。 +- 事件(Event):触发状态变化的外部或内部事件。 +- 跳转(Route):当特定事件发生时,状态机从一个状态转换到另一个状态的过程。 +- 动作(Action):状态转换过程中执行的操作,可以在进入状态、退出状态或转换期间执行。 + +参考资料: +- [什么是状态机?](https://zhuanlan.zhihu.com/p/47434856) + +在设计模式中有“状态机模式”,见网上的文章:《[状态机模式](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/)》。 +由于cpp-tbox不是这么实现的,所以不再赘述。有兴趣的朋友可以自行查资料。 + +为什么作者本人不采用这种方式呢?因为本人觉得这种实现比较繁琐与拖沓。在实际的工程实际中,用这种方式实现状态机会让我们反反复复通过继承的方式定义很多状态类,不方便维护。在cpp-tbox里采用的是状态表的实现方式。 + +状态机的本质,就是先创建几个状态(State),再为这些状态添加遇到不同事件(Event)的跳转(Route)。当然,这些跳转并不同一发生事件就跳转的,有些也需要满足一定的条件。这可以用一张状态表进行描述,也是我们实现状态机的思想。 + +具体的代码实现见源码:[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) + +## 实现个交通信号灯 +接下来我们通过模拟红绿灯的状态,来向大家展示cpp-tbox中状态机的使用; + +### 方案设计 +为方便学习,我们抛开细节,简化一下红绿灯的功能: +- 最开始是绿灯; +- 绿灯亮20秒,转黄灯; +- 黄灯亮5秒,转红灯; +- 红灯亮15秒,转绿灯; + +首先,我们一起罗列一下有哪些状态,以及进入与退出该状态的动作: +| 状态 | 进入动作 | 退出动作 | +|:----:|:----:|:----:| +| 绿灯 | 开始计时20秒,亮绿灯 | 灭绿灯 | +| 黄灯 | 开始计时5秒,亮黄灯 | 灭黄灯 | +| 红灯 | 开始计时15秒,亮红灯 | 灭红灯 | + +初始状态:绿灯 +终止状态:无 + +接下来,我们罗列各个状态之间的跳转 +| 源状态 | 事件 | 目标状态 | 跳转条件 | 执行动作 | +|:----:|:----:|:----:|:----:|:----:| +| 绿灯 | 时间到 | 黄灯 | 无 | 无 | +| 黄灯 | 时间到 | 红灯 | 无 | 无 | +| 红灯 | 时间到 | 绿灯 | 无 | 无 | + +至于计时,我们采用之前在《[定时器池](12-timer-pool.md)》中学到的`ctx().timer_pool()->doAfter(...)`; + +### 代码实现 +首先,我们需要包含状态机的头文件:`tbox/flow/state_machine.h`,如下: +![代码](images/058-traffic-light-code-1.png) + +然后,在类中定义状态机对象`sm_`与后面定时要用到的`timer_token_`: +![定义状态机变量](images/059-traffic-light-code-2.png) + +定义状态枚举类型与事件枚举类型: +![定义状态与事件枚举](images/060-traffic-light-code-3.png) +如上,我定义了三种状态:绿灯、黄灯、红灯。一定要注意:**状态枚举中的0是保留的,表示终止状态**。所以上面的代码中定义了`kTerm = 0`,预留了0值。这点大家一定要记住! +我们还定义了事件。目前只有一种事件,就是超时。一定要注意:**事件中的0也是保留的,表示任何事件**。正常我们定义的事件不能使用0。为此,我们定义了`kAny = 0`预留了它。 + +接下来,实现打开定时器与开关信号灯的函数: +![实时启动定时器与开关灯的函数](images/061-traffic-light-code-4.png) +我们先看`startTimer()`的实现: +L62,为防止重复开定时器,在开定时器之前先取消之前创建的定时器。如果`timer_token_`为空,或者定时器不存在,也不会有什么操作; +L63~66,这里使用定时器池创建了一个新的定时器,它将在`sec`秒后触发。执行的内容便是往状态机`sm_`中投喂`kTimerout`事件。 + +L69~77,定义了`setXXXXLightStatus()`三个函数是分别操作绿、黄、红三种信号灯开关。这里为了演示效果,就直接令其打印日志即可。 + +重点了来了。接下来,我们要实现`initSm()`函数。它负责对状态机的初始化: +![initSm函数实现](images/062-traffic-light-code-5.png) +L41,为了方便,将 `tbox::flow::Event` 重命名为 `Event`,将在L43~48中使用; +L43~48,定义每种状态进入与退出时的动作。以绿灯状态为例,进入时要将绿灯打开,并启动20秒的定时器;退出则要关闭绿灯。黄灯、红灯一样的操作。 +L50~52,分别创建三种状态,并为每个状态指定进入与退出时的动作; +L54,指定起始状态。这步不是必须的,如果不明确指定则创建的第一个状态就是起始状态; +L56~58,添加状态之间的跳转。目前业务比较简单,每种状态都只在遇到`kTimeout`事件时跳转到下一个状态。 + +最后,在`onInit()`中调`initSm()`初始化状态机;在`onStart()`中调`sm_.start()`启动状态机;在`onStop()`中调`sm_.stop()`停止状态机: +![启动状态机](images/063-traffic-light-code-6.png) + +完成! + +[示例源码](examples/19-traffic-light-sm/app_main.cpp) +[示例工程目录](examples/19-traffic-light-sm) + +### 执行效果 + +编译后,执行效果: +![执行效果](images/057-traffic-light-run.png) +可以看到: +- 它是按绿、黄、红循环亮灯的; +- 在最后程序退出的时候,正在亮着的红灯是会被关闭的; + +达到预期效果! + +## 总结 + +首先,要梳理状态机的需求。明确有哪些状态,哪些事件,状态之间的跳转。 +然后,将需求翻译成代码,通过`newState()`创建状态,通过`addRoute()`添加状态之间的跳转; +最后,将事件通过`run(...)`喂给状态机; + +注意点: +- 0状态保留为终止状态,不要使用; +- 1事件保留为任务事件,不要使用; +- 不要在状态动作函数中再调`run(...)`函数,会失败。 +用`ctx().loop().runInLoop([this] { sm_.run(XXXX); });` 包起来; + +------- +[[返回主页]](README.md) diff --git a/17-action-tree.md b/17-action-tree.md new file mode 100644 index 0000000..e69de29 diff --git a/50-coffee-machine.md b/50-coffee-machine.md new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md index 668c10c..a78f75a 100644 --- a/README.md +++ b/README.md @@ -51,11 +51,22 @@ ## 日志输出 -## 线程池的使用 +## 线程池与工作线程的使用 +由于cpp-tbox是基于事件驱动的编程模型,对于事件的处理要求不能阻塞。然而实际的工作中,多多少少会遇到一些需要阻塞的事务。比如说:大运算、调用第三方库的阻塞性接口等。 +这时,就得需要借助`ThreadPool`与`WorkThread`来解决问题。 +[[点击前往]](14-thread-pool-and-work-thread.md) ## 子线程向主线程委派任务 +上面学会了Loop线程往线程池与工作线程单向委派任务。反过来,子线程也可以向Loop线程委派任务。 +[[点击前往]](15-run-in-loop.md) -## 定时器池使用 +## 定时器池的使用 +前面了解了定时器的使用,这里来了解一种创建定时器更方便的方式。 +[[点击前往]](12-timer-pool.md) + +## TimerFd的使用 +学习另一种定时更精准的定时器。 +[[点击前往]](13-timer-fd.md) ## 运行时异常捕获功能 @@ -81,3 +92,15 @@ [[点击前往]](10-multi-modules.md) ## 模块插件化 + +## 状态机的使用 +通过交通信号灯的示例,带你掌握状态机的使用。 +[[点击前往]](16-state-machine.md) + +## 动作树的使用 +[[点击前往]](17-action-tree.md) + +## 实战 +### 打造一个咖啡机 +学习状态机、行为树的使用 +[[点击前往]](50-coffee-machine.md) diff --git a/00-first-demo/Makefile b/examples/00-first-demo/Makefile similarity index 88% rename from 00-first-demo/Makefile rename to examples/00-first-demo/Makefile index c1577cb..48cbf1f 100644 --- a/00-first-demo/Makefile +++ b/examples/00-first-demo/Makefile @@ -4,6 +4,8 @@ CXXFLAGS:=-I$(HOME)/.tbox/include LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic LIBS:=\ -ltbox_main \ + -ltbox_coroutine \ + -ltbox_trace \ -ltbox_terminal \ -ltbox_network \ -ltbox_eventx \ diff --git a/06-log-print/Makefile b/examples/01-first-module/Makefile similarity index 77% rename from 06-log-print/Makefile rename to examples/01-first-module/Makefile index 15b10e8..42c1fa6 100644 --- a/06-log-print/Makefile +++ b/examples/01-first-module/Makefile @@ -2,10 +2,12 @@ TARGET:=demo OBJECTS:=app_main.o -CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' +CXXFLAGS:=-I$(HOME)/.tbox/include -std=c++11 LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic LIBS:=\ -ltbox_main \ + -ltbox_coroutine \ + -ltbox_trace \ -ltbox_terminal \ -ltbox_network \ -ltbox_eventx \ @@ -17,6 +19,3 @@ LIBS:=\ $(TARGET): $(OBJECTS) g++ -o $@ $^ $(LDFLAGS) $(LIBS) - -clean: - rm *.o diff --git a/01-first-module/app_main.cpp b/examples/01-first-module/app_main.cpp similarity index 100% rename from 01-first-module/app_main.cpp rename to examples/01-first-module/app_main.cpp diff --git a/07-timer-event/Makefile b/examples/02-add-log-tag/Makefile similarity index 74% rename from 07-timer-event/Makefile rename to examples/02-add-log-tag/Makefile index 15b10e8..9d913c0 100644 --- a/07-timer-event/Makefile +++ b/examples/02-add-log-tag/Makefile @@ -2,10 +2,12 @@ TARGET:=demo OBJECTS:=app_main.o -CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' +CXXFLAGS:=-I$(HOME)/.tbox/include -DMODULE_ID='"demo"' -std=c++11 LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic LIBS:=\ -ltbox_main \ + -ltbox_coroutine \ + -ltbox_trace \ -ltbox_terminal \ -ltbox_network \ -ltbox_eventx \ diff --git a/02-add-log-tag/app_main.cpp b/examples/02-add-log-tag/app_main.cpp similarity index 100% rename from 02-add-log-tag/app_main.cpp rename to examples/02-add-log-tag/app_main.cpp diff --git a/02-add-log-tag/Makefile b/examples/03-add-app-info/Makefile similarity index 73% rename from 02-add-log-tag/Makefile rename to examples/03-add-app-info/Makefile index 15b10e8..526d446 100644 --- a/02-add-log-tag/Makefile +++ b/examples/03-add-app-info/Makefile @@ -2,10 +2,12 @@ TARGET:=demo OBJECTS:=app_main.o -CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' +CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' -std=c++11 LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic LIBS:=\ -ltbox_main \ + -ltbox_coroutine \ + -ltbox_trace \ -ltbox_terminal \ -ltbox_network \ -ltbox_eventx \ diff --git a/03-add-app-info/app_main.cpp b/examples/03-add-app-info/app_main.cpp similarity index 100% rename from 03-add-app-info/app_main.cpp rename to examples/03-add-app-info/app_main.cpp diff --git a/04-normal-app-demo/Makefile b/examples/04-normal-app-demo/Makefile similarity index 80% rename from 04-normal-app-demo/Makefile rename to examples/04-normal-app-demo/Makefile index cd32012..fc4d177 100644 --- a/04-normal-app-demo/Makefile +++ b/examples/04-normal-app-demo/Makefile @@ -5,10 +5,12 @@ OBJECTS:= \ build.o \ info.o -CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' +CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' -std=c++11 LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic LIBS:=\ -ltbox_main \ + -ltbox_coroutine \ + -ltbox_trace \ -ltbox_terminal \ -ltbox_network \ -ltbox_eventx \ diff --git a/04-normal-app-demo/apps.cpp b/examples/04-normal-app-demo/apps.cpp similarity index 100% rename from 04-normal-app-demo/apps.cpp rename to examples/04-normal-app-demo/apps.cpp diff --git a/04-normal-app-demo/build.cpp b/examples/04-normal-app-demo/build.cpp similarity index 100% rename from 04-normal-app-demo/build.cpp rename to examples/04-normal-app-demo/build.cpp diff --git a/04-normal-app-demo/info.cpp b/examples/04-normal-app-demo/info.cpp similarity index 100% rename from 04-normal-app-demo/info.cpp rename to examples/04-normal-app-demo/info.cpp diff --git a/04-normal-app-demo/my/app.mk b/examples/04-normal-app-demo/my/app.mk similarity index 100% rename from 04-normal-app-demo/my/app.mk rename to examples/04-normal-app-demo/my/app.mk diff --git a/04-normal-app-demo/my/module.cpp b/examples/04-normal-app-demo/my/module.cpp similarity index 100% rename from 04-normal-app-demo/my/module.cpp rename to examples/04-normal-app-demo/my/module.cpp diff --git a/04-normal-app-demo/my/module.h b/examples/04-normal-app-demo/my/module.h similarity index 100% rename from 04-normal-app-demo/my/module.h rename to examples/04-normal-app-demo/my/module.h diff --git a/05-two-modules/Makefile b/examples/05-two-modules/Makefile similarity index 81% rename from 05-two-modules/Makefile rename to examples/05-two-modules/Makefile index 9c0701a..20de0b5 100644 --- a/05-two-modules/Makefile +++ b/examples/05-two-modules/Makefile @@ -5,10 +5,12 @@ OBJECTS:= \ build.o \ info.o -CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' +CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' -std=c++11 LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic LIBS:=\ -ltbox_main \ + -ltbox_coroutine \ + -ltbox_trace \ -ltbox_terminal \ -ltbox_network \ -ltbox_eventx \ diff --git a/05-two-modules/apps.cpp b/examples/05-two-modules/apps.cpp similarity index 100% rename from 05-two-modules/apps.cpp rename to examples/05-two-modules/apps.cpp diff --git a/05-two-modules/build.cpp b/examples/05-two-modules/build.cpp similarity index 100% rename from 05-two-modules/build.cpp rename to examples/05-two-modules/build.cpp diff --git a/05-two-modules/info.cpp b/examples/05-two-modules/info.cpp similarity index 100% rename from 05-two-modules/info.cpp rename to examples/05-two-modules/info.cpp diff --git a/05-two-modules/my/app.mk b/examples/05-two-modules/my/app.mk similarity index 100% rename from 05-two-modules/my/app.mk rename to examples/05-two-modules/my/app.mk diff --git a/05-two-modules/my/module.cpp b/examples/05-two-modules/my/module.cpp similarity index 100% rename from 05-two-modules/my/module.cpp rename to examples/05-two-modules/my/module.cpp diff --git a/05-two-modules/my/module.h b/examples/05-two-modules/my/module.h similarity index 100% rename from 05-two-modules/my/module.h rename to examples/05-two-modules/my/module.h diff --git a/05-two-modules/your/app.mk b/examples/05-two-modules/your/app.mk similarity index 100% rename from 05-two-modules/your/app.mk rename to examples/05-two-modules/your/app.mk diff --git a/05-two-modules/your/module.cpp b/examples/05-two-modules/your/module.cpp similarity index 100% rename from 05-two-modules/your/module.cpp rename to examples/05-two-modules/your/module.cpp diff --git a/05-two-modules/your/module.h b/examples/05-two-modules/your/module.h similarity index 100% rename from 05-two-modules/your/module.h rename to examples/05-two-modules/your/module.h diff --git a/03-add-app-info/Makefile b/examples/06-log-print/Makefile similarity index 73% rename from 03-add-app-info/Makefile rename to examples/06-log-print/Makefile index 15b10e8..526d446 100644 --- a/03-add-app-info/Makefile +++ b/examples/06-log-print/Makefile @@ -2,10 +2,12 @@ TARGET:=demo OBJECTS:=app_main.o -CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' +CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' -std=c++11 LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic LIBS:=\ -ltbox_main \ + -ltbox_coroutine \ + -ltbox_trace \ -ltbox_terminal \ -ltbox_network \ -ltbox_eventx \ diff --git a/06-log-print/app_main.cpp b/examples/06-log-print/app_main.cpp similarity index 95% rename from 06-log-print/app_main.cpp rename to examples/06-log-print/app_main.cpp index f4546bf..ad8dcc0 100644 --- a/06-log-print/app_main.cpp +++ b/examples/06-log-print/app_main.cpp @@ -12,6 +12,7 @@ class MyModule : public tbox::main::Module { LogErr("this is error log"); LogWarn("this is warn log"); LogNotice("this is notice log"); + LogImportant("this is important log"); LogInfo("this is info log"); LogDbg("this is debug log"); LogTrace("this is trace log"); diff --git a/examples/07-timer-event/Makefile b/examples/07-timer-event/Makefile new file mode 100644 index 0000000..526d446 --- /dev/null +++ b/examples/07-timer-event/Makefile @@ -0,0 +1,24 @@ +TARGET:=demo + +OBJECTS:=app_main.o + +CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' -std=c++11 +LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic +LIBS:=\ + -ltbox_main \ + -ltbox_coroutine \ + -ltbox_trace \ + -ltbox_terminal \ + -ltbox_network \ + -ltbox_eventx \ + -ltbox_event \ + -ltbox_log \ + -ltbox_util \ + -ltbox_base \ + -lpthread -ldl + +$(TARGET): $(OBJECTS) + g++ -o $@ $^ $(LDFLAGS) $(LIBS) + +clean: + rm *.o diff --git a/07-timer-event/app_main.cpp b/examples/07-timer-event/app_main.cpp similarity index 100% rename from 07-timer-event/app_main.cpp rename to examples/07-timer-event/app_main.cpp diff --git a/examples/08-fd-event/Makefile b/examples/08-fd-event/Makefile new file mode 100644 index 0000000..526d446 --- /dev/null +++ b/examples/08-fd-event/Makefile @@ -0,0 +1,24 @@ +TARGET:=demo + +OBJECTS:=app_main.o + +CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' -std=c++11 +LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic +LIBS:=\ + -ltbox_main \ + -ltbox_coroutine \ + -ltbox_trace \ + -ltbox_terminal \ + -ltbox_network \ + -ltbox_eventx \ + -ltbox_event \ + -ltbox_log \ + -ltbox_util \ + -ltbox_base \ + -lpthread -ldl + +$(TARGET): $(OBJECTS) + g++ -o $@ $^ $(LDFLAGS) $(LIBS) + +clean: + rm *.o diff --git a/08-fd-event/app_main.cpp b/examples/08-fd-event/app_main.cpp similarity index 100% rename from 08-fd-event/app_main.cpp rename to examples/08-fd-event/app_main.cpp diff --git a/examples/09-signal-event/Makefile b/examples/09-signal-event/Makefile new file mode 100644 index 0000000..526d446 --- /dev/null +++ b/examples/09-signal-event/Makefile @@ -0,0 +1,24 @@ +TARGET:=demo + +OBJECTS:=app_main.o + +CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' -std=c++11 +LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic +LIBS:=\ + -ltbox_main \ + -ltbox_coroutine \ + -ltbox_trace \ + -ltbox_terminal \ + -ltbox_network \ + -ltbox_eventx \ + -ltbox_event \ + -ltbox_log \ + -ltbox_util \ + -ltbox_base \ + -lpthread -ldl + +$(TARGET): $(OBJECTS) + g++ -o $@ $^ $(LDFLAGS) $(LIBS) + +clean: + rm *.o diff --git a/09-signal-event/app_main.cpp b/examples/09-signal-event/app_main.cpp similarity index 100% rename from 09-signal-event/app_main.cpp rename to examples/09-signal-event/app_main.cpp diff --git a/10-tiny-http-server/Makefile b/examples/10-tiny-http-server/Makefile similarity index 74% rename from 10-tiny-http-server/Makefile rename to examples/10-tiny-http-server/Makefile index 940722d..104b21d 100644 --- a/10-tiny-http-server/Makefile +++ b/examples/10-tiny-http-server/Makefile @@ -2,11 +2,13 @@ TARGET:=demo OBJECTS:=app_main.o -CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' +CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' -std=c++11 LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic LIBS:=\ -ltbox_http \ -ltbox_main \ + -ltbox_coroutine \ + -ltbox_trace \ -ltbox_terminal \ -ltbox_network \ -ltbox_eventx \ diff --git a/10-tiny-http-server/app_main.cpp b/examples/10-tiny-http-server/app_main.cpp similarity index 100% rename from 10-tiny-http-server/app_main.cpp rename to examples/10-tiny-http-server/app_main.cpp diff --git a/11-parameters/Makefile b/examples/11-parameters/Makefile similarity index 74% rename from 11-parameters/Makefile rename to examples/11-parameters/Makefile index 940722d..104b21d 100644 --- a/11-parameters/Makefile +++ b/examples/11-parameters/Makefile @@ -2,11 +2,13 @@ TARGET:=demo OBJECTS:=app_main.o -CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' +CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' -std=c++11 LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic LIBS:=\ -ltbox_http \ -ltbox_main \ + -ltbox_coroutine \ + -ltbox_trace \ -ltbox_terminal \ -ltbox_network \ -ltbox_eventx \ diff --git a/11-parameters/app_main.cpp b/examples/11-parameters/app_main.cpp similarity index 100% rename from 11-parameters/app_main.cpp rename to examples/11-parameters/app_main.cpp diff --git a/11-parameters/config.json b/examples/11-parameters/config.json similarity index 100% rename from 11-parameters/config.json rename to examples/11-parameters/config.json diff --git a/12-terminal/Makefile b/examples/12-terminal/Makefile similarity index 74% rename from 12-terminal/Makefile rename to examples/12-terminal/Makefile index 940722d..104b21d 100644 --- a/12-terminal/Makefile +++ b/examples/12-terminal/Makefile @@ -2,11 +2,13 @@ TARGET:=demo OBJECTS:=app_main.o -CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' +CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' -std=c++11 LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic LIBS:=\ -ltbox_http \ -ltbox_main \ + -ltbox_coroutine \ + -ltbox_trace \ -ltbox_terminal \ -ltbox_network \ -ltbox_eventx \ diff --git a/12-terminal/app_main.cpp b/examples/12-terminal/app_main.cpp similarity index 99% rename from 12-terminal/app_main.cpp rename to examples/12-terminal/app_main.cpp index 5112879..1e25eb6 100644 --- a/12-terminal/app_main.cpp +++ b/examples/12-terminal/app_main.cpp @@ -12,8 +12,6 @@ class MyModule : public tbox::main::Module { public: virtual bool onInit(const tbox::Json &js) override { - initShell(); - if (!http_server_.initialize(tbox::network::SockAddr::FromString("0.0.0.0:12345"), 2)) return false; @@ -24,6 +22,7 @@ class MyModule : public tbox::main::Module { } ); + initShell(); return true; } virtual bool onStart() override { diff --git a/examples/13-timer-pool/Makefile b/examples/13-timer-pool/Makefile new file mode 100644 index 0000000..526d446 --- /dev/null +++ b/examples/13-timer-pool/Makefile @@ -0,0 +1,24 @@ +TARGET:=demo + +OBJECTS:=app_main.o + +CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' -std=c++11 +LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic +LIBS:=\ + -ltbox_main \ + -ltbox_coroutine \ + -ltbox_trace \ + -ltbox_terminal \ + -ltbox_network \ + -ltbox_eventx \ + -ltbox_event \ + -ltbox_log \ + -ltbox_util \ + -ltbox_base \ + -lpthread -ldl + +$(TARGET): $(OBJECTS) + g++ -o $@ $^ $(LDFLAGS) $(LIBS) + +clean: + rm *.o diff --git a/examples/13-timer-pool/app_main.cpp b/examples/13-timer-pool/app_main.cpp new file mode 100644 index 0000000..cbcaa42 --- /dev/null +++ b/examples/13-timer-pool/app_main.cpp @@ -0,0 +1,43 @@ +#include +#include + +class MyModule : public tbox::main::Module { + public: + explicit MyModule(tbox::main::Context &ctx) + : tbox::main::Module("my", ctx) + { } + + public: + virtual bool onStart() { + //! 启动周期性定时 + repeat_timer_token_ = ctx().timer_pool()->doEvery(std::chrono::seconds(1), [this] { onTick(); }); + //! 启动单次性定时 + oneshot_timer_token_ = ctx().timer_pool()->doAfter(std::chrono::seconds(5), [] { LogTag(); }); + return true; + } + + virtual void onStop() { + //! 退出时要记得cancel所有的定时器,以防生命期倒挂问题 + ctx().timer_pool()->cancel(repeat_timer_token_); + ctx().timer_pool()->cancel(oneshot_timer_token_); + } + + private: + void onTick() { + ++count_; + LogDbg("count:%d", count_); + } + + private: + tbox::eventx::TimerPool::TimerToken repeat_timer_token_; + tbox::eventx::TimerPool::TimerToken oneshot_timer_token_; + int count_ = 0; +}; + +namespace tbox { +namespace main { +void RegisterApps(Module &apps, Context &ctx) { + apps.add(new MyModule(ctx)); +} +} +} diff --git a/examples/14-timer-fd/Makefile b/examples/14-timer-fd/Makefile new file mode 100644 index 0000000..526d446 --- /dev/null +++ b/examples/14-timer-fd/Makefile @@ -0,0 +1,24 @@ +TARGET:=demo + +OBJECTS:=app_main.o + +CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' -std=c++11 +LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic +LIBS:=\ + -ltbox_main \ + -ltbox_coroutine \ + -ltbox_trace \ + -ltbox_terminal \ + -ltbox_network \ + -ltbox_eventx \ + -ltbox_event \ + -ltbox_log \ + -ltbox_util \ + -ltbox_base \ + -lpthread -ldl + +$(TARGET): $(OBJECTS) + g++ -o $@ $^ $(LDFLAGS) $(LIBS) + +clean: + rm *.o diff --git a/examples/14-timer-fd/app_main.cpp b/examples/14-timer-fd/app_main.cpp new file mode 100644 index 0000000..3984014 --- /dev/null +++ b/examples/14-timer-fd/app_main.cpp @@ -0,0 +1,53 @@ +#include +#include +#include + +class MyModule : public tbox::main::Module { + public: + explicit MyModule(tbox::main::Context &ctx) + : tbox::main::Module("my", ctx) + , oneshot_timer_(ctx.loop()) + , repeat_timer_(ctx.loop()) + { } + + public: + virtual bool onInit(const tbox::Json &) { + //! 只设置一个时间,表示单次定时5秒 + oneshot_timer_.initialize(std::chrono::seconds(5)); + oneshot_timer_.setCallback([] { LogDbg("oneshot"); }); + + //! 设置了两个时间,第一个时间表示首次触发为100ms,第二个时间表示后续间隔1秒触发 + repeat_timer_.initialize(std::chrono::milliseconds(100), std::chrono::seconds(1)); + repeat_timer_.setCallback([] { LogDbg("repeat"); }); + + return true; + } + + virtual bool onStart() { + oneshot_timer_.enable(); + repeat_timer_.enable(); + return true; + } + + virtual void onStop() { + repeat_timer_.disable(); + oneshot_timer_.disable(); + } + + virtual void onCleanup() { + repeat_timer_.cleanup(); + oneshot_timer_.cleanup(); + } + + private: + tbox::eventx::TimerFd oneshot_timer_; + tbox::eventx::TimerFd repeat_timer_; +}; + +namespace tbox { +namespace main { +void RegisterApps(Module &apps, Context &ctx) { + apps.add(new MyModule(ctx)); +} +} +} diff --git a/examples/15-thread-pool-simple/Makefile b/examples/15-thread-pool-simple/Makefile new file mode 100644 index 0000000..526d446 --- /dev/null +++ b/examples/15-thread-pool-simple/Makefile @@ -0,0 +1,24 @@ +TARGET:=demo + +OBJECTS:=app_main.o + +CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' -std=c++11 +LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic +LIBS:=\ + -ltbox_main \ + -ltbox_coroutine \ + -ltbox_trace \ + -ltbox_terminal \ + -ltbox_network \ + -ltbox_eventx \ + -ltbox_event \ + -ltbox_log \ + -ltbox_util \ + -ltbox_base \ + -lpthread -ldl + +$(TARGET): $(OBJECTS) + g++ -o $@ $^ $(LDFLAGS) $(LIBS) + +clean: + rm *.o diff --git a/examples/15-thread-pool-simple/app_main.cpp b/examples/15-thread-pool-simple/app_main.cpp new file mode 100644 index 0000000..0e2f597 --- /dev/null +++ b/examples/15-thread-pool-simple/app_main.cpp @@ -0,0 +1,41 @@ +#include +#include + +//! CPU密集运算 +uint64_t Fibonacci(int n) { + if (n <= 1) return n; + return Fibonacci(n - 1) + Fibonacci(n - 2); +} + +class MyModule : public tbox::main::Module { + public: + explicit MyModule(tbox::main::Context &ctx) + : tbox::main::Module("my", ctx) + { } + + public: + virtual bool onStart() { + int n = 40; + LogTrace("start caculate fibonacci %d", n); + //! 委托线程池执行任务 + ctx().thread_pool()->execute( + [n] { + //! 在线程池中执行 + LogTrace("fibonacci %d calculating", n); + auto result = Fibonacci(n); //! 进行CPU密集的运算 + LogTrace("caculate fibonacci %d result: %llu", n, result); + } + ); + + LogTag(); + return true; + } +}; + +namespace tbox { +namespace main { +void RegisterApps(Module &apps, Context &ctx) { + apps.add(new MyModule(ctx)); +} +} +} diff --git a/examples/16-thread-pool-advance/Makefile b/examples/16-thread-pool-advance/Makefile new file mode 100644 index 0000000..526d446 --- /dev/null +++ b/examples/16-thread-pool-advance/Makefile @@ -0,0 +1,24 @@ +TARGET:=demo + +OBJECTS:=app_main.o + +CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' -std=c++11 +LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic +LIBS:=\ + -ltbox_main \ + -ltbox_coroutine \ + -ltbox_trace \ + -ltbox_terminal \ + -ltbox_network \ + -ltbox_eventx \ + -ltbox_event \ + -ltbox_log \ + -ltbox_util \ + -ltbox_base \ + -lpthread -ldl + +$(TARGET): $(OBJECTS) + g++ -o $@ $^ $(LDFLAGS) $(LIBS) + +clean: + rm *.o diff --git a/examples/16-thread-pool-advance/app_main.cpp b/examples/16-thread-pool-advance/app_main.cpp new file mode 100644 index 0000000..7085c15 --- /dev/null +++ b/examples/16-thread-pool-advance/app_main.cpp @@ -0,0 +1,84 @@ +#include +#include +#include +#include +#include + +//! CPU密集运算 +uint64_t Fibonacci(int n) { + if (n <= 1) return n; + return Fibonacci(n - 1) + Fibonacci(n - 2); +} + +class MyModule : public tbox::main::Module { + public: + explicit MyModule(tbox::main::Context &ctx) + : tbox::main::Module("my", ctx) + { } + + public: + virtual bool onStart() { + startCalculateTask(45); + startCalculateTask(40); + startCalculateTask(43); + startCalculateTask(42); + return true; + } + + //! 启动计算任务 + void startCalculateTask(int n) { + struct Tmp { + int n = 0; + uint64_t result = 0; + }; + auto tmp = std::make_shared(); + tmp->n = n; + unfinished_task_.insert(n); + + LogTrace("start caculate fibonacci %d", n); + ctx().thread_pool()->execute( + [tmp] { + //! 工作线程中执行,要避免让它访问到公共资源 + LogTrace("fibonacci %d underway", tmp->n); + tmp->result = Fibonacci(tmp->n); + }, + [this, tmp] { + //! Loop线程中执行,允许访问公共资源 + onCaculateTaskFinished(tmp->n, tmp->result); + } + ); + //! Q: 为什么不能在工作线程中直接调用onCaculateTaskFinished()? + //! A: 因为onCaculateTaskFinished()函数会访问到公共变量unfinished_task_,result_,这是非常危险的事情。 + //! Loop线程与工作线程都去访问就会导致变量竞态,如果没有加锁就会引起不可预知的异常。 + //! 为此,onCaculateTaskFinished()必须在工作线程做完事情后的后续处理函数中调用,后续处理函数是由 + //! Loop线程进执行,所以是安全的。 + //! 由于工作线程不能直接访问公共数据,那么它正常工作需要的数据与执行的结果都需要通过Tmp这个结构体 + //! 进行传递。由于tmp所指的对象被Loop线程与工作线程的访问在时间上是隔离的,所以是安全的。 + } + + //! 计算任务完成后的处理 + void onCaculateTaskFinished(int n, uint64_t result) { + LogTrace("caculate fibonacci %d result: %llu", n, result); + unfinished_task_.erase(n); //! 从未完成任务中移除n + results_[n] = result; //! 将结果存放到result_中 + + if (unfinished_task_.empty()) { + LogTrace("=== all task finished ==="); + for (auto item : results_) { + std::cout << item.first << " --> " << item.second << std::endl; + } + } + } + + private: + std::set unfinished_task_; + std::map results_; +}; + +namespace tbox { +namespace main { +void RegisterApps(Module &apps, Context &ctx) { + apps.add(new MyModule(ctx)); +} +} +} diff --git a/examples/17-work-thread/Makefile b/examples/17-work-thread/Makefile new file mode 100644 index 0000000..526d446 --- /dev/null +++ b/examples/17-work-thread/Makefile @@ -0,0 +1,24 @@ +TARGET:=demo + +OBJECTS:=app_main.o + +CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' -std=c++11 +LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic +LIBS:=\ + -ltbox_main \ + -ltbox_coroutine \ + -ltbox_trace \ + -ltbox_terminal \ + -ltbox_network \ + -ltbox_eventx \ + -ltbox_event \ + -ltbox_log \ + -ltbox_util \ + -ltbox_base \ + -lpthread -ldl + +$(TARGET): $(OBJECTS) + g++ -o $@ $^ $(LDFLAGS) $(LIBS) + +clean: + rm *.o diff --git a/examples/17-work-thread/app_main.cpp b/examples/17-work-thread/app_main.cpp new file mode 100644 index 0000000..fafa683 --- /dev/null +++ b/examples/17-work-thread/app_main.cpp @@ -0,0 +1,44 @@ +#include +#include +#include + +//! CPU密集运算 +uint64_t Fibonacci(int n) { + if (n <= 1) return n; + return Fibonacci(n - 1) + Fibonacci(n - 2); +} + +class MyModule : public tbox::main::Module { + public: + explicit MyModule(tbox::main::Context &ctx) + : tbox::main::Module("my", ctx) + { } + + public: + virtual bool onStart() { + int n = 40; + LogTrace("start caculate fibonacci %d", n); + //! 委托线程池执行任务 + worker_.execute( + [n] { + //! 在线程池中执行 + LogTrace("fibonacci %d calculating", n); + auto result = Fibonacci(n); //! 进行CPU密集的运算 + LogTrace("caculate fibonacci %d result: %llu", n, result); + } + ); + + LogTag(); + return true; + } + private: + tbox::eventx::WorkThread worker_; +}; + +namespace tbox { +namespace main { +void RegisterApps(Module &apps, Context &ctx) { + apps.add(new MyModule(ctx)); +} +} +} diff --git a/examples/18-run-in-loop/Makefile b/examples/18-run-in-loop/Makefile new file mode 100644 index 0000000..526d446 --- /dev/null +++ b/examples/18-run-in-loop/Makefile @@ -0,0 +1,24 @@ +TARGET:=demo + +OBJECTS:=app_main.o + +CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' -std=c++11 +LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic +LIBS:=\ + -ltbox_main \ + -ltbox_coroutine \ + -ltbox_trace \ + -ltbox_terminal \ + -ltbox_network \ + -ltbox_eventx \ + -ltbox_event \ + -ltbox_log \ + -ltbox_util \ + -ltbox_base \ + -lpthread -ldl + +$(TARGET): $(OBJECTS) + g++ -o $@ $^ $(LDFLAGS) $(LIBS) + +clean: + rm *.o diff --git a/examples/18-run-in-loop/app_main.cpp b/examples/18-run-in-loop/app_main.cpp new file mode 100644 index 0000000..9e35647 --- /dev/null +++ b/examples/18-run-in-loop/app_main.cpp @@ -0,0 +1,45 @@ +#include +#include +#include + +class MyModule : public tbox::main::Module { + public: + explicit MyModule(tbox::main::Context &ctx) + : tbox::main::Module("my", ctx) + { } + + public: + virtual bool onStart() override { + //! 创建子线程,令其周期性调用runInLoop() + thread_ = std::thread([this] { + int index = 0; + while (keep_running_) { + //! 延时1秒 + std::this_thread::sleep_for(std::chrono::seconds(1)); + ++index; + + LogDbg("thread index: %d", index); + //! 委托给Loop线程 + ctx().loop()->runInLoop([index] { + LogDbg("loop index: %d", index); + }); + } + }); + return true; + } + + virtual void onStop() override { keep_running_ = false; } + virtual void onCleanup() override { thread_.join(); } + + private: + bool keep_running_ = true; + std::thread thread_; +}; + +namespace tbox { +namespace main { +void RegisterApps(Module &apps, Context &ctx) { + apps.add(new MyModule(ctx)); +} +} +} diff --git a/examples/19-traffic-light-sm/Makefile b/examples/19-traffic-light-sm/Makefile new file mode 100644 index 0000000..cba33ba --- /dev/null +++ b/examples/19-traffic-light-sm/Makefile @@ -0,0 +1,25 @@ +TARGET:=demo + +OBJECTS:=app_main.o + +CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"demo"' -std=c++11 +LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic +LIBS:=\ + -ltbox_flow \ + -ltbox_main \ + -ltbox_coroutine \ + -ltbox_trace \ + -ltbox_terminal \ + -ltbox_network \ + -ltbox_eventx \ + -ltbox_event \ + -ltbox_log \ + -ltbox_util \ + -ltbox_base \ + -lpthread -ldl + +$(TARGET): $(OBJECTS) + g++ -o $@ $^ $(LDFLAGS) $(LIBS) + +clean: + rm *.o diff --git a/examples/19-traffic-light-sm/app_main.cpp b/examples/19-traffic-light-sm/app_main.cpp new file mode 100644 index 0000000..a0058d4 --- /dev/null +++ b/examples/19-traffic-light-sm/app_main.cpp @@ -0,0 +1,90 @@ +#include +#include +#include + +class MyModule : public tbox::main::Module { + public: + explicit MyModule(tbox::main::Context &ctx) + : tbox::main::Module("my", ctx) + { } + + public: + virtual bool onInit(const tbox::Json &js) { + initSm(); + return true; + } + + virtual bool onStart() override { + sm_.start(); + return true; + } + + virtual void onStop() override { + ctx().timer_pool()->cancel(timer_token_); + sm_.stop(); + } + + protected: + enum class StateId { + kTerm = 0, + kGreen, //! 绿灯 + kYellow, //! 黄灯 + kRed, //! 红灯 + }; + + enum class EventId { + kAny = 0, + kTimeout, //! 时间到了 + }; + + void initSm() { + using Event = tbox::flow::Event; + + auto do_enter_green = [this] (Event) { setGreenLightStatus(true); startTimer(20); }; + auto do_exit_green = [this] (Event) { setGreenLightStatus(false); }; + auto do_enter_yellow = [this] (Event) { setYellowLightStatus(true); startTimer(5); }; + auto do_exit_yellow = [this] (Event) { setYellowLightStatus(false); }; + auto do_enter_red = [this] (Event) { setRedLightStatus(true); startTimer(15); }; + auto do_exit_red = [this] (Event) { setRedLightStatus(false); }; + + sm_.newState(StateId::kGreen, do_enter_green, do_exit_green, "Green"); + sm_.newState(StateId::kYellow, do_enter_yellow, do_exit_yellow, "Yellow"); + sm_.newState(StateId::kRed, do_enter_red, do_exit_red, "Red"); + + sm_.setInitState(StateId::kGreen); //! 如果不明确指定,那么第一个被创建的状态就是起始状态 + + sm_.addRoute(StateId::kGreen, EventId::kTimeout, StateId::kYellow, nullptr, nullptr, "Timeout"); + sm_.addRoute(StateId::kYellow, EventId::kTimeout, StateId::kRed, nullptr, nullptr, "Timeout"); + sm_.addRoute(StateId::kRed, EventId::kTimeout, StateId::kGreen, nullptr, nullptr, "Timeout"); + } + + void startTimer(int sec) { + ctx().timer_pool()->cancel(timer_token_); + timer_token_ = ctx().timer_pool()->doAfter( + std::chrono::seconds(sec), + [this] { sm_.run(EventId::kTimeout); } + ); + } + + void setGreenLightStatus(bool is_on) { + LogPrintf(is_on ? LOG_LEVEL_IMPORTANT : LOG_LEVEL_INFO, "green light: %s", is_on ? "on" : "off"); + } + void setYellowLightStatus(bool is_on) { + LogPrintf(is_on ? LOG_LEVEL_WARN: LOG_LEVEL_NOTICE, "yellow light: %s", is_on ? "on" : "off"); + } + void setRedLightStatus(bool is_on) { + LogPrintf(is_on ? LOG_LEVEL_FATAL: LOG_LEVEL_ERROR, "red light: %s", is_on ? "on" : "off"); + } + + private: + tbox::flow::StateMachine sm_; + tbox::eventx::TimerPool::TimerToken timer_token_; +}; + +namespace tbox { +namespace main { +void RegisterApps(Module &apps, Context &ctx) { + apps.add(new MyModule(ctx)); +} +} +} diff --git a/examples/50-coffee-machine/Makefile b/examples/50-coffee-machine/Makefile new file mode 100644 index 0000000..946e600 --- /dev/null +++ b/examples/50-coffee-machine/Makefile @@ -0,0 +1,37 @@ +TARGET:=coffee-machine + +OBJECTS:= \ + apps.o \ + build.o \ + info.o + +CXXFLAGS:=-I$(HOME)/.tbox/include -DLOG_MODULE_ID='"coffee"' -std=c++11 -ggdb +LDFLAGS:=-L$(HOME)/.tbox/lib -rdynamic + +include coffee/app.mk + +LIBS +=\ + -ltbox_main \ + -ltbox_coroutine \ + -ltbox_trace \ + -ltbox_terminal \ + -ltbox_network \ + -ltbox_eventx \ + -ltbox_event \ + -ltbox_log \ + -ltbox_util \ + -ltbox_base \ + -lpthread -ldl + +.PHONY: all clean distclean build.cpp + +all: $(TARGET) + +$(TARGET): $(OBJECTS) + g++ -o $@ $^ $(LDFLAGS) $(LIBS) + +clean: + rm -f $(OBJECTS) + +distclean: clean + rm -f $(TARGET) diff --git a/examples/50-coffee-machine/apps.cpp b/examples/50-coffee-machine/apps.cpp new file mode 100644 index 0000000..557baa5 --- /dev/null +++ b/examples/50-coffee-machine/apps.cpp @@ -0,0 +1,14 @@ +#include +#include + +#include "coffee/app.h" + +namespace tbox { +namespace main { + +void RegisterApps(Module &apps, Context &ctx) { + apps.add(new coffee::App(ctx)); +} + +} +} diff --git a/examples/50-coffee-machine/build.cpp b/examples/50-coffee-machine/build.cpp new file mode 100644 index 0000000..d550d47 --- /dev/null +++ b/examples/50-coffee-machine/build.cpp @@ -0,0 +1,11 @@ +#include + +namespace tbox { +namespace main { + +std::string GetAppBuildTime() { + return std::string(__DATE__) + " " + __TIME__; +} + +} +} diff --git a/examples/50-coffee-machine/coffee-machine b/examples/50-coffee-machine/coffee-machine new file mode 100755 index 0000000..69fae68 Binary files /dev/null and b/examples/50-coffee-machine/coffee-machine differ diff --git a/examples/50-coffee-machine/coffee/app.cpp b/examples/50-coffee-machine/coffee/app.cpp new file mode 100644 index 0000000..3eda5d6 --- /dev/null +++ b/examples/50-coffee-machine/coffee/app.cpp @@ -0,0 +1,164 @@ +#include "app.h" + +#include + +#include +#include +#include +#include +#include +#include + +namespace coffee { + +using SM = tbox::flow::StateMachine; +using Event = tbox::flow::Event; + +App::App(tbox::main::Context &ctx) + : tbox::main::Module("coffee", ctx) + , timer_(ctx.loop()->newTimerEvent()) +{ } + +App::~App() { + CHECK_DELETE_RESET_OBJ(timer_); +} + +bool App::onInit(const tbox::Json &js) { + initSm(); + initShell(); + + timer_->initialize(std::chrono::seconds(1), tbox::event::Event::Mode::kPersist); + timer_->setCallback([this] { onTimerTick(); }); + + return true; +} + +bool App::onStart() { + sm_.start(); + return true; +} + +void App::onStop() { + sm_.stop(); +} + +void App::onCleanup() { } + +void App::initSm() { + auto do_enter_check = [this] (Event) { + LogDbg("start check"); + remain_sec_ = 3; + timer_->enable(); + }; + auto do_exit_check = [this] (Event) { + timer_->disable(); + }; + + auto do_enter_working = [this] (Event) { + LogDbg("start make"); + remain_sec_ = 10; + timer_->enable(); + }; + auto do_exit_working = [this] (Event) { + timer_->disable(); + }; + + //! 创建状态 + sm_.newState(StateId::kCheck, do_enter_check, nullptr, "Check"); + sm_.newState(StateId::kIdle, nullptr, nullptr, "Idle"); + sm_.newState(StateId::kWorking, do_enter_working, do_exit_working, "Working"); + sm_.newState(StateId::kFault, nullptr, nullptr, "Fault"); + + //! 建立状态间的跳转链路 + sm_.addRoute(StateId::kCheck, EventId::kSucc, StateId::kIdle, nullptr, nullptr, "Succ"); + sm_.addRoute(StateId::kCheck, EventId::kFail, StateId::kFault, nullptr, nullptr, "Fault"); + + sm_.addRoute(StateId::kIdle, EventId::kMake, StateId::kWorking, nullptr, nullptr, "Make"); + + sm_.addRoute(StateId::kWorking, EventId::kSucc, StateId::kIdle, nullptr, nullptr, "Succ"); + sm_.addRoute(StateId::kWorking, EventId::kFail, StateId::kFault, nullptr, nullptr, "Fail"); + sm_.addRoute(StateId::kWorking, EventId::kStop, StateId::kIdle, nullptr, nullptr, "Stop"); + + sm_.addRoute(StateId::kFault, EventId::kCheck, StateId::kCheck, nullptr, nullptr, "Check"); + + //! 注册状态跳转打印 + sm_.setStateChangedCallback( + [this] (SM::StateID from, SM::StateID to, Event event) { + LogDbg("state changed: %s --(%s)-> %s", + ToString(static_cast(from)).c_str(), + ToString(static_cast(event.id)).c_str(), + ToString(static_cast(to)).c_str() + ); + } + ); +} + +bool RamdomFail() { + std::default_random_engine e; + std::uniform_int_distribution u(0, 100); + + e.seed(tbox::util::GetUtcMilliseconds()); + return u(e) < 10; +} + +void App::onTimerTick() { + if (remain_sec_ > 0) { + --remain_sec_; + if (RamdomFail()) { + sm_.run(EventId::kFail); + } + + } else { + sm_.run(EventId::kSucc); + } +} + +void App::initShell() { + auto &shell = *ctx().terminal(); + auto dir_node = shell.createDirNode(); + + shell.mountNode(shell.rootNode(), dir_node, name()); + + tbox::terminal::AddFuncNode(shell, dir_node, "make", [this] { sm_.run(EventId::kMake); }); + tbox::terminal::AddFuncNode(shell, dir_node, "check", [this] { sm_.run(EventId::kCheck); }); + tbox::terminal::AddFuncNode(shell, dir_node, "stop", [this] { sm_.run(EventId::kStop); }); +} + +std::string App::ToString(StateId id) { + const char* tbl[] = { + "Term", + "Check", + "Idle", + "Working", + "Fault" + }; + + static_assert(NUMBER_OF_ARRAY(tbl) == static_cast(StateId::kMax)); + auto index = static_cast(id); + if (index >= 0 && index < NUMBER_OF_ARRAY(tbl)) + return tbl[index]; + + return "unknown " + std::to_string(index); +} + +std::string App::ToString(EventId id) { + const char* tbl[] = { + "Any", + "Make", + "Succ", + "Fail", + "Stop", + "Check", + }; + + static_assert(NUMBER_OF_ARRAY(tbl) == static_cast(EventId::kMax)); + auto index = static_cast(id); + if (index >= 0 && index < NUMBER_OF_ARRAY(tbl)) + return tbl[index]; + + return "unknown " + std::to_string(index); + +} + + +} diff --git a/examples/50-coffee-machine/coffee/app.h b/examples/50-coffee-machine/coffee/app.h new file mode 100644 index 0000000..b08001a --- /dev/null +++ b/examples/50-coffee-machine/coffee/app.h @@ -0,0 +1,56 @@ +#ifndef COFFEE_APP_H +#define COFFEE_APP_H + +#include +#include + +namespace coffee { + +class App : public tbox::main::Module { + public: + explicit App(tbox::main::Context &ctx); + virtual ~App(); + + public: + virtual bool onInit(const tbox::Json &js) override; + virtual bool onStart() override; + virtual void onStop() override; + virtual void onCleanup() override; + + protected: + enum class StateId { + kTerm = 0, + kCheck, //!< 自检中 + kIdle, //!< 空闲中 + kWorking, //!< 工作中 + kFault, //!< 故障中 + kMax + }; + std::string ToString(StateId id); + + enum class EventId { + kAny = 0, + kMake, //!< 制作 + kSucc, //!< 成功 + kFail, //!< 失败 + kStop, //!< 停止 + kCheck, //!< 自检 + kMax + }; + std::string ToString(EventId id); + + void initSm(); + void initShell(); + + void onTimerTick(); + + private: + tbox::event::TimerEvent *timer_ = nullptr; + tbox::flow::StateMachine sm_; + + int remain_sec_ = 0; +}; + +} + +#endif //COFFEE_APP_H diff --git a/examples/50-coffee-machine/coffee/app.mk b/examples/50-coffee-machine/coffee/app.mk new file mode 100644 index 0000000..eba4796 --- /dev/null +++ b/examples/50-coffee-machine/coffee/app.mk @@ -0,0 +1,2 @@ +OBJECTS += coffee/app.o +LIBS += -ltbox_flow diff --git a/examples/50-coffee-machine/info.cpp b/examples/50-coffee-machine/info.cpp new file mode 100644 index 0000000..22a9c40 --- /dev/null +++ b/examples/50-coffee-machine/info.cpp @@ -0,0 +1,18 @@ +#include + +namespace tbox { +namespace main { + +std::string GetAppDescribe() { + return "This is CppTbox Demo, writen by Hevake Lee."; +} + +void GetAppVersion(int &major, int &minor, int &rev, int &build) { + major = 1; + minor = 2; + rev = 3; + build = 4; +} + +} +} diff --git a/images/000-first-demo.png b/images/000-first-demo.png index f6ea771..cdc6327 100644 Binary files a/images/000-first-demo.png and b/images/000-first-demo.png differ diff --git a/images/001-first-demo-tips.png b/images/001-first-demo-tips.png index 24de43e..143c393 100644 Binary files a/images/001-first-demo-tips.png and b/images/001-first-demo-tips.png differ diff --git a/images/002-your-first-module-1.png b/images/002-your-first-module-1.png index 4b83dfa..9dbe628 100644 Binary files a/images/002-your-first-module-1.png and b/images/002-your-first-module-1.png differ diff --git a/images/003-your-first-module-with-log.png b/images/003-your-first-module-with-log.png index d0408cf..1149dbf 100644 Binary files a/images/003-your-first-module-with-log.png and b/images/003-your-first-module-with-log.png differ diff --git a/images/011-log-print.png b/images/011-log-print.png index abbb350..3122096 100644 Binary files a/images/011-log-print.png and b/images/011-log-print.png differ diff --git a/images/012-log-print-code.png b/images/012-log-print-code.png index 6d83860..fa93517 100644 Binary files a/images/012-log-print-code.png and b/images/012-log-print-code.png differ diff --git a/images/013-log-field.png b/images/013-log-field.png index 9a51789..28001fa 100644 Binary files a/images/013-log-field.png and b/images/013-log-field.png differ diff --git a/images/015-timer-result.png b/images/015-timer-result.png index e256542..9a4e52a 100644 Binary files a/images/015-timer-result.png and b/images/015-timer-result.png differ diff --git a/images/034-terminal-show.gif b/images/034-terminal-show.gif deleted file mode 100644 index 82db629..0000000 Binary files a/images/034-terminal-show.gif and /dev/null differ diff --git a/images/043-terminal-show.png b/images/043-terminal-show.png new file mode 100644 index 0000000..d24c5b9 Binary files /dev/null and b/images/043-terminal-show.png differ diff --git a/images/044-timer-pool-run.png b/images/044-timer-pool-run.png new file mode 100644 index 0000000..12e4a17 Binary files /dev/null and b/images/044-timer-pool-run.png differ diff --git a/images/045-timer-pool-code.png b/images/045-timer-pool-code.png new file mode 100644 index 0000000..9b7b8f5 Binary files /dev/null and b/images/045-timer-pool-code.png differ diff --git a/images/046-timer-fd-run.png b/images/046-timer-fd-run.png new file mode 100644 index 0000000..6ef0ef7 Binary files /dev/null and b/images/046-timer-fd-run.png differ diff --git a/images/047-timer-fd-code.png b/images/047-timer-fd-code.png new file mode 100644 index 0000000..cedca57 Binary files /dev/null and b/images/047-timer-fd-code.png differ diff --git a/images/048-thread-pool-simple-code.png b/images/048-thread-pool-simple-code.png new file mode 100644 index 0000000..36f44c5 Binary files /dev/null and b/images/048-thread-pool-simple-code.png differ diff --git a/images/049-thread-pool-simple-run.png b/images/049-thread-pool-simple-run.png new file mode 100644 index 0000000..71f79c5 Binary files /dev/null and b/images/049-thread-pool-simple-run.png differ diff --git a/images/050-thread-pool-advance-code.png b/images/050-thread-pool-advance-code.png new file mode 100644 index 0000000..2fc4a80 Binary files /dev/null and b/images/050-thread-pool-advance-code.png differ diff --git a/images/051-thread-pool-advance-run-1.png b/images/051-thread-pool-advance-run-1.png new file mode 100644 index 0000000..d24febb Binary files /dev/null and b/images/051-thread-pool-advance-run-1.png differ diff --git a/images/052-thread-pool-advance-run-2.png b/images/052-thread-pool-advance-run-2.png new file mode 100644 index 0000000..047ccef Binary files /dev/null and b/images/052-thread-pool-advance-run-2.png differ diff --git a/images/053-work-thread-code.png b/images/053-work-thread-code.png new file mode 100644 index 0000000..9b7690b Binary files /dev/null and b/images/053-work-thread-code.png differ diff --git a/images/054-work-thread-run.png b/images/054-work-thread-run.png new file mode 100644 index 0000000..eb7c052 Binary files /dev/null and b/images/054-work-thread-run.png differ diff --git a/images/055-run-in-loop-code.png b/images/055-run-in-loop-code.png new file mode 100644 index 0000000..9e8133e Binary files /dev/null and b/images/055-run-in-loop-code.png differ diff --git a/images/056-run-in-loop-run.png b/images/056-run-in-loop-run.png new file mode 100644 index 0000000..84e78ec Binary files /dev/null and b/images/056-run-in-loop-run.png differ diff --git a/images/057-traffic-light-run.png b/images/057-traffic-light-run.png new file mode 100644 index 0000000..72014f6 Binary files /dev/null and b/images/057-traffic-light-run.png differ diff --git a/images/058-traffic-light-code-1.png b/images/058-traffic-light-code-1.png new file mode 100644 index 0000000..c8a5260 Binary files /dev/null and b/images/058-traffic-light-code-1.png differ diff --git a/images/059-traffic-light-code-2.png b/images/059-traffic-light-code-2.png new file mode 100644 index 0000000..4f80494 Binary files /dev/null and b/images/059-traffic-light-code-2.png differ diff --git a/images/060-traffic-light-code-3.png b/images/060-traffic-light-code-3.png new file mode 100644 index 0000000..4450803 Binary files /dev/null and b/images/060-traffic-light-code-3.png differ diff --git a/images/061-traffic-light-code-4.png b/images/061-traffic-light-code-4.png new file mode 100644 index 0000000..cd621e8 Binary files /dev/null and b/images/061-traffic-light-code-4.png differ diff --git a/images/062-traffic-light-code-5.png b/images/062-traffic-light-code-5.png new file mode 100644 index 0000000..3b63912 Binary files /dev/null and b/images/062-traffic-light-code-5.png differ diff --git a/images/063-traffic-light-code-6.png b/images/063-traffic-light-code-6.png new file mode 100644 index 0000000..aba160e Binary files /dev/null and b/images/063-traffic-light-code-6.png differ