编程导航undefined话题讨论

0 参与
分享

快来分享你的内容吧~

点击登录,快来和大家讨论吧~
表情
图片
话题
打卡
综合
交流
文章
问答

C++埋点项目学员-求源码+教程文本排版改进诉求

项目实战里的c++埋点项目,有源码吗?另外这个教程的文本排版也太难看了,全是平铺的。会出其他c++项目实战吗

C++ 埋点 SDK 项目是否适合新手做?

喵哥你好,关于那个C++埋点SDK项目,我有几个问题想问: (我的目前情况介绍:末流211,科班大二,学过C/C++,数据结构,算法设计与分析,计组,计网等) 1.这个项目是否适合新手做? 我第一次写项目,以前没有接触过,我的想法是从项目入手去了解和学习我所欠缺的知识。 这个项目中一开始就需要用到cmake,所以我去学了cmake,了解了一些常用指令。编译环境是VS Code,我以前一直用的vs 2022,对目前的编译环境没有很熟悉,终端的操作指令是临时才去了解的,但也能看懂。 虽然了解了这些,但是我还是卡在了项目的“4.代码结构设计”那里,我看了目录,里面有很多文件,而在星球你的项目介绍和解释里只有CMakeLists.txt和build.py等几个有数的介绍,我不知道是所有的文件都需要自己手动敲,还是说有些文件是编译之后自动生成的 2.如果项目不适合新手,接下来我应该再学哪些东西? 虽然但是吧我确实挺菜的,但是我还是想从项目入手去有针对性的学习,尽最大努力去学习与项目匹配的相关技术,做这个项目我发现关于C++的知识我还没用上呢,就先卡在其他地方了,这应该是所有新手都会遇到的坎吧,没人带的话,有时候真的挺无助的呜呜,但是我不会放弃,关关难过关关过,争取在下个学期之前能多做几个项目,然后在九月份左右投递实习简历。 快给指条明路吧喵哥呜呜!!!

16. 结束语

<html> <head></head> <body> <div class="content ql-editor"> <p>大家好,我是程序喵。</p> <p><br></p> <p>在之前的章节中,我已经详细介绍了整个C++埋点项目。本节主要是为了给这个项目画上一个句号。</p> <p><br></p> <p>通过这个项目,我介绍了许多与C++相关的技术,但我们其实还可以更深入,通过扩展这个项目来提升C++编程能力。</p> <p><br></p> <p>在开篇词中,我已经介绍了通过这个项目可以接触到许多C++相关的知识点:</p> <p><br></p> <ol> <li data-list="ordered"><span class="ql-ui"></span>C、C++</li> <li data-list="ordered"><span class="ql-ui"></span>C++ 新特性</li> <li data-list="ordered"><span class="ql-ui"></span>线程池</li> <li data-list="ordered"><span class="ql-ui"></span>定时器</li> <li data-list="ordered"><span class="ql-ui"></span>Windows 注册表操作</li> <li data-list="ordered"><span class="ql-ui"></span>数据库操作</li> <li data-list="ordered"><span class="ql-ui"></span>文件系统相关操作</li> <li data-list="ordered"><span class="ql-ui"></span>时间相关操作</li> <li data-list="ordered"><span class="ql-ui"></span>日志系统</li> <li data-list="ordered"><span class="ql-ui"></span>Json</li> <li data-list="ordered"><span class="ql-ui"></span>加解密</li> <li data-list="ordered"><span class="ql-ui"></span>HTTP、HTTPS网络请求</li> <li data-list="ordered"><span class="ql-ui"></span>网络通信协议的设计</li> </ol> <p><br></p> <p>虽然在这个项目中,我们已经接触到了这些技术,但是还不太特别深入。</p> <p><br></p> <p>我们还可以进一步深入研究扩展这个项目,方便更加全面地掌握C++的高级技术。</p> <p><br></p> <p>在以下方面,我们可以进行更深入的实践:</p> <p><br></p> <ol> <li data-list="ordered"><span class="ql-ui"></span>多线程方面:我们使用了boost asio中的strand,但大家可以考虑自己实现一套多线程方案。可以研究不同的多线程模型,比如生产者-消费者模型、线程池等,并深入了解各个模型的优缺点以及适用场景,写一套自己的strand调度模块。</li> <li data-list="ordered"><span class="ql-ui"></span>数据库方面:我们使用了ORM数据库,但大家可以尝试直接使用sqlite,尝试调用sqlite API,编写数据库语句,处理数据库事务,并了解sqlite返回的各种错误码的含义。还可以学习其他数据库技术,比如MySQL、PostgreSQL等,并对比它们的特点和适用场景。</li> <li data-list="ordered"><span class="ql-ui"></span>日志方面:我们引入了spdlog,但大家可以参考spdlog和glog等其他相关源代码,自己实现一套日志系统。可以深入研究日志系统的设计原理,包括日志级别、日志格式化、日志存储等,并尝试优化日志系统的性能和鲁邦性。</li> <li data-list="ordered"><span class="ql-ui"></span>JSON模块方面:虽然我们引入了第三方库,但大家也可以考虑自己实现一个JSON模块。可以学习JSON的解析和生成算法,了解JSON的数据结构和语法规范,并尝试优化JSON的性能和可扩展性。</li> <li data-list="ordered"><span class="ql-ui"></span>HTTP、HTTPS网络请求方面:我们引入了asio,但大家可以尝试使用socket,并通过mbtdtls中的ssl,自己构建HTTP、HTTPS网络请求的请求体。可以深入研究HTTP协议的设计原理,了解HTTP请求和响应的格式,以及HTTP头部字段的含义。还可以学习HTTPS的加密算法和证书验证机制,保证网络请求的安全性。</li> <li data-list="ordered"><span class="ql-ui"></span>加解密方面:我们只使用了AES对称加密,大家可以尝试使用其他加密方式,比如RC加密,RSA、ECC等非对称加密方式。</li> </ol> <p><br></p> <p>除了以上的内容,我们还可以进一步拓展C++的知识领域。比如,可以学习C++的高级特性,如模板元编程、Lambda表达式、智能指针原理、左右值等。</p> <p><br></p> <p>此外,推荐大家阅读一些经典的C++书籍,以进一步提升编程技能和理解深度:</p> <p><br></p> <ol> <li data-list="bullet"><span class="ql-ui"></span>《C++20高级编程》它包含了许多C++的新特性,从入门到进阶都适合阅读。</li> <li data-list="bullet"><span class="ql-ui"></span>《C++ CoreGuidelines解析》它是C++之父的精华翻译,能够帮助我们更好地理解和应用C++的规范和最佳实践。</li> </ol> <p><br></p> <p>再推荐几个开源C++C++项目:</p> <p><br></p> <ol> <li data-list="ordered"><span class="ql-ui"></span>Taskflow:Taskflow是一个现代化的C++ flow 仓库,可以帮助我们实现并发任务的调度和执行。它提供的接口都很灵活,能够帮助我们更好地管理和控制任务流程。</li> <li data-list="ordered"><span class="ql-ui"></span>Chromium:应该是Google最优秀的开源库了吧,跨平台,很多浏览器都是基于Chromium二次开发的,内部有很多开发理念,都值得我们学习。</li> <li data-list="ordered"><span class="ql-ui"></span>Boost:可以简单理解为是C++标准库的后花园,但是比标准基础库拥有更多的能力,很多特性将来也会进入到C++标准中。</li> <li data-list="ordered"><span class="ql-ui"></span>spdlog:代码简单易读,想要实现日志系统的同学都可以参考学习它。</li> </ol> <p><br></p> <p>这些都是非常优秀的C++项目,我们可以学习到很多有价值的知识和经验,进一步提升我们的编程能力。</p> <p><br></p> <p>希望能够帮助大家更好地理解和应用C++技术。祝大家学习进步,编程愉快!</p> <p><br></p> <p>后面我计划分享一些C++项目源码分析的文章,大家如果有感兴趣的项目可以留言。</p> <p><br></p> </div> </body> </html>

15. 整体功能组装

<html> <head></head> <body> <div class="content ql-editor"> <p>大家好,我是程序喵。</p> <p><br></p> <p>在前面的章节中,我向大家详细介绍了项目中各个子模块的实现,包括公共信息获取模块、多线程模块、上报模块、数据库模块、加解密模块以及三方库等模块。</p> <p><br></p> <p>这些子模块就好比汽车的几个车轮,它们各自拥有特定的功能和作用,但单独运行时并不能完成整个系统的正常工作。就像一辆汽车需要所有车轮协同工作才能行驶一样,我们需要将这些子模块进行整合,形成一个core模块,来完成整体的埋点上报功能。</p> <p><br></p> <p>core模块就像是汽车的发动机,它负责将各个子模块串联起来,使其协同工作。通过有效的整合,能够更好地发挥各个子模块的功能,实现完整的埋点上报功能。</p> <p><br></p> <p>首先看下<a href="http://CMakeLists.txt" target="_blank">CMakeLists.txt</a>:</p> <p><br></p> <p><img src="https://pic.code-nav.cn/planet_post_image/1796153137144709122/jo6rzgqe.jpeg" style="display: block; margin: auto;"></p> <p><br></p> <p>这里将很多模块的源文件整体编译,生成一个静态库或者静态库。</p> <p><br></p> <p>其中的<a href="http://buried_core.cc" target="_blank">buried_core.cc</a>就可以理解为咱上面说的core模块,用于整合所有子模块。</p> <p><br></p> <p>看下buried_core.h中暴露的接口:</p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">struct</span> <span class="ql-token hljs-title">Buried</span> { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">public</span>: </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">struct</span> <span class="ql-token hljs-title">Config</span> { </div> <div class="ql-code-block"> std::string host; </div> <div class="ql-code-block"> std::string port; </div> <div class="ql-code-block"> std::string topic; </div> <div class="ql-code-block"> std::string user_id; </div> <div class="ql-code-block"> std::string app_version; </div> <div class="ql-code-block"> std::string app_name; </div> <div class="ql-code-block"> std::string custom_data; </div> <div class="ql-code-block"> }; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">public</span>: </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">Buried</span>(<span class="ql-token hljs-type">const</span> std::string&amp; work_dir); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> ~<span class="ql-token hljs-built_in">Buried</span>(); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-function">BuriedResult Start(const Config&amp; config)</span>; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-function">BuriedResult Report(std::string title, std::string data, uint32_t priority)</span>; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">public</span>: </div> <div class="ql-code-block"> <span class="ql-token hljs-function">std::shared_ptr&lt;spdlog::logger&gt; Logger()</span>; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">private</span>: </div> <div class="ql-code-block"> <span class="ql-token hljs-function">void InitWorkPath_(const std::string&amp; work_dir)</span>; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-function">void InitLogger_()</span>; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">private</span>: </div> <div class="ql-code-block"> std::shared_ptr&lt;spdlog::logger&gt; logger_; </div> <div class="ql-code-block"> std::unique_ptr&lt;buried::BuriedReport&gt; buried_report_; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> std::filesystem::path work_path_; </div> <div class="ql-code-block"> }; </div> </div> <p><br></p> <p>这段代码展示了C++埋点SDK的一些关键实现。首先,在buried_core.h中定义了一个名为Buried的结构体,其中包含了一些公共接口和私有成员变量。</p> <p><br></p> <p>公共接口包括:</p> <ol> <li data-list="bullet"><span class="ql-ui"></span>Buried(const std::string&amp; work_dir):使用工作目录构造Buried对象。</li> <li data-list="bullet"><span class="ql-ui"></span>~Buried():析构函数。</li> <li data-list="bullet"><span class="ql-ui"></span>BuriedResult Start(const Config&amp; config):通过Config启动整个对象的上报能力。</li> <li data-list="bullet"><span class="ql-ui"></span>BuriedResult Report(std::string title, std::string data, uint32_t priority):使用Report接口上报某条数据。</li> <li data-list="bullet"><span class="ql-ui"></span>std::shared_ptr&lt;spdlog::logger&gt; Logger():提供对外的Logger接口,用于获取此对象内部的Logger实例。</li> </ol> <p><br></p> <p>私有成员变量包括:</p> <ol> <li data-list="bullet"><span class="ql-ui"></span>std::shared_ptr&lt;spdlog::logger&gt; logger_:Logger对象。</li> <li data-list="bullet"><span class="ql-ui"></span>std::unique_ptr&lt;buried::BuriedReport&gt; buried_report_:BuriedReport对象。</li> <li data-list="bullet"><span class="ql-ui"></span>std::filesystem::path work_path_:工作路径。</li> </ol> <p><br></p> <p>通过调用公共接口,可以构造对象、启动上报能力,并上报数据。</p> <p><br></p> <p>再看core模块的具体实现</p> <div class="ql-code-block-container"> <div class="ql-code-block"> Buried::<span class="ql-token hljs-built_in">Buried</span>(<span class="ql-token hljs-type">const</span> std::string&amp; work_dir) { </div> <div class="ql-code-block"> buried::Context::<span class="ql-token hljs-built_in">GetGlobalContext</span>().<span class="ql-token hljs-built_in">Start</span>(); </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">InitWorkPath_</span>(work_dir); </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">InitLogger_</span>(); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">SPDLOG_LOGGER_INFO</span>(<span class="ql-token hljs-built_in">Logger</span>(), <span class="ql-token hljs-string">"Buried init success"</span>); </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p>构造函数Buried::Buried(const std::string&amp; work_dir)中,首先启动了Context模块,然后设置了工作目录和Logger对象。</p> <p><br></p> <p>InitLogger_方法</p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-function">void Buried::InitLogger_() </span>{ </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">auto</span> console_sink = std::<span class="ql-token hljs-built_in">make_shared</span>&lt;spdlog::sinks::stdout_color_sink_mt&gt;(); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> std::filesystem::path _log_dir = work_path_ / <span class="ql-token hljs-string">"buried.log"</span>; </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">auto</span> file_sink = std::<span class="ql-token hljs-built_in">make_shared</span>&lt;spdlog::sinks::basic_file_sink_mt&gt;( </div> <div class="ql-code-block"> _log_dir.<span class="ql-token hljs-built_in">string</span>(), <span class="ql-token hljs-literal">true</span>); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> logger_ = std::<span class="ql-token hljs-built_in">shared_ptr</span>&lt;spdlog::logger&gt;( </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">new</span> spdlog::<span class="ql-token hljs-built_in">logger</span>(<span class="ql-token hljs-string">"buried_sink"</span>, {console_sink, file_sink})); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">// ref: &lt;https://github.com/gabime/spdlog/wiki/3.-Custom-formatting&gt;</span> </div> <div class="ql-code-block"> logger_-&gt;<span class="ql-token hljs-built_in">set_pattern</span>(<span class="ql-token hljs-string">"[%c] [%s:%#] [%l] %v"</span>); </div> <div class="ql-code-block"> logger_-&gt;<span class="ql-token hljs-built_in">set_level</span>(spdlog::level::trace); </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p>在InitLogger_方法中,首先创建了一个用于控制台输出的console_sink对象,并将其与文件输出的sinks::basic_file_sink_mt对象一起传递给logger_对象的构造函数。文件输出的路径为工作目录下的<a href="http://buried.log" target="_blank">buried.log</a>文件。</p> <p><br></p> <p>然后,通过set_pattern方法设置了日志的格式,包括时间戳、文件名、行号和日志内容等。最后,设置了日志的级别为trace级别,即最详细的日志级别。</p> <p><br></p> <p>这样,Logger对象就构造完成了,可以通过调用Logger()方法获取到此对象。</p> <p><br></p> <p>Start方法</p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-function">BuriedResult Buried::Start(const Config&amp; config) </span>{ </div> <div class="ql-code-block"> buried::CommonService common_service; </div> <div class="ql-code-block"> common_service.host = config.host; </div> <div class="ql-code-block"> common_service.port = config.port; </div> <div class="ql-code-block"> common_service.topic = config.topic; </div> <div class="ql-code-block"> common_service.user_id = config.user_id; </div> <div class="ql-code-block"> common_service.app_version = config.app_version; </div> <div class="ql-code-block"> common_service.app_name = config.app_name; </div> <div class="ql-code-block"> common_service.custom_data = nlohmann::json::<span class="ql-token hljs-built_in">parse</span>(config.custom_data); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> buried_report_ = std::<span class="ql-token hljs-built_in">make_unique</span>&lt;buried::BuriedReport&gt;( </div> <div class="ql-code-block"> logger_, std::<span class="ql-token hljs-built_in">move</span>(common_service), work_path_.<span class="ql-token hljs-built_in">string</span>()); </div> <div class="ql-code-block"> buried_report_-&gt;<span class="ql-token hljs-built_in">Start</span>(); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> BuriedResult::kBuriedOk; </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p>Start方法接收一个Config对象作为参数。首先,它创建了一个buried::CommonService对象,并将config中的各项配置赋值给common_service的对应成员变量。然后,它使用logger_、common_service和<a href="http://work_path_.string" target="_blank">work_path_.string</a>()作为参数,创建了一个新的buried::BuriedReport对象,最后,调用buried_report_的Start方法,启动了埋点上报功能。</p> <p><br></p> <p>通过将配置信息传递给CommonService对象,能够获取到上报所需的host、port、topic、user id、app version、app name和custom data等信息。然后,SDK使用这些信息初始化BuriedReport对象,才启动的埋点上报功能。</p> <p><br></p> <p>Report方法:</p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-function">BuriedResult Buried::Report(std::string title, std::string data,</span> </div> <div class="ql-code-block"><span class="ql-token hljs-function"> uint32_t priority) </span>{ </div> <div class="ql-code-block"> buried::BuriedData buried_data; </div> <div class="ql-code-block"> buried_data.title = std::<span class="ql-token hljs-built_in">move</span>(title); </div> <div class="ql-code-block"> buried_data.data = std::<span class="ql-token hljs-built_in">move</span>(data); </div> <div class="ql-code-block"> buried_data.priority = priority; </div> <div class="ql-code-block"> buried_report_-&gt;<span class="ql-token hljs-built_in">InsertData</span>(buried_data); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> BuriedResult::kBuriedOk; </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p>这个方法接受三个参数:title、data和priority。首先,它创建一个BuriedData对象,然后将title和data移动到BuriedData对象的对应成员变量中。接下来,它将priority赋值给BuriedData对象的priority成员变量。最后,通过调用buried_report_对象的InsertData方法,将BuriedData对象传递给BuriedReport对象,实现数据的上报功能。</p> <p><br></p> <p>到这里,整个项目代码已经完成了组装,你应该能够实现一个完整的埋点SDK功能。不知道你是否已经尝试运行整个项目。</p> <p><br></p> <p>详细代码请参考:<a href="https://github.com/chengxumiaodaren/BuriedPoint/blob/main/src/buried_core.cc" target="_blank">https://github.com/chengxumiaodaren/BuriedPoint/blob/main/src/buried_core.cc</a></p> <p><br></p> </div> </body> </html>

14. 公共信息获取

<html> <head></head> <body> <div class="content ql-editor"> <p>大家好,我是程序喵。</p> <p><br></p> <p>上一章节中,我介绍了项目中上报模块的实现,本节我们来点轻松的,介绍下如何获取公共信息。</p> <p><br></p> <p>为什么要获取公共信息?</p> <p><br></p> <p>上报每一条埋点数据时,我们其实最好还是需要拼接一些其他公共信息和系统信息,这样才方便我们排查问题。</p> <p><br></p> <p>具体要获取的信息字段,我其实在前面章节中有过介绍,这里再简单贴一下:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1796153137144709122/5m7z0p22.jpeg"></p> <p>这其中很多有一些字段需要用户配置进来,但也有些字段需要我们内部自己获取。</p> <p><br></p> <p>比如:</p> <ol> <li data-list="bullet"><span class="ql-ui"></span>system_version</li> <li data-list="bullet"><span class="ql-ui"></span>device_name</li> <li data-list="bullet"><span class="ql-ui"></span>device_id</li> <li data-list="bullet"><span class="ql-ui"></span>buried_version</li> <li data-list="bullet"><span class="ql-ui"></span>lifecycle_id</li> <li data-list="bullet"><span class="ql-ui"></span>timestamp</li> <li data-list="bullet"><span class="ql-ui"></span>process_time</li> </ol> <p><br></p> <p>本节我就主要介绍如何获取和生成这些字段值。</p> <p><br></p> <p><strong>system_version</strong></p> <p>由于我们这个项目是针对Windows平台,所以需要获取Windows的系统版本,可以直接调用系统的API,详见代码:</p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-function">static std::string GetSystemVersion() </span>{ </div> <div class="ql-code-block"> OSVERSIONINFOEXA os_version_info; </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">ZeroMemory</span>(&amp;os_version_info, <span class="ql-token hljs-built_in">sizeof</span>(OSVERSIONINFOEXA)); </div> <div class="ql-code-block"> os_version_info.dwOSVersionInfoSize = <span class="ql-token hljs-built_in">sizeof</span>(OSVERSIONINFOEXA); </div> <div class="ql-code-block"> ::<span class="ql-token hljs-built_in">GetVersionExA</span>(<span class="ql-token hljs-built_in">reinterpret_cast</span>&lt;OSVERSIONINFOA*&gt;(&amp;os_version_info)); </div> <div class="ql-code-block"> std::string system_version = </div> <div class="ql-code-block"> std::<span class="ql-token hljs-built_in">to_string</span>(os_version_info.dwMajorVersion) + <span class="ql-token hljs-string">"."</span> + </div> <div class="ql-code-block"> std::<span class="ql-token hljs-built_in">to_string</span>(os_version_info.dwMinorVersion) + <span class="ql-token hljs-string">"."</span> + </div> <div class="ql-code-block"> std::<span class="ql-token hljs-built_in">to_string</span>(os_version_info.dwBuildNumber); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> system_version; </div> <div class="ql-code-block"> } </div> </div> <p>通过GetVersionExA方法给os_version_info赋值,然后将major、minor、build三个字段拼接即可。</p> <p><br></p> <p><strong>device_name</strong></p> <p>device_name和system_version类似,都和操作系统强相关,这里也需要使用Windows API获取:</p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-function">static std::string GetDeviceName() </span>{ </div> <div class="ql-code-block"> <span class="ql-token hljs-type">char</span> buf[<span class="ql-token hljs-number">1024</span>] = {<span class="ql-token hljs-number">0</span>}; </div> <div class="ql-code-block"> DWORD buf_size = <span class="ql-token hljs-built_in">sizeof</span>(buf); </div> <div class="ql-code-block"> ::<span class="ql-token hljs-built_in">GetComputerNameA</span>(buf, &amp;buf_size); </div> <div class="ql-code-block"> std::string device_name = buf; </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> device_name; </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p><strong>device_id</strong></p> <p>device_id作为设备唯一的ID,它稍微麻烦一些,需要我们先生成一个随机数,作为设备ID,存到设备的某个固定的位置,后面每次都从这个位置读取,这样才可以保证重启程序或者重启设备后,获取到的device_id是一致的。</p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-function">static std::string GetDeviceId() </span>{ </div> <div class="ql-code-block"> <span class="ql-token hljs-type">static</span> <span class="ql-token hljs-keyword">constexpr</span> <span class="ql-token hljs-keyword">auto</span> kDeviceIdKey = <span class="ql-token hljs-string">"device_id"</span>; </div> <div class="ql-code-block"> <span class="ql-token hljs-type">static</span> std::string device_id = <span class="ql-token hljs-built_in">ReadRegister</span>(kDeviceIdKey); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">if</span> (device_id.<span class="ql-token hljs-built_in">empty</span>()) { </div> <div class="ql-code-block"> device_id = CommonService::<span class="ql-token hljs-built_in">GetRandomId</span>(); </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">WriteRegister</span>(kDeviceIdKey, device_id); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> device_id; </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p>存到哪里呢?Windows下我们可以存到注册表里,正常用户一般不会查看和改动注册表,这里还涉及到注册表相关的编码:</p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-function">static void WriteRegister(const std::string&amp; key, const std::string&amp; value) </span>{ </div> <div class="ql-code-block"> HKEY h_key; </div> <div class="ql-code-block"> LONG ret = ::<span class="ql-token hljs-built_in">RegCreateKeyExA</span>(HKEY_CURRENT_USER, <span class="ql-token hljs-string">"Software\\\\Buried"</span>, <span class="ql-token hljs-number">0</span>, <span class="ql-token hljs-literal">NULL</span>, </div> <div class="ql-code-block"> REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, <span class="ql-token hljs-literal">NULL</span>, </div> <div class="ql-code-block"> &amp;h_key, <span class="ql-token hljs-literal">NULL</span>); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">if</span> (ret != ERROR_SUCCESS) { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span>; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> ret = ::<span class="ql-token hljs-built_in">RegSetValueExA</span>(h_key, key.<span class="ql-token hljs-built_in">c_str</span>(), <span class="ql-token hljs-number">0</span>, REG_SZ, </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">reinterpret_cast</span>&lt;<span class="ql-token hljs-type">const</span> BYTE*&gt;(value.<span class="ql-token hljs-built_in">c_str</span>()), </div> <div class="ql-code-block"> value.<span class="ql-token hljs-built_in">size</span>()); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">if</span> (ret != ERROR_SUCCESS) { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span>; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> ::<span class="ql-token hljs-built_in">RegCloseKey</span>(h_key); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"><span class="ql-token hljs-function">static std::string ReadRegister(const std::string&amp; key) </span>{ </div> <div class="ql-code-block"> HKEY h_key; </div> <div class="ql-code-block"> LONG ret = ::<span class="ql-token hljs-built_in">RegOpenKeyExA</span>(HKEY_CURRENT_USER, <span class="ql-token hljs-string">"Software\\\\Buried"</span>, <span class="ql-token hljs-number">0</span>, </div> <div class="ql-code-block"> KEY_ALL_ACCESS, &amp;h_key); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">if</span> (ret != ERROR_SUCCESS) { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> <span class="ql-token hljs-string">""</span>; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> <span class="ql-token hljs-type">char</span> buf[<span class="ql-token hljs-number">1024</span>] = {<span class="ql-token hljs-number">0</span>}; </div> <div class="ql-code-block"> DWORD buf_size = <span class="ql-token hljs-built_in">sizeof</span>(buf); </div> <div class="ql-code-block"> ret = ::<span class="ql-token hljs-built_in">RegQueryValueExA</span>(h_key, key.<span class="ql-token hljs-built_in">c_str</span>(), <span class="ql-token hljs-literal">NULL</span>, <span class="ql-token hljs-literal">NULL</span>, </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">reinterpret_cast</span>&lt;BYTE*&gt;(buf), &amp;buf_size); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">if</span> (ret != ERROR_SUCCESS) { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> <span class="ql-token hljs-string">""</span>; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> ::<span class="ql-token hljs-built_in">RegCloseKey</span>(h_key); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> buf; </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p>代码中可以看到,我们将相关数据存到了HKEY_CURRENT_USER\Software\Buried下面。</p> <p><br></p> <p>写完代码运行后,我们也可以打开Windows的注册表,查看是否存储成功。</p> <p><br></p> <p><img src="https://pic.code-nav.cn/planet_post_image/1796153137144709122/4cd3zd6u.jpeg" style="display: block; margin: auto;"></p> <p><br></p> <p><br></p> <p><strong>buried_version</strong></p> <p>用这个version表示SDK的版本号,这个版本号我们通过CMake生成,可以看到项目src目录下有个<a href="http://buried_config.h.in" target="_blank">buried_config.h.in</a>文件,内容如下:</p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-meta">#pragma once</span> </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"><span class="ql-token hljs-meta">#define PROJECT_NAME "@PROJECT_NAME@"</span> </div> <div class="ql-code-block"><span class="ql-token hljs-meta">#define PROJECT_VER "@PROJECT_VERSION@"</span> </div> </div> <p><br></p> <p>那如何使用这个文件呢?再看src目录下的<a href="http://CMakelists.txt" target="_blank">CMakelists.txt</a>文件,有一行这样的代码:</p> <div class="ql-code-block-container"> <div class="ql-code-block"> configure_file(${CMAKE_CURRENT_SOURCE_DIR}/buried_config.h.in ${CMAKE_CURRENT_SOURCE_DIR}/buried_config.h) </div> </div> <p><br></p> <p>根目录的<a href="http://CMakeLists.txt" target="_blank">CMakeLists.txt</a>就设置了项目的PROJECT_NAME和VERSION:</p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-built_in">cmake_minimum_required</span>(VERSION <span class="ql-token hljs-number">3.20</span>) </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"><span class="ql-token hljs-built_in">set</span>(PROJECT_NAME <span class="ql-token hljs-string">"BuriedPoint"</span>) </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"><span class="ql-token hljs-built_in">project</span>(${PROJECT_NAME} VERSION <span class="ql-token hljs-number">1.1.1.1</span>) </div> </div> <p><br></p> <p>这样通过cmake编译后,就会自动生成build_config.h的头文件,我们只需include这个头文件,就可以获取buried_version:</p> <div class="ql-code-block-container"> <div class="ql-code-block"> buried_version = PROJECT_VER; </div> </div> <p><br></p> <p><strong>lifecycle_id</strong></p> <p>标识某一进程的id,这个好弄,一个static的随机数即可:</p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-function">static std::string GetLifeCycleId() </span>{ </div> <div class="ql-code-block"> <span class="ql-token hljs-type">static</span> std::string life_cycle_id = CommonService::<span class="ql-token hljs-built_in">GetRandomId</span>(); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> life_cycle_id; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"><span class="ql-token hljs-function">std::string CommonService::GetRandomId() </span>{ </div> <div class="ql-code-block"> <span class="ql-token hljs-type">static</span> <span class="ql-token hljs-keyword">constexpr</span> <span class="ql-token hljs-type">size_t</span> len = <span class="ql-token hljs-number">32</span>; </div> <div class="ql-code-block"> <span class="ql-token hljs-type">static</span> <span class="ql-token hljs-keyword">constexpr</span> <span class="ql-token hljs-keyword">auto</span> chars = </div> <div class="ql-code-block"> <span class="ql-token hljs-string">"0123456789"</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-string">"ABCDEFGHIJKLMNOPQRSTUVWXYZ"</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-string">"abcdefghijklmnopqrstuvwxyz"</span>; </div> <div class="ql-code-block"> <span class="ql-token hljs-type">static</span> std::mt19937_64 rng{std::random_device{}()}; </div> <div class="ql-code-block"> <span class="ql-token hljs-type">static</span> std::uniform_int_distribution&lt;<span class="ql-token hljs-type">size_t</span>&gt; dist{<span class="ql-token hljs-number">0</span>, <span class="ql-token hljs-number">60</span>}; </div> <div class="ql-code-block"> std::string result; </div> <div class="ql-code-block"> result.<span class="ql-token hljs-built_in">reserve</span>(len); </div> <div class="ql-code-block"> std::<span class="ql-token hljs-built_in">generate_n</span>(std::<span class="ql-token hljs-built_in">back_inserter</span>(result), len, </div> <div class="ql-code-block"> [&amp;]() { <span class="ql-token hljs-keyword">return</span> chars[<span class="ql-token hljs-built_in">dist</span>(rng)]; }); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> result; </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p><strong>process_time</strong></p> <p>获取进程的运行时间直接调用Windows的API方法即可,不过Windows的GetProcessTimes获取出来的时间是FILETIME,还需要我们自己转成SystemTime才可以,具体可以看下面的代码:</p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-function">std::string CommonService::GetProcessTime() </span>{ </div> <div class="ql-code-block"> DWORD pid = ::<span class="ql-token hljs-built_in">GetCurrentProcessId</span>(); </div> <div class="ql-code-block"> HANDLE h_process = </div> <div class="ql-code-block"> ::<span class="ql-token hljs-built_in">OpenProcess</span>(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">if</span> (h_process == <span class="ql-token hljs-literal">NULL</span>) { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> <span class="ql-token hljs-string">""</span>; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> FILETIME create_time; </div> <div class="ql-code-block"> FILETIME exit_time; </div> <div class="ql-code-block"> FILETIME kernel_time; </div> <div class="ql-code-block"> FILETIME user_time; </div> <div class="ql-code-block"> BOOL ret = ::<span class="ql-token hljs-built_in">GetProcessTimes</span>(h_process, &amp;create_time, &amp;exit_time, </div> <div class="ql-code-block"> &amp;kernel_time, &amp;user_time); </div> <div class="ql-code-block"> ::<span class="ql-token hljs-built_in">CloseHandle</span>(h_process); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">if</span> (ret == <span class="ql-token hljs-number">0</span>) { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> <span class="ql-token hljs-string">""</span>; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> FILETIME create_local_time; </div> <div class="ql-code-block"> ::<span class="ql-token hljs-built_in">FileTimeToLocalFileTime</span>(&amp;create_time, &amp;create_local_time); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> SYSTEMTIME create_sys_time; </div> <div class="ql-code-block"> ::<span class="ql-token hljs-built_in">FileTimeToSystemTime</span>(&amp;create_local_time, &amp;create_sys_time); </div> <div class="ql-code-block"> <span class="ql-token hljs-type">char</span> buf[<span class="ql-token hljs-number">128</span>] = {<span class="ql-token hljs-number">0</span>}; </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">// year month day hour minute second millisecond</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">sprintf_s</span>(buf, <span class="ql-token hljs-string">"%04d-%02d-%02d %02d:%02d:%02d.%03d"</span>, create_sys_time.wYear, </div> <div class="ql-code-block"> create_sys_time.wMonth, create_sys_time.wDay, create_sys_time.wHour, </div> <div class="ql-code-block"> create_sys_time.wMinute, create_sys_time.wSecond, </div> <div class="ql-code-block"> create_sys_time.wMilliseconds); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> buf; </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p>有了这些公共信息后,上报的埋点才更有意义,更方便我们排查问题。</p> <p>到这里,整个公共信息模块已经介绍完毕,下期见。</p> </div> </body> </html>

13. 上报模块实现

<html> <head></head> <body> <div class="content ql-editor"> <p>大家好,我是程序喵。</p> <p><br></p> <p>在前面几期中,我已经介绍过如何发起http通信,如何实现加解密功能,如何进行多线程开发,这一期,我会把这些能力整合到一起,完成整个上报模块。</p> <p><br></p> <p>在写代码前,首先要梳理出上报模块需要提供什么能力。我的设计是,上报模块可以提供一个开关,只要打开开关,塞进来的消息就会每隔一段时间自动上报,同时,之前由于网络等原因上报失败的数据,也会自动从数据库中提取出来并上报。</p> <p><br></p> <p>核心点就是:</p> <ol> <li data-list="bullet"><span class="ql-ui"></span>有开关,需要每隔一段时间自动上报</li> <li data-list="bullet"><span class="ql-ui"></span>上报失败的数据,需要有重试</li> </ol> <p><br></p> <p>梳理后,看下对应的头文件代码:</p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">struct</span> <span class="ql-token hljs-title">BuriedData</span> { </div> <div class="ql-code-block"> std::string title; </div> <div class="ql-code-block"> std::string data; </div> <div class="ql-code-block"> <span class="ql-token hljs-type">uint32_t</span> priority; </div> <div class="ql-code-block"> }; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"><span class="ql-token hljs-keyword">class</span> <span class="ql-token hljs-title">BuriedReportImpl</span>; </div> <div class="ql-code-block"><span class="ql-token hljs-keyword">class</span> <span class="ql-token hljs-title">BuriedReport</span> { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">public</span>: </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">BuriedReport</span>(std::shared_ptr&lt;spdlog::logger&gt; logger, </div> <div class="ql-code-block"> CommonService common_service, std::string work_path); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> ~<span class="ql-token hljs-built_in">BuriedReport</span>(); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-function">void Start()</span>; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-function">void InsertData(const BuriedData&amp; data)</span>; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">private</span>: </div> <div class="ql-code-block"> std::unique_ptr&lt;BuriedReportImpl&gt; impl_; </div> <div class="ql-code-block"> }; </div> </div> <p><br></p> <p>头文件代码中有几个关键点:</p> <ol> <li data-list="ordered"><span class="ql-ui"></span>BuriedData的结构定义:包含优先级、标题和详细数据,这三个由外部调用方传入具体数据</li> <li data-list="ordered"><span class="ql-ui"></span>构造函数中的logger、common_service、work_path:因为整个SDK需要支持多实例,所以里面的各个模块,需要传递进来实际的子模块实例对象和工作目录。</li> <li data-list="ordered"><span class="ql-ui"></span>Start接口:整个上报模块的开关,调用此接口后,会自动获取数据并上报。</li> <li data-list="ordered"><span class="ql-ui"></span>InsertData接口:外部调用方想要上报某条数据就调用此接口,然后模块内部会自动挑时机上报这条数据,无需担心上报成功与否,只要调用此接口,数据就会上报成功。</li> <li data-list="ordered"><span class="ql-ui"></span>BuriedReportImpl:pimpl模式,C++中的一个很常用的设计模式,相关的实现都放在源文件中,不暴露到外边,这个模式建议大家重点学习。</li> </ol> <p><br></p> <p>然后我们再看它的实现:</p> <div class="ql-code-block-container"> <div class="ql-code-block"> BuriedReport::<span class="ql-token hljs-built_in">BuriedReport</span>(std::shared_ptr&lt;spdlog::logger&gt; logger, </div> <div class="ql-code-block"> CommonService common_service, std::string work_path) </div> <div class="ql-code-block"> : <span class="ql-token hljs-built_in">impl_</span>(std::<span class="ql-token hljs-built_in">make_unique</span>&lt;BuriedReportImpl&gt;( </div> <div class="ql-code-block"> std::<span class="ql-token hljs-built_in">move</span>(logger), std::<span class="ql-token hljs-built_in">move</span>(common_service), std::<span class="ql-token hljs-built_in">move</span>(work_path))) { </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"><span class="ql-token hljs-function">void BuriedReport::Start() </span>{ impl_-&gt;<span class="ql-token hljs-built_in">Start</span>(); } </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"><span class="ql-token hljs-function">void BuriedReport::InsertData(const BuriedData&amp; data) </span>{ </div> <div class="ql-code-block"> impl_-&gt;<span class="ql-token hljs-built_in">InsertData</span>(data); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> BuriedReport::~<span class="ql-token hljs-built_in">BuriedReport</span>() {} </div> </div> <p><br></p> <p>这里就是使用的pimpl模式,BuriedReport相关实现其实是调用了BuriedReportImpl的相关方法。</p> <p><br></p> <p>BuriedReportImpl类的定义如下:</p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-type">static</span> <span class="ql-token hljs-type">const</span> <span class="ql-token hljs-type">char</span> kDbName[] = <span class="ql-token hljs-string">"buried.db"</span>; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"><span class="ql-token hljs-keyword">class</span> <span class="ql-token hljs-title">BuriedReportImpl</span> { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">public</span>: </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">BuriedReportImpl</span>(std::shared_ptr&lt;spdlog::logger&gt; logger, </div> <div class="ql-code-block"> CommonService common_service, std::string work_path); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> ~<span class="ql-token hljs-built_in">BuriedReportImpl</span>() = <span class="ql-token hljs-keyword">default</span>; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-function">void Start()</span>; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-function">void InsertData(const BuriedData&amp; data)</span>; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">private</span>: </div> <div class="ql-code-block"> <span class="ql-token hljs-function">void Init_()</span>; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-function">void ReportCache_()</span>; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-function">void NextCycle_()</span>; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-function">BuriedDb::Data MakeDbData_(const BuriedData&amp; data)</span>; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-function">std::string GenReportData_(const std::vector&lt;BuriedDb::Data&gt;&amp; datas)</span>; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-function">bool ReportData_(const std::string&amp; data)</span>; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">private</span>: </div> <div class="ql-code-block"> std::shared_ptr&lt;spdlog::logger&gt; logger_; </div> <div class="ql-code-block"> std::string work_dir_; </div> <div class="ql-code-block"> std::unique_ptr&lt;BuriedDb&gt; db_; </div> <div class="ql-code-block"> CommonService common_service_; </div> <div class="ql-code-block"> std::unique_ptr&lt;buried::Crypt&gt; crypt_; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> std::unique_ptr&lt;boost::asio::deadline_timer&gt; timer_; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> std::vector&lt;BuriedDb::Data&gt; data_caches_; </div> <div class="ql-code-block"> }; </div> </div> <p><br></p> <p>整个类不是只有Start和InsertData的public方法,还有好多个private方法,供类内部调用。</p> <p><br></p> <p>内部还有很多成员变量:</p> <ol> <li data-list="bullet"><span class="ql-ui"></span>logger_:打印日志的实例</li> <li data-list="bullet"><span class="ql-ui"></span>work_dir_:工作目录</li> <li data-list="bullet"><span class="ql-ui"></span>db_:数据库实例,数据库相关的方法都通过此实例调用</li> <li data-list="bullet"><span class="ql-ui"></span>common_service_:公共数据通过此实例获取</li> <li data-list="bullet"><span class="ql-ui"></span>crypt_:加解密相关的能力通过此实例调用</li> <li data-list="bullet"><span class="ql-ui"></span>timer_:定时器相关的能力使用此实例</li> <li data-list="bullet"><span class="ql-ui"></span>data_caches_:装载要上报的数据,会不停的从数据库中按优先级取数据,装载到这个cache中</li> </ol> <p><br></p> <p>构造函数</p> <div class="ql-code-block-container"> <div class="ql-code-block"> BuriedReportImpl(<span class="ql-token hljs-built_in">std</span>::<span class="ql-token hljs-built_in">shared_ptr</span>&lt;spdlog::logger&gt; logger, </div> <div class="ql-code-block"> CommonService common_service, <span class="ql-token hljs-built_in">std</span>::<span class="ql-token hljs-built_in">string</span> work_path) </div> <div class="ql-code-block"> : logger_(<span class="ql-token hljs-built_in">std</span>::move(logger)), </div> <div class="ql-code-block"> common_service_(<span class="ql-token hljs-built_in">std</span>::move(common_service)), </div> <div class="ql-code-block"> work_dir_(<span class="ql-token hljs-built_in">std</span>::move(work_path)) { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">if</span> (logger_ == nullptr) { </div> <div class="ql-code-block"> logger_ = spdlog::stdout_color_mt(<span class="ql-token hljs-string">"buried"</span>); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">std</span>::<span class="ql-token hljs-built_in">string</span> key = AESCrypt::GetKey(<span class="ql-token hljs-string">"buried_salt"</span>, <span class="ql-token hljs-string">"buried_password"</span>); </div> <div class="ql-code-block"> crypt_ = <span class="ql-token hljs-built_in">std</span>::make_unique&lt;AESCrypt&gt;(key); </div> <div class="ql-code-block"> SPDLOG_LOGGER_INFO(logger_, <span class="ql-token hljs-string">"BuriedReportImpl init success"</span>); </div> <div class="ql-code-block"> Context::GetGlobalContext().GetReportStrand().post([this]() { Init_(); }); </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p>从代码中,可以看到,构造或赋值了logger和crypt对象后,通过ReportStrand调度Init_方法,看了后面的几个方法的实现后,大家就会发现整个report相关的方法都会通过ReportStrand调度,也就是说它既是在子线程中调用,又确保了所有与report相关的方法都在一个线程顺序实现,也就无需加锁处理,无需处理数据竞争问题。</p> <p><br></p> <p>Init_方法</p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-function">void BuriedReportImpl::Init_() </span>{ </div> <div class="ql-code-block"> std::filesystem::path db_path = work_dir_; </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">SPDLOG_LOGGER_INFO</span>(logger_, <span class="ql-token hljs-string">"BuriedReportImpl init db path: {}"</span>, </div> <div class="ql-code-block"> db_path.<span class="ql-token hljs-built_in">string</span>()); </div> <div class="ql-code-block"> db_path /= kDbName; </div> <div class="ql-code-block"> db_ = std::<span class="ql-token hljs-built_in">make_unique</span>&lt;BuriedDb&gt;(db_path.<span class="ql-token hljs-built_in">string</span>()); </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p>在此方法中,会传入数据库路径,构建db数据库对象。</p> <p><br></p> <p>Start方法</p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-function">void BuriedReportImpl::Start() </span>{ </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">SPDLOG_LOGGER_INFO</span>(logger_, <span class="ql-token hljs-string">"BuriedReportImpl start"</span>); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> timer_ = std::<span class="ql-token hljs-built_in">make_unique</span>&lt;boost::asio::deadline_timer&gt;( </div> <div class="ql-code-block"> Context::<span class="ql-token hljs-built_in">GetGlobalContext</span>().<span class="ql-token hljs-built_in">GetMainContext</span>(), </div> <div class="ql-code-block"> boost::posix_time::<span class="ql-token hljs-built_in">seconds</span>(<span class="ql-token hljs-number">5</span>)); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> timer_-&gt;<span class="ql-token hljs-built_in">async_wait</span>(Context::<span class="ql-token hljs-built_in">GetGlobalContext</span>().<span class="ql-token hljs-built_in">GetReportStrand</span>().<span class="ql-token hljs-built_in">wrap</span>( </div> <div class="ql-code-block"> [<span class="ql-token hljs-keyword">this</span>](<span class="ql-token hljs-type">const</span> boost::system::error_code&amp; ec) { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">if</span> (ec) { </div> <div class="ql-code-block"> logger_-&gt;<span class="ql-token hljs-built_in">error</span>(<span class="ql-token hljs-string">"BuriedReportImpl::Start error: {}"</span>, ec.<span class="ql-token hljs-built_in">message</span>()); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span>; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">ReportCache_</span>(); </div> <div class="ql-code-block"> })); </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p>在Start方法中,通过asio中的deadline_timer创建了一个定时器,并约定在5s后调用ReportCache_方法。</p> <p><br></p> <p>ps:asio中的这个定时器我个人感觉挺难用,不过这个项目中,为了保持简洁,不引入其他复杂的代码,我最终还是选择使用它,大家自己的项目中,可以考虑自己实现一个定时器和strand。</p> <p><br></p> <p>再看ReportCache_方法</p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-function">void BuriedReportImpl::ReportCache_() </span>{ </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">SPDLOG_LOGGER_INFO</span>(logger_, <span class="ql-token hljs-string">"BuriedReportImpl report cache"</span>); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">if</span> (data_caches_.<span class="ql-token hljs-built_in">empty</span>()) { </div> <div class="ql-code-block"> data_caches_ = db_-&gt;<span class="ql-token hljs-built_in">QueryData</span>(<span class="ql-token hljs-number">10</span>); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">if</span> (!data_caches_.<span class="ql-token hljs-built_in">empty</span>()) { </div> <div class="ql-code-block"> std::string report_data = <span class="ql-token hljs-built_in">GenReportData_</span>(data_caches_); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">if</span> (<span class="ql-token hljs-built_in">ReportData_</span>(report_data)) { </div> <div class="ql-code-block"> db_-&gt;<span class="ql-token hljs-built_in">DeleteDatas</span>(data_caches_); </div> <div class="ql-code-block"> data_caches_.<span class="ql-token hljs-built_in">clear</span>(); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">NextCycle_</span>(); </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p>每次调用此方法时,会通过db对象从数据库中按优先级取10条数据,再通过GenReportData_构造网络上报的body,再调用ReportData_上报数据,上报成功后通过db对象从数据库中删除对应数据,并清空data_caches_,防止重复上报,然后调用NexeCycle_启动下一次定时任务。</p> <p><br></p> <p>如何构造网络上报的body</p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-function">std::string BuriedReportImpl::GenReportData_(</span> </div> <div class="ql-code-block"><span class="ql-token hljs-function"> const std::vector&lt;BuriedDb::Data&gt;&amp; datas) </span>{ </div> <div class="ql-code-block"> nlohmann::json json_datas; </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">for</span> (<span class="ql-token hljs-type">const</span> <span class="ql-token hljs-keyword">auto</span>&amp; data : datas) { </div> <div class="ql-code-block"> std::string content = </div> <div class="ql-code-block"> crypt_-&gt;<span class="ql-token hljs-built_in">Decrypt</span>(data.content.<span class="ql-token hljs-built_in">data</span>(), data.content.<span class="ql-token hljs-built_in">size</span>()); </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">SPDLOG_LOGGER_INFO</span>(logger_, <span class="ql-token hljs-string">"BuriedReportImpl report data content size: {}"</span>, </div> <div class="ql-code-block"> data.content.<span class="ql-token hljs-built_in">size</span>()); </div> <div class="ql-code-block"> json_datas.<span class="ql-token hljs-built_in">push_back</span>(content); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> std::string ret = json_datas.<span class="ql-token hljs-built_in">dump</span>(); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> ret; </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p>因为数据库中拿出来的10条数据都是被加密过的,所以取出来后我们需要先进行解密操作,然后通过json构造成一个json array,最后再序列化成string,也就是网络上报的body。</p> <p><br></p> <p>如何启动下一次循环任务</p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-function">void BuriedReportImpl::NextCycle_() </span>{ </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">SPDLOG_LOGGER_INFO</span>(logger_, <span class="ql-token hljs-string">"BuriedReportImpl next cycle"</span>); </div> <div class="ql-code-block"> timer_-&gt;<span class="ql-token hljs-built_in">expires_at</span>(timer_-&gt;<span class="ql-token hljs-built_in">expires_at</span>() + boost::posix_time::<span class="ql-token hljs-built_in">seconds</span>(<span class="ql-token hljs-number">5</span>)); </div> <div class="ql-code-block"> timer_-&gt;<span class="ql-token hljs-built_in">async_wait</span>([<span class="ql-token hljs-keyword">this</span>](<span class="ql-token hljs-type">const</span> boost::system::error_code&amp; ec) { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">if</span> (ec) { </div> <div class="ql-code-block"> logger_-&gt;<span class="ql-token hljs-built_in">error</span>(<span class="ql-token hljs-string">"BuriedReportImpl::NextCycle_ error: {}"</span>, ec.<span class="ql-token hljs-built_in">message</span>()); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span>; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> Context::<span class="ql-token hljs-built_in">GetGlobalContext</span>().<span class="ql-token hljs-built_in">GetReportStrand</span>().<span class="ql-token hljs-built_in">post</span>( </div> <div class="ql-code-block"> [<span class="ql-token hljs-keyword">this</span>]() { <span class="ql-token hljs-built_in">ReportCache_</span>(); }); </div> <div class="ql-code-block"> }); </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p>还是使用asio的定时器,约定5s后再次调用ReportCache_方法即可。</p> <p><br></p> <p>用户如何向上报模块插入数据</p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-function">void BuriedReportImpl::InsertData(const BuriedData&amp; data) </span>{ </div> <div class="ql-code-block"> Context::<span class="ql-token hljs-built_in">GetGlobalContext</span>().<span class="ql-token hljs-built_in">GetReportStrand</span>().<span class="ql-token hljs-built_in">post</span>( </div> <div class="ql-code-block"> [<span class="ql-token hljs-keyword">this</span>, data]() { db_-&gt;<span class="ql-token hljs-built_in">InsertData</span>(<span class="ql-token hljs-built_in">MakeDbData_</span>(data)); }); </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p>首先需要切线程,统一放到ReportStrand模块调度任务,因为我们也不知道外部究竟是在哪个线程调用的此方法,如果不是相同的Strand,就会产生数据竞争的问题,所以这里需要通过ReportStrand post对应的任务。</p> <p><br></p> <p>如何将数据存入数据库中</p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-function">BuriedDb::Data BuriedReportImpl::MakeDbData_(const BuriedData&amp; data) </span>{ </div> <div class="ql-code-block"> BuriedDb::Data db_data; </div> <div class="ql-code-block"> db_data.id = <span class="ql-token hljs-number">-1</span>; </div> <div class="ql-code-block"> db_data.priority = data.priority; </div> <div class="ql-code-block"> db_data.timestamp = std::chrono::<span class="ql-token hljs-built_in">duration_cast</span>&lt;std::chrono::milliseconds&gt;( </div> <div class="ql-code-block"> std::chrono::system_clock::<span class="ql-token hljs-built_in">now</span>().<span class="ql-token hljs-built_in">time_since_epoch</span>()) </div> <div class="ql-code-block"> .<span class="ql-token hljs-built_in">count</span>(); </div> <div class="ql-code-block"> nlohmann::json json_data; </div> <div class="ql-code-block"> json_data[<span class="ql-token hljs-string">"title"</span>] = data.title; </div> <div class="ql-code-block"> json_data[<span class="ql-token hljs-string">"data"</span>] = data.data; </div> <div class="ql-code-block"> json_data[<span class="ql-token hljs-string">"user_id"</span>] = common_service_.user_id; </div> <div class="ql-code-block"> json_data[<span class="ql-token hljs-string">"app_version"</span>] = common_service_.app_version; </div> <div class="ql-code-block"> json_data[<span class="ql-token hljs-string">"app_name"</span>] = common_service_.app_name; </div> <div class="ql-code-block"> json_data[<span class="ql-token hljs-string">"custom_data"</span>] = common_service_.custom_data; </div> <div class="ql-code-block"> json_data[<span class="ql-token hljs-string">"system_version"</span>] = common_service_.system_version; </div> <div class="ql-code-block"> json_data[<span class="ql-token hljs-string">"device_name"</span>] = common_service_.device_name; </div> <div class="ql-code-block"> json_data[<span class="ql-token hljs-string">"device_id"</span>] = common_service_.device_id; </div> <div class="ql-code-block"> json_data[<span class="ql-token hljs-string">"buried_version"</span>] = common_service_.buried_version; </div> <div class="ql-code-block"> json_data[<span class="ql-token hljs-string">"lifecycle_id"</span>] = common_service_.lifecycle_id; </div> <div class="ql-code-block"> json_data[<span class="ql-token hljs-string">"priority"</span>] = data.priority; </div> <div class="ql-code-block"> json_data[<span class="ql-token hljs-string">"timestamp"</span>] = CommonService::<span class="ql-token hljs-built_in">GetNowDate</span>(); </div> <div class="ql-code-block"> json_data[<span class="ql-token hljs-string">"process_time"</span>] = CommonService::<span class="ql-token hljs-built_in">GetProcessTime</span>(); </div> <div class="ql-code-block"> json_data[<span class="ql-token hljs-string">"report_id"</span>] = CommonService::<span class="ql-token hljs-built_in">GetRandomId</span>(); </div> <div class="ql-code-block"> std::string report_data = crypt_-&gt;<span class="ql-token hljs-built_in">Encrypt</span>(json_data.<span class="ql-token hljs-built_in">dump</span>()); </div> <div class="ql-code-block"> db_data.content = std::<span class="ql-token hljs-built_in">vector</span>&lt;<span class="ql-token hljs-type">char</span>&gt;(report_data.<span class="ql-token hljs-built_in">begin</span>(), report_data.<span class="ql-token hljs-built_in">end</span>()); </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">SPDLOG_LOGGER_INFO</span>(logger_, <span class="ql-token hljs-string">"BuriedReportImpl insert data size: {}"</span>, </div> <div class="ql-code-block"> db_data.content.<span class="ql-token hljs-built_in">size</span>()); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> db_data; </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p>BuriedData中有用户传进来的优先级、title、data等数据,再加上common_service中的公共数据,再加上当前时间等数据,构造成一个json object,然后序列化后再加密,即可通过db对象存入数据库中。</p> <p><br></p> <p>整个上报模块需要特别注意几个关键点:</p> <ol> <li data-list="bullet"><span class="ql-ui"></span>通过定时器启动任务,确保每隔一段时间都能上报数据</li> <li data-list="bullet"><span class="ql-ui"></span>通过strand调度任务,确保report模块内的操作都通过一个strand调度,避免数据竞争问题</li> <li data-list="bullet"><span class="ql-ui"></span>存数据前要加密,取数据后要解密,更安全一些</li> </ol> <p><br></p> <p>到这里,整个上报模块已经介绍完毕,下期我会具体介绍下如何获取的系统信息,下期见。</p> </div> </body> </html>

12. 多线程开发

<html> <head></head> <body> <div class="content ql-editor"> <p>大家好,我是程序喵。</p> <p><br></p> <p>本节我主要向大家介绍埋点项目中的多线程模块。</p> <p><br></p> <p>估计正常做C++多线程开发,大多数朋友都是这样写代码:</p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-function">std::thread a([]() {</span> </div> <div class="ql-code-block"><span class="ql-token hljs-function"> while (xxx) {</span> </div> <div class="ql-code-block"><span class="ql-token hljs-function"> yyy;</span> </div> <div class="ql-code-block"><span class="ql-token hljs-function"> }</span> </div> <div class="ql-code-block"><span class="ql-token hljs-function">})</span>; </div> <div class="ql-code-block"><span class="ql-token hljs-keyword">if</span> (a.<span class="ql-token hljs-built_in">joinable</span>()) { </div> <div class="ql-code-block"> a.<span class="ql-token hljs-built_in">join</span>(); </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p>或者使用async,或者使用线程池,然后往线程池里抛任务。</p> <p><br></p> <p>这个项目中,我想向大家介绍一种新的多线程开发理念,strand模型。</p> <p><br></p> <p>我们可以理解为strand是一个任务队列,strand会确保任务按顺序执行,它存在于executor体系中,大家可能也听过,executor大概率会进C++26的新标准,我这样做技术选型,也是希望能提前带大家熟悉C++26,熟悉这种编程理念。</p> <p><br></p> <p>个人认为,strand最大的优点就是可以避免任务并发执行时,可能出现的数据竞争问题。也就是说我们不需要做加锁处理,它是用来确保任务按顺序执行的一种机制,可以保证多个任务在同一个线程上按序执行,从而避免了数据竞争问题。</p> <p><br></p> <p>前面介绍过,我们可以将strand理解为一个任务队列,但任务队列自己是不可能调度的,还需要有个调度器,我们可以考虑自己实现这种机制,不过这里,我会选择asio中的调度器,来执行strand中的任务,asio中executor实现很接近标准库提案,我们使用这个大概率没错,而且会让我们将来学习C++26少走一些弯路。</p> <p><br></p> <p>下面是一个简单易懂的boost executor的strand示例代码:</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-meta">#include &lt;iostream&gt;</span> </div> <div class="ql-code-block"><span class="ql-token hljs-meta">#include &lt;boost/asio.hpp&gt;</span> </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"><span class="ql-token hljs-function">void task1() </span>{ </div> <div class="ql-code-block"> std::cout &lt;&lt; <span class="ql-token hljs-string">"Task 1 executed."</span> &lt;&lt; std::endl; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"><span class="ql-token hljs-function">void task2() </span>{ </div> <div class="ql-code-block"> std::cout &lt;&lt; <span class="ql-token hljs-string">"Task 2 executed."</span> &lt;&lt; std::endl; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"><span class="ql-token hljs-function">int main() </span>{ </div> <div class="ql-code-block"> boost::asio::io_context io_context; <span class="ql-token hljs-comment">// 运行时环境</span> </div> <div class="ql-code-block"> boost::asio::<span class="ql-token hljs-function">io_context::strand strand(io_context)</span>; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> boost::asio::<span class="ql-token hljs-built_in">post</span>(strand, task1); </div> <div class="ql-code-block"> boost::asio::<span class="ql-token hljs-built_in">post</span>(strand, task2); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> io_context.<span class="ql-token hljs-built_in">run</span>(); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> <span class="ql-token hljs-number">0</span>; </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p>在示例代码中,我们创建了一个strand对象,并使用boost::asio::post()将任务提交到strand中。由于strand的存在,任务task1和task2将会按照提交的顺序依次执行,保证了任务的顺序性。</p> <p><br></p> <p>希望通过这个示例,大家能更好地理解C++中的strand理念,以及它们在多线程开发中的作用和优势。</p> <p><br></p> <p>理解了strand后,我们再把它引入到项目中:</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">class</span> <span class="ql-token hljs-title">Context</span> { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">public</span>: </div> <div class="ql-code-block"> <span class="ql-token hljs-function">static Context&amp; GetGlobalContext() </span>{ </div> <div class="ql-code-block"> <span class="ql-token hljs-type">static</span> Context global_context; </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> global_context; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> ~<span class="ql-token hljs-built_in">Context</span>(); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">using</span> Strand = boost::asio::io_context::strand; </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">using</span> IOContext = boost::asio::io_context; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-function">Strand&amp; GetMainStrand() </span>{ <span class="ql-token hljs-keyword">return</span> main_strand_; } </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-function">Strand&amp; GetReportStrand() </span>{ <span class="ql-token hljs-keyword">return</span> report_strand_; } </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-function">IOContext&amp; GetMainContext() </span>{ <span class="ql-token hljs-keyword">return</span> main_context_; } </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-function">void Start()</span>; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">private</span>: </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">Context</span>() : <span class="ql-token hljs-built_in">main_strand_</span>(main_context_), <span class="ql-token hljs-built_in">report_strand_</span>(report_context_) {} </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">Context</span>(<span class="ql-token hljs-type">const</span> Context&amp;) = <span class="ql-token hljs-keyword">delete</span>; </div> <div class="ql-code-block"> Context&amp; <span class="ql-token hljs-keyword">operator</span>=(<span class="ql-token hljs-type">const</span> Context&amp;) = <span class="ql-token hljs-keyword">delete</span>; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">private</span>: </div> <div class="ql-code-block"> boost::asio::io_context main_context_; </div> <div class="ql-code-block"> boost::asio::io_context report_context_; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> boost::asio::io_context::strand main_strand_; </div> <div class="ql-code-block"> boost::asio::io_context::strand report_strand_; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> std::unique_ptr&lt;std::thread&gt; main_thread_; </div> <div class="ql-code-block"> std::unique_ptr&lt;std::thread&gt; report_thread_; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> std::atomic&lt;<span class="ql-token hljs-type">bool</span>&gt; is_start_{<span class="ql-token hljs-literal">false</span>}; </div> <div class="ql-code-block"> std::atomic&lt;<span class="ql-token hljs-type">bool</span>&gt; is_stop_{<span class="ql-token hljs-literal">false</span>}; </div> <div class="ql-code-block"> }; </div> </div> <p><br></p> <p>这里设计了一个Context类来做多线程的任务处理,外部不创建任何线程,线程统一在Context内部管理。</p> <p><br></p> <p>Start后,可以拿到不同的strand,不同的strand可以理解为不同的线程,通过不同的strand执行任务就相当于在某个线程执行任务,既满足了多个线程并行执行任务的需求,又保证一个strand的任务顺序执行,因为一个strand内的任务顺序执行,所以我们不需要做加锁处理数据竞争问题。</p> <p><br></p> <p>我们写代码时,要明确知道哪个函数要执行在哪个线程中,如果开发者都不明确任务运行在哪个线程,那代码大概率会有bug。</p> <p><br></p> <p>我们再看下它的调用示例:</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-function">int main() </span>{ </div> <div class="ql-code-block"> buried::Context::<span class="ql-token hljs-built_in">GetGlobalContext</span>().<span class="ql-token hljs-built_in">Start</span>(); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> buried::Context::<span class="ql-token hljs-built_in">GetGlobalContext</span>().<span class="ql-token hljs-built_in">GetMainStrand</span>().<span class="ql-token hljs-built_in">post</span>([]() { </div> <div class="ql-code-block"> std::cout &lt;&lt; <span class="ql-token hljs-string">"Operation 1 executed in strand1 on thread id "</span> </div> <div class="ql-code-block"> &lt;&lt; std::this_thread::<span class="ql-token hljs-built_in">get_id</span>() &lt;&lt; std::endl; </div> <div class="ql-code-block"> }); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> buried::Context::<span class="ql-token hljs-built_in">GetGlobalContext</span>().<span class="ql-token hljs-built_in">GetReportStrand</span>().<span class="ql-token hljs-built_in">post</span>([]() { </div> <div class="ql-code-block"> std::cout &lt;&lt; <span class="ql-token hljs-string">"Operation 2 executed in strand2 on thread id "</span> </div> <div class="ql-code-block"> &lt;&lt; std::this_thread::<span class="ql-token hljs-built_in">get_id</span>() &lt;&lt; std::endl; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> buried::Context::<span class="ql-token hljs-built_in">GetGlobalContext</span>().<span class="ql-token hljs-built_in">GetReportStrand</span>().<span class="ql-token hljs-built_in">post</span>([]() { </div> <div class="ql-code-block"> std::cout &lt;&lt; <span class="ql-token hljs-string">"Operation 3 executed in strand2 on thread id "</span> </div> <div class="ql-code-block"> &lt;&lt; std::this_thread::<span class="ql-token hljs-built_in">get_id</span>() &lt;&lt; std::endl; </div> <div class="ql-code-block"> }); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> buried::Context::<span class="ql-token hljs-built_in">GetGlobalContext</span>().<span class="ql-token hljs-built_in">GetMainStrand</span>().<span class="ql-token hljs-built_in">post</span>([]() { </div> <div class="ql-code-block"> std::cout &lt;&lt; <span class="ql-token hljs-string">"Operation 4 executed in strand1 on thread id "</span> </div> <div class="ql-code-block"> &lt;&lt; std::this_thread::<span class="ql-token hljs-built_in">get_id</span>() &lt;&lt; std::endl; </div> <div class="ql-code-block"> }); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> buried::Context::<span class="ql-token hljs-built_in">GetGlobalContext</span>().<span class="ql-token hljs-built_in">GetReportStrand</span>().<span class="ql-token hljs-built_in">post</span>([]() { </div> <div class="ql-code-block"> std::cout &lt;&lt; <span class="ql-token hljs-string">"Operation 5 executed in strand3 on thread id "</span> </div> <div class="ql-code-block"> &lt;&lt; std::this_thread::<span class="ql-token hljs-built_in">get_id</span>() &lt;&lt; std::endl; </div> <div class="ql-code-block"> }); </div> <div class="ql-code-block"> }); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> std::this_thread::<span class="ql-token hljs-built_in">sleep_for</span>(std::chrono::<span class="ql-token hljs-built_in">seconds</span>(<span class="ql-token hljs-number">5</span>)); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> <span class="ql-token hljs-number">0</span>; </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p>这里有两个strand,mainStrand和reportStrand,两个strand内的任务会并行执行,但是,又会保证同一个strand中的任务顺序执行。</p> <p><br></p> <p>埋点项目中,使用两个strand足以,mainStrand用于处理大多数主要逻辑,reportStrand用于执行网络相关的任务。</p> <p><br></p> <p>再看下它的实现:</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-function">void Context::Start() </span>{ </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">if</span> (is_start_.<span class="ql-token hljs-built_in">load</span>()) { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span>; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> is_start_.<span class="ql-token hljs-built_in">store</span>(<span class="ql-token hljs-literal">true</span>); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> main_thread_ = std::<span class="ql-token hljs-built_in">make_unique</span>&lt;std::thread&gt;([<span class="ql-token hljs-keyword">this</span>]() { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">for</span> (;;) { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">if</span> (is_stop_) { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">break</span>; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> main_context_.<span class="ql-token hljs-built_in">run</span>(); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> }); </div> <div class="ql-code-block"> report_thread_ = std::<span class="ql-token hljs-built_in">make_unique</span>&lt;std::thread&gt;([<span class="ql-token hljs-keyword">this</span>]() { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">for</span> (;;) { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">if</span> (is_stop_) { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">break</span>; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> report_context_.<span class="ql-token hljs-built_in">run</span>(); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> }); </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p>因为我们需要两个strand并行执行,所以这里创建了两个线程,分别调度strand。</p> <p><br></p> <p>到这里,多线程开发模块已经介绍完毕。下一节我会具体介绍整个上报模块的实现,下周见。</p> <p><br></p> <p>本节相关代码见:</p> <ol> <li data-list="bullet"><span class="ql-ui"></span><a href="https://github.com/chengxumiaodaren/BuriedPoint/blob/main/src/context/context.cc" target="_blank">https://github.com/chengxumiaodaren/BuriedPoint/blob/main/src/context/context.cc</a></li> <li data-list="bullet"><span class="ql-ui"></span><a href="https://github.com/chengxumiaodaren/BuriedPoint/blob/main/src/context/context.h" target="_blank">https://github.com/chengxumiaodaren/BuriedPoint/blob/main/src/context/context.h</a></li> <li data-list="bullet"><span class="ql-ui"></span><a href="https://github.com/chengxumiaodaren/BuriedPoint/blob/main/examples/context_example.cc" target="_blank">https://github.com/chengxumiaodaren/BuriedPoint/blob/main/examples/context_example.cc</a></li> </ol> <p><br></p> <p>我们其实也可以自己简单实现一个strand和调度器,我星球也发过相关的实现,可以看看这篇文章:</p> <p><a href="https://articles.zsxq.com/id_dhm6h72wdsk0.html" target="_blank">https://articles.zsxq.com/id_dhm6h72wdsk0.html</a></p> </div> </body> </html>

11. 加解密实现

<html> <head></head> <body> <div class="content ql-editor"> <p>大家好,我是程序喵。</p> <p><br></p> <p>前面我介绍过数据库的作用,会在上报前先把数据按一定格式存储到数据库中,用于持久化,防止信息丢失,而且也提到过,存储内容一定要是加密过的数据,防止数据泄漏,如果数据随随便便就以明文形式泄漏到外部,这可是很大的安全事故。</p> <p><br></p> <p>所以,本节我就会主要介绍如何实现加解密模块。</p> <p><br></p> <p>加密一般分为对称加密和非对称加密,对称加密大体指的是双方使用同样的秘钥,A使用一个秘钥加密后的数据,B使用同样的秘钥可以直接解密。非对称加密一般要分为公钥和私钥,公钥和私钥是一对,A使用公钥加密后的数据,B拿相同的公钥无法解密,拿其它的私钥也无法加密,一定要拿和此公钥同时相伴而成的私钥才可以成功解密。</p> <p><br></p> <p>常用的对称加密算法有AES、RC4等,常用的非对称加密算法有RSA、ECC等。给大家出个思考题,https使用的是对称加密还是非对称加密?</p> <p><br></p> <p>在本项目中,我们会使用AES这种经典的对称加密算法,会引入mbedtls三方库来实现加解密功能,关于如何引入mbedtls,可以查看第4节。</p> <p><br></p> <p>下面就直接介绍如何实现项目中的加解密算法模块。</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-function">std::string AESCrypt::GetKey(const std::string&amp; salt,</span> </div> <div class="ql-code-block"><span class="ql-token hljs-function"> const std::string password) </span>{ </div> <div class="ql-code-block"> <span class="ql-token hljs-type">int32_t</span> keylen = <span class="ql-token hljs-number">32</span>; </div> <div class="ql-code-block"> <span class="ql-token hljs-type">uint32_t</span> iterations = <span class="ql-token hljs-number">1000</span>; </div> <div class="ql-code-block"> <span class="ql-token hljs-type">unsigned</span> <span class="ql-token hljs-type">char</span> key[<span class="ql-token hljs-number">32</span>] = {<span class="ql-token hljs-number">0</span>}; </div> <div class="ql-code-block"> <span class="ql-token hljs-type">mbedtls_md_context_t</span> md_ctx; </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">mbedtls_md_init</span>(&amp;md_ctx); </div> <div class="ql-code-block"> <span class="ql-token hljs-type">const</span> <span class="ql-token hljs-type">mbedtls_md_info_t</span>* md_info = </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">mbedtls_md_info_from_type</span>(MBEDTLS_MD_SHA256); </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">mbedtls_md_setup</span>(&amp;md_ctx, md_info, <span class="ql-token hljs-number">1</span>); </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">mbedtls_md_starts</span>(&amp;md_ctx); </div> <div class="ql-code-block"> <span class="ql-token hljs-type">int</span> ret = <span class="ql-token hljs-built_in">mbedtls_pkcs5_pbkdf2_hmac</span>( </div> <div class="ql-code-block"> &amp;md_ctx, <span class="ql-token hljs-built_in">reinterpret_cast</span>&lt;<span class="ql-token hljs-type">const</span> <span class="ql-token hljs-type">unsigned</span> <span class="ql-token hljs-type">char</span>*&gt;(password.<span class="ql-token hljs-built_in">data</span>()), </div> <div class="ql-code-block"> password.<span class="ql-token hljs-built_in">size</span>(), <span class="ql-token hljs-built_in">reinterpret_cast</span>&lt;<span class="ql-token hljs-type">const</span> <span class="ql-token hljs-type">unsigned</span> <span class="ql-token hljs-type">char</span>*&gt;(salt.<span class="ql-token hljs-built_in">data</span>()), </div> <div class="ql-code-block"> salt.<span class="ql-token hljs-built_in">size</span>(), iterations, keylen, key); </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">mbedtls_md_free</span>(&amp;md_ctx); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">if</span> (ret != <span class="ql-token hljs-number">0</span>) { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> <span class="ql-token hljs-string">""</span>; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> std::<span class="ql-token hljs-built_in">string</span>((<span class="ql-token hljs-type">char</span>*)key, keylen); </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p>首先,我们需要了解一下AES加密算法是如何工作的,前面说过AES(Advanced Encryption Standard)是一种对称加密算法,意味着加密和解密使用相同的密钥。在这段代码中,我们就会生成一个用于AES加密的密钥。</p> <p><br></p> <p>通过将salt与用户提供的password相结合,可以生成一个更强大的密钥。使用随机的salt可以防止恶意攻击者使用预先计算好的表来破解密码。</p> <p><br></p> <p>password是用户提供的密码,它结合salt,然后使用PBKDF2算法(Password-Based Key Derivation Function 2)和HMAC-SHA256哈希算法来生成密钥。</p> <p><br></p> <p>PBKDF2算法是一种密码推导函数,它使用 password 和salt作为输入,并通过多次迭代的哈希运算生成一个密钥。在这段代码中,我们使用了1000次迭代和SHA256哈希算法来生成一个32字节(256位)的密钥。</p> <p><br></p> <p>通过将password和salt传递给mbedtls_pkcs5_pbkdf2_hmac函数,我们可以生成一个加密安全的密钥。最后,我们将生成的密钥转换为std::string类型并返回。</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">class</span> <span class="ql-token hljs-title">AESImpl</span> { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">public</span>: </div> <div class="ql-code-block"> <span class="ql-token hljs-function">explicit AESImpl(const std::string&amp; key) </span>{ <span class="ql-token hljs-built_in">Init</span>(key.<span class="ql-token hljs-built_in">data</span>(), key.<span class="ql-token hljs-built_in">size</span>()); } </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> ~<span class="ql-token hljs-built_in">AESImpl</span>() { <span class="ql-token hljs-built_in">UnInit</span>(); } </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">AESImpl</span>(<span class="ql-token hljs-type">const</span> AESImpl&amp; other) = <span class="ql-token hljs-keyword">delete</span>; </div> <div class="ql-code-block"> AESImpl&amp; <span class="ql-token hljs-keyword">operator</span>=(<span class="ql-token hljs-type">const</span> AESImpl&amp; other) = <span class="ql-token hljs-keyword">delete</span>; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-function">void Init(const char* key, size_t key_size)</span>; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-function">void UnInit()</span>; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-function">std::string Encrypt(const void* input, size_t input_size)</span>; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-function">std::string Decrypt(const void* input, size_t input_size)</span>; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">private</span>: </div> <div class="ql-code-block"> <span class="ql-token hljs-type">mbedtls_cipher_context_t</span> encrypt_ctx_; </div> <div class="ql-code-block"> <span class="ql-token hljs-type">mbedtls_cipher_context_t</span> decrypt_ctx_; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-type">uint32_t</span> encrypt_block_size_ = <span class="ql-token hljs-number">0</span>; </div> <div class="ql-code-block"> <span class="ql-token hljs-type">uint32_t</span> decrypt_block_size_ = <span class="ql-token hljs-number">0</span>; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-type">unsigned</span> <span class="ql-token hljs-type">char</span> iv_[<span class="ql-token hljs-number">16</span>] = {<span class="ql-token hljs-number">0</span>}; </div> <div class="ql-code-block"> }; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"><span class="ql-token hljs-function">void AESImpl::Init(const char* key, size_t key_size) </span>{ </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">mbedtls_cipher_init</span>(&amp;encrypt_ctx_); </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">mbedtls_cipher_setup</span>( </div> <div class="ql-code-block"> &amp;encrypt_ctx_, <span class="ql-token hljs-built_in">mbedtls_cipher_info_from_type</span>(MBEDTLS_CIPHER_AES_256_CBC)); </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">mbedtls_cipher_set_padding_mode</span>(&amp;encrypt_ctx_, MBEDTLS_PADDING_PKCS7); </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">mbedtls_cipher_setkey</span>(&amp;encrypt_ctx_, </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">reinterpret_cast</span>&lt;<span class="ql-token hljs-type">const</span> <span class="ql-token hljs-type">unsigned</span> <span class="ql-token hljs-type">char</span>*&gt;(key), </div> <div class="ql-code-block"> key_size * <span class="ql-token hljs-number">8</span>, MBEDTLS_ENCRYPT); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> encrypt_block_size_ = <span class="ql-token hljs-built_in">mbedtls_cipher_get_block_size</span>(&amp;encrypt_ctx_); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">mbedtls_cipher_init</span>(&amp;decrypt_ctx_); </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">mbedtls_cipher_setup</span>( </div> <div class="ql-code-block"> &amp;decrypt_ctx_, <span class="ql-token hljs-built_in">mbedtls_cipher_info_from_type</span>(MBEDTLS_CIPHER_AES_256_CBC)); </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">mbedtls_cipher_set_padding_mode</span>(&amp;decrypt_ctx_, MBEDTLS_PADDING_PKCS7); </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">mbedtls_cipher_setkey</span>(&amp;decrypt_ctx_, </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">reinterpret_cast</span>&lt;<span class="ql-token hljs-type">const</span> <span class="ql-token hljs-type">unsigned</span> <span class="ql-token hljs-type">char</span>*&gt;(key), </div> <div class="ql-code-block"> key_size * <span class="ql-token hljs-number">8</span>, MBEDTLS_DECRYPT); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> decrypt_block_size_ = <span class="ql-token hljs-built_in">mbedtls_cipher_get_block_size</span>(&amp;decrypt_ctx_); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"><span class="ql-token hljs-function">void AESImpl::UnInit() </span>{ </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">mbedtls_cipher_free</span>(&amp;encrypt_ctx_); </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">mbedtls_cipher_free</span>(&amp;decrypt_ctx_); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"><span class="ql-token hljs-function">std::string AESImpl::Encrypt(const void* input, size_t input_size) </span>{ </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">mbedtls_cipher_set_iv</span>(&amp;encrypt_ctx_, iv_, <span class="ql-token hljs-built_in">sizeof</span>(iv_)); </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">mbedtls_cipher_reset</span>(&amp;encrypt_ctx_); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-function">std::string output(input_size + encrypt_block_size_, 0)</span>; </div> <div class="ql-code-block"> <span class="ql-token hljs-type">size_t</span> olen = <span class="ql-token hljs-number">0</span>; </div> <div class="ql-code-block"> <span class="ql-token hljs-type">int</span> ret = <span class="ql-token hljs-built_in">mbedtls_cipher_update</span>( </div> <div class="ql-code-block"> &amp;encrypt_ctx_, <span class="ql-token hljs-built_in">reinterpret_cast</span>&lt;<span class="ql-token hljs-type">const</span> <span class="ql-token hljs-type">unsigned</span> <span class="ql-token hljs-type">char</span>*&gt;(input), input_size, </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">reinterpret_cast</span>&lt;<span class="ql-token hljs-type">unsigned</span> <span class="ql-token hljs-type">char</span>*&gt;(output.<span class="ql-token hljs-built_in">data</span>()), &amp;olen); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">if</span> (ret != <span class="ql-token hljs-number">0</span>) { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> <span class="ql-token hljs-string">""</span>; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> <span class="ql-token hljs-type">size_t</span> olen2 = <span class="ql-token hljs-number">0</span>; </div> <div class="ql-code-block"> ret = <span class="ql-token hljs-built_in">mbedtls_cipher_finish</span>( </div> <div class="ql-code-block"> &amp;encrypt_ctx_, <span class="ql-token hljs-built_in">reinterpret_cast</span>&lt;<span class="ql-token hljs-type">unsigned</span> <span class="ql-token hljs-type">char</span>*&gt;(output.<span class="ql-token hljs-built_in">data</span>()) + olen, </div> <div class="ql-code-block"> &amp;olen2); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">if</span> (ret != <span class="ql-token hljs-number">0</span>) { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> <span class="ql-token hljs-string">""</span>; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> output.<span class="ql-token hljs-built_in">resize</span>(olen + olen2); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> output; </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p>这段代码实现了一个AES加密算法模块,主要包括以下几个函数及其作用:</p> <p><br></p> <ol> <li data-list="ordered"><span class="ql-ui"></span><em>AESImpl::Init(const char key, size_t key_size)</em>: 该函数用于初始化加密算法模块。它接受一个密钥 key 和密钥的长度 key_size 作为参数,并进行以下操作:</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>调用 mbedtls_cipher_init 初始化加密上下文 encrypt_ctx_ 和解密上下文 decrypt_ctx_。</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>调用 mbedtls_cipher_setup 配置加密上下文 encrypt_ctx_ 和解密上下文 decrypt_ctx_,指定加密算法类型为 AES-256-CBC。</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>调用 mbedtls_cipher_set_padding_mode 设置加密上下文 encrypt_ctx_ 和解密上下文 decrypt_ctx_ 的填充模式为 PKCS7。</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>调用 mbedtls_cipher_setkey 设置加密上下文 encrypt_ctx_ 和解密上下文 decrypt_ctx_ 的密钥,将传入的 key 转换为 unsigned char* 类型,并指定密钥的位数为 key_size * 8。</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>获取加密上下文 encrypt_ctx_ 的块大小,并将其保存在变量 encrypt_block_size_ 中。</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>获取解密上下文 decrypt_ctx_ 的块大小,并将其保存在变量 decrypt_block_size_ 中。</li> <li data-list="ordered"><span class="ql-ui"></span>AESImpl::UnInit(): 该函数用于释放加密算法模块的资源。它进行以下操作:</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>调用 mbedtls_cipher_free 释放加密上下文 encrypt_ctx_ 和解密上下文 decrypt_ctx_。</li> <li data-list="ordered"><span class="ql-ui"></span><em>AESImpl::Encrypt(const void input, size_t input_size)</em>: 该函数用于对输入数据进行加密。它接受一个指向输入数据的指针 input 和输入数据的大小 input_size,并返回加密后的数据。在函数内部,它进行以下操作:</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>调用 mbedtls_cipher_set_iv 设置加密上下文 encrypt_ctx_ 的初始化向量(IV)为全零的 16 字节。</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>调用 mbedtls_cipher_reset 重置加密上下文 encrypt_ctx_ 的状态。</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>创建一个输出字符串 output,大小为输入数据的大小加上加密块的大小。</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>调用 mbedtls_cipher_update 对输入数据进行加密处理,并将加密结果存储在 output 中。</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>调用 mbedtls_cipher_finish 完成加密操作,并将最后一部分加密结果追加到 output 中。</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>调整 output 的大小为实际加密结果的大小,并返回加密结果。</li> </ol> <p><br></p> <p>通过使用这些函数,可以实现对数据的加密操作。</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-function">std::string AESImpl::Decrypt(const void* input, size_t input_size) </span>{ </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">mbedtls_cipher_set_iv</span>(&amp;decrypt_ctx_, iv_, <span class="ql-token hljs-built_in">sizeof</span>(iv_)); </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">mbedtls_cipher_reset</span>(&amp;decrypt_ctx_); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-function">std::string output(input_size + decrypt_block_size_, 0)</span>; </div> <div class="ql-code-block"> <span class="ql-token hljs-type">size_t</span> olen = <span class="ql-token hljs-number">0</span>; </div> <div class="ql-code-block"> <span class="ql-token hljs-type">int</span> ret = <span class="ql-token hljs-built_in">mbedtls_cipher_update</span>( </div> <div class="ql-code-block"> &amp;decrypt_ctx_, <span class="ql-token hljs-built_in">reinterpret_cast</span>&lt;<span class="ql-token hljs-type">const</span> <span class="ql-token hljs-type">unsigned</span> <span class="ql-token hljs-type">char</span>*&gt;(input), input_size, </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">reinterpret_cast</span>&lt;<span class="ql-token hljs-type">unsigned</span> <span class="ql-token hljs-type">char</span>*&gt;(output.<span class="ql-token hljs-built_in">data</span>()), &amp;olen); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">if</span> (ret != <span class="ql-token hljs-number">0</span>) { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> <span class="ql-token hljs-string">""</span>; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> <span class="ql-token hljs-type">size_t</span> olen2 = <span class="ql-token hljs-number">0</span>; </div> <div class="ql-code-block"> ret = <span class="ql-token hljs-built_in">mbedtls_cipher_finish</span>( </div> <div class="ql-code-block"> &amp;decrypt_ctx_, <span class="ql-token hljs-built_in">reinterpret_cast</span>&lt;<span class="ql-token hljs-type">unsigned</span> <span class="ql-token hljs-type">char</span>*&gt;(output.<span class="ql-token hljs-built_in">data</span>()) + olen, </div> <div class="ql-code-block"> &amp;olen2); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">if</span> (ret != <span class="ql-token hljs-number">0</span>) { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> <span class="ql-token hljs-string">""</span>; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> output.<span class="ql-token hljs-built_in">resize</span>(olen + olen2); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> output; </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p>再介绍这段解密的代码</p> <p><br></p> <p>这段代码实现了一个AES解密算法模块,主要包括:</p> <p><br></p> <ol> <li data-list="ordered"><span class="ql-ui"></span><em>AESImpl::Decrypt(const void</em> input, size_t input_size)*: 该函数用于对输入数据进行解密。它接受一个指向输入数据的指针 input 和输入数据的大小 input_size,并返回解密后的数据。在函数内部,它进行以下操作:</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>调用 mbedtls_cipher_set_iv 设定解密上下文 decrypt_ctx_ 的初始向量为 iv_。</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>调用 mbedtls_cipher_reset 重置解密上下文 decrypt_ctx_。</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>创建一个初始大小为 input_size + decrypt_block_size_ 的输出字符串 output。</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>调用 mbedtls_cipher_update 对输入数据进行解密操作,并将解密后的数据存储到 output 中。</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>检查解密操作是否成功,如果失败则返回空字符串。</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>调用 mbedtls_cipher_finish 完成解密操作,并将结果追加到 output 中。</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>重新调整 output 的大小,将其截断为正确的长度。</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>返回解密后的数据。</li> </ol> <p><br></p> <p>可以看到,前面的类声明的是AESCryptImpl,带了个Impl后缀,这里我还是和上一节一样,使用了pimpl模式,对外类会只会暴露以下方法:</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">class</span> <span class="ql-token hljs-title">Crypt</span> { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">public</span>: </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">virtual</span> ~<span class="ql-token hljs-built_in">Crypt</span>() = <span class="ql-token hljs-keyword">default</span>; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-function">virtual std::string Encrypt(const std::string&amp; input) </span>= <span class="ql-token hljs-number">0</span>; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-function">virtual std::string Decrypt(const std::string&amp; input) </span>= <span class="ql-token hljs-number">0</span>; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-function">virtual std::string Encrypt(const void* input, size_t input_size) </span>= <span class="ql-token hljs-number">0</span>; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-function">virtual std::string Decrypt(const void* input, size_t input_size) </span>= <span class="ql-token hljs-number">0</span>; </div> <div class="ql-code-block"> }; </div> </div> <p><br></p> <p>然后我们再写个程序测试一下本节功能:</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-built_in">TEST</span>(CryptTest, BasicTest) { </div> <div class="ql-code-block"> std::string key = buried::AESCrypt::<span class="ql-token hljs-built_in">GetKey</span>(<span class="ql-token hljs-string">"salt"</span>, <span class="ql-token hljs-string">"password"</span>); </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">EXPECT_TRUE</span>(!key.<span class="ql-token hljs-built_in">empty</span>()); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> buried::Crypt* crypt = <span class="ql-token hljs-keyword">new</span> buried::<span class="ql-token hljs-built_in">AESCrypt</span>(key); </div> <div class="ql-code-block"> std::string input = <span class="ql-token hljs-string">"hello world"</span>; </div> <div class="ql-code-block"> std::string encrypted = crypt-&gt;<span class="ql-token hljs-built_in">Encrypt</span>(input); </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">EXPECT_TRUE</span>(!encrypted.<span class="ql-token hljs-built_in">empty</span>()); </div> <div class="ql-code-block"> std::string decrypted = crypt-&gt;<span class="ql-token hljs-built_in">Decrypt</span>(encrypted); </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">EXPECT_TRUE</span>(!decrypted.<span class="ql-token hljs-built_in">empty</span>()); </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">EXPECT_EQ</span>(input, decrypted); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">delete</span> crypt; </div> <div class="ql-code-block"> } </div> </div> <p>结果就不贴了,肯定是正确的。</p> <p><br></p> <p>到这里,本节已经介绍完毕,我们下期见。</p> <p><br></p> <p>本节详细代码见:</p> <ol> <li data-list="bullet"><span class="ql-ui"></span><a href="https://github.com/chengxumiaodaren/BuriedPoint/blob/main/src/crypt/crypt.h" target="_blank">https://github.com/chengxumiaodaren/BuriedPoint/blob/main/src/crypt/crypt.h</a></li> <li data-list="bullet"><span class="ql-ui"></span><a href="https://github.com/chengxumiaodaren/BuriedPoint/blob/main/src/crypt/crypt.cc" target="_blank">https://github.com/chengxumiaodaren/BuriedPoint/blob/main/src/crypt/crypt.cc</a></li> <li data-list="bullet"><span class="ql-ui"></span><a href="https://github.com/chengxumiaodaren/BuriedPoint/blob/main/tests/test_crypt.cc" target="_blank">https://github.com/chengxumiaodaren/BuriedPoint/blob/main/tests/test_crypt.cc</a></li> </ol> <p><br></p> </div> </body> </html>

10. 如何发起http请求

<html> <head></head> <body> <div class="content ql-editor"> <p>大家好,我是程序喵。</p> <p><br></p> <p>前面我们设计了上报的协议,并且确定使用http协议做埋点的网络上报。</p> <p><br></p> <p>HTTP请求是基于TCP协议的,它涉及以下几个主要步骤:</p> <p><br></p> <ol> <li data-list="ordered"><span class="ql-ui"></span>解析域名,获取对应的IP地址和端口号。</li> <li data-list="ordered"><span class="ql-ui"></span>建立TCP连接,确保客户端和服务器之间的通信。</li> <li data-list="ordered"><span class="ql-ui"></span>获取套接字通道FD,用于在通道中传输数据。</li> <li data-list="ordered"><span class="ql-ui"></span>向通道FD中写入HTTP相关信息,包括请求方法、目标路径、HTTP版本以及请求头字段等。</li> <li data-list="ordered"><span class="ql-ui"></span>将要上传的请求体写入通道FD中,向服务器发送数据。</li> <li data-list="ordered"><span class="ql-ui"></span>从通道FD接收服务端返回的数据,获取服务器的响应。</li> <li data-list="ordered"><span class="ql-ui"></span>关闭TCP链接,结束通信。</li> </ol> <p><br></p> <p>值得注意,HTTP协议本身只是一个应用层协议,定义了一些应用层规则。上述步骤中的许多细节实际上并不属于HTTP的一部分,我将它们整理到了HTTP通信的代码流程中,方便更好地理解整个过程。</p> <p><br></p> <p>在C++中,有很多关于HTTP请求的第三方库可供选择,包括Beast、cpp-httplib、libcurl、libhv等。在这些库中,Boost中的Beast可能是使用难度最高、集成使用最复杂的。尽管如此,我仍然在这个项目中选择Beast作为HTTP请求的库,主要是因为Beast的HTTP请求是基于Asio库的封装。</p> <p><br></p> <p>在使用Beast的过程中,我们将更加深入了解Asio库的设计理念和使用方法。值得一提的是,Asio将成为C++标准库的一部分,因此选择Beast可以让我们提前熟悉这套Asio设计,相信这可以为我们未来的职业生涯增加一些优势。</p> <p><br></p> <p>如何使用Beast发起http请求?</p> <p><br></p> <p>直接看代码:</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-built_in">TEST</span>(BuriedHttpTest, DISABLED_HttpPost) { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">try</span> { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">auto</span> <span class="ql-token hljs-type">const</span> host = <span class="ql-token hljs-string">"localhost"</span>; </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">auto</span> <span class="ql-token hljs-type">const</span> target = <span class="ql-token hljs-string">"/buried"</span>; </div> <div class="ql-code-block"> <span class="ql-token hljs-type">int</span> version = <span class="ql-token hljs-number">11</span>; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">// The io_context is required for all I/O</span> </div> <div class="ql-code-block"> net::io_context ioc; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">// These objects perform our I/O</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-function">tcp::resolver resolver(ioc)</span>; </div> <div class="ql-code-block"> <span class="ql-token hljs-function">beast::tcp_stream stream(ioc)</span>; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> boost::asio::ip::tcp::<span class="ql-token hljs-function">resolver::query query(host, "5678")</span>; </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">// Look up the domain name</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">auto</span> <span class="ql-token hljs-type">const</span> results = resolver.<span class="ql-token hljs-built_in">resolve</span>(query); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">// Make the connection on the IP address we get from a lookup</span> </div> <div class="ql-code-block"> stream.<span class="ql-token hljs-built_in">connect</span>(results); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">// Set up an HTTP POST request message</span> </div> <div class="ql-code-block"> http::request&lt;http::string_body&gt; req{http::verb::post, target, version}; </div> <div class="ql-code-block"> req.<span class="ql-token hljs-built_in">set</span>(http::field::host, host); </div> <div class="ql-code-block"> req.<span class="ql-token hljs-built_in">set</span>(http::field::user_agent, BOOST_BEAST_VERSION_STRING); </div> <div class="ql-code-block"> req.<span class="ql-token hljs-built_in">set</span>(http::field::content_type, <span class="ql-token hljs-string">"application/json"</span>); </div> <div class="ql-code-block"> req.<span class="ql-token hljs-built_in">body</span>() = <span class="ql-token hljs-string">"{\\"</span>hello\\<span class="ql-token hljs-string">":\\"</span>world\\<span class="ql-token hljs-string">"}"</span>; </div> <div class="ql-code-block"> req.<span class="ql-token hljs-built_in">prepare_payload</span>(); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">// Send the HTTP request to the remote host</span> </div> <div class="ql-code-block"> http::<span class="ql-token hljs-built_in">write</span>(stream, req); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">// This buffer is used for reading and must be persisted</span> </div> <div class="ql-code-block"> beast::flat_buffer buffer; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">// Declare a container to hold the response</span> </div> <div class="ql-code-block"> http::response&lt;http::dynamic_body&gt; res; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">// Receive the HTTP response</span> </div> <div class="ql-code-block"> http::<span class="ql-token hljs-built_in">read</span>(stream, buffer, res); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> std::string bdy = boost::beast::<span class="ql-token hljs-built_in">buffers_to_string</span>(res.<span class="ql-token hljs-built_in">body</span>().<span class="ql-token hljs-built_in">data</span>()); </div> <div class="ql-code-block"> std::cout &lt;&lt; <span class="ql-token hljs-string">"bdy "</span> &lt;&lt; bdy &lt;&lt; std::endl; </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">// Write the message to standard out</span> </div> <div class="ql-code-block"> std::cout &lt;&lt; <span class="ql-token hljs-string">"res "</span> &lt;&lt; res &lt;&lt; std::endl; </div> <div class="ql-code-block"> std::cout &lt;&lt; <span class="ql-token hljs-string">"res code "</span> &lt;&lt; res.<span class="ql-token hljs-built_in">result_int</span>() &lt;&lt; std::endl; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">// Gracefully close the socket</span> </div> <div class="ql-code-block"> beast::error_code ec; </div> <div class="ql-code-block"> stream.<span class="ql-token hljs-built_in">socket</span>().<span class="ql-token hljs-built_in">shutdown</span>(tcp::socket::shutdown_both, ec); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">// not_connected happens sometimes</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">// so don't bother reporting it.</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">//</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">if</span> (ec &amp;&amp; ec != beast::errc::not_connected) <span class="ql-token hljs-keyword">throw</span> beast::system_error{ec}; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">// If we get here then the connection is closed gracefully</span> </div> <div class="ql-code-block"> } <span class="ql-token hljs-built_in">catch</span> (std::exception <span class="ql-token hljs-type">const</span>&amp; e) { </div> <div class="ql-code-block"> std::cerr &lt;&lt; <span class="ql-token hljs-string">"Error: "</span> &lt;&lt; e.<span class="ql-token hljs-built_in">what</span>() &lt;&lt; std::endl; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p>这段代码是一个使用Boost的Beast库发起HTTP请求的示例。整体和我上面介绍的HTTP流程类似,我来详细解释一下每个步骤:</p> <p><br></p> <p>首先,这里指定了要请求的主机名(host)和目标路径(target)。接下来,创建了一个io_context对象,该对象对所有的I/O操作都是必需的,可以理解为是IO操作的运行时环境。我们还创建了一个resolver对象和一个tcp_stream对象,用于执行I/O操作。</p> <p><br></p> <p>接着,使用resolver对象来解析host,获取对应的IP地址和端口号。然后,使用tcp_stream对象与服务器建立TCP连接。</p> <p><br></p> <p>接着,定义了一个http::request对象,表示一个HTTP POST请求。我们设置了请求的方法为POST,目标路径为target,HTTP版本为11。然后,这里设置了一些HTTP头字段,包括host、user_agent和content_type。我们将请求的主体(body)设置为测试的body{"hello":"world"},并调用prepare_payload方法,用于构造整个请求体。</p> <p><br></p> <p>接着,我们使用http::write函数将请求发送到远程host。然后使用beast::flat_buffer对象作为缓冲区来接收服务器的response。这里定义了一个http::response对象来存储接收到的HTTP响应。</p> <p><br></p> <p>然后,我们使用http::read函数从流中读取服务器的响应。我们将响应的主体转换为字符串,并打印到标准输出。我们还打印了完整的响应和响应状态码。</p> <p><br></p> <p>最后,我们使用beast::error_code对象来处理错误,并通过调用shutdown方法优雅地关闭socket连接。</p> <p><br></p> <p>当然,如果发生了异常,这里也会捕获并打印相关的错误信息。</p> <p><br></p> <p>相关代码:<a href="https://github.com/chengxumiaodaren/BuriedPoint/blob/main/tests/test_http.cc" target="_blank">https://github.com/chengxumiaodaren/BuriedPoint/blob/main/tests/test_http.cc</a></p> <p><br></p> <p>到这里,大家应该已经知道了如何使用Beast发起http请求。</p> <p><br></p> <p>但,这样肯定不太好用,难道每次发起网络请求都要写这么一大堆代码吗?我们需要基于埋点项目需求,封装一套好用的http接口。</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">class</span> <span class="ql-token hljs-title">HttpReporter</span> { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">public</span>: </div> <div class="ql-code-block"> <span class="ql-token hljs-function">explicit HttpReporter(std::shared_ptr&lt;spdlog::logger&gt; logger)</span>; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-function">HttpReporter&amp; Host(const std::string&amp; host) </span>{ </div> <div class="ql-code-block"> host_ = host; </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> *<span class="ql-token hljs-keyword">this</span>; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-function">HttpReporter&amp; Topic(const std::string&amp; topic) </span>{ </div> <div class="ql-code-block"> topic_ = topic; </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> *<span class="ql-token hljs-keyword">this</span>; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-function">HttpReporter&amp; Port(const std::string&amp; port) </span>{ </div> <div class="ql-code-block"> port_ = port; </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> *<span class="ql-token hljs-keyword">this</span>; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-function">HttpReporter&amp; Body(const std::string&amp; body) </span>{ </div> <div class="ql-code-block"> body_ = body; </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> *<span class="ql-token hljs-keyword">this</span>; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-function">bool Report()</span>; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">private</span>: </div> <div class="ql-code-block"> std::string host_; </div> <div class="ql-code-block"> std::string topic_; </div> <div class="ql-code-block"> std::string port_; </div> <div class="ql-code-block"> std::string body_; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> std::shared_ptr&lt;spdlog::logger&gt; logger_; </div> <div class="ql-code-block"> }; </div> </div> <p><br></p> <p>我再解释下:</p> <p><br></p> <p>首先我定义了一个名为HttpReporter的类,用于封装一个方便使用的HTTP接口。</p> <p><br></p> <p>在类的构造函数中,接收一个std::shared_ptr&lt;spdlog::logger&gt;类型的参数,用于记录日志,看过我前几节专栏的朋友应该会知道如何传入这个logger实例。</p> <p><br></p> <p>类中定义了一些公有成员函数,用于设置HTTP请求的各个参数。Host函数用于设置主机名,Topic函数用于设置具体路径,Port函数用于设置端口号,Body函数用于设置请求的主体。</p> <p><br></p> <p>这些函数都返回一个HttpReporter&amp;类型的引用,允许我们使用链式调用来方便地设置参数。</p> <p><br></p> <p>最后,定义了一个Report函数,用于发起HTTP请求。该函数会使用之前设置的参数,使用Boost的Beast库发起HTTP请求,并返回一个bool类型的值,表示请求是否成功。</p> <p><br></p> <p>整个HttpReporter类的设计目的是为了封装和简化HTTP请求的过程,让它更加方便易用。</p> <p><br></p> <p>然后,我们再实现它:</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-meta">#include "report/http_report.h"</span> </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"><span class="ql-token hljs-meta">#include "boost/asio/connect.hpp"</span> </div> <div class="ql-code-block"><span class="ql-token hljs-meta">#include "boost/asio/io_context.hpp"</span> </div> <div class="ql-code-block"><span class="ql-token hljs-meta">#include "boost/asio/ip/tcp.hpp"</span> </div> <div class="ql-code-block"><span class="ql-token hljs-meta">#include "boost/beast/core.hpp"</span> </div> <div class="ql-code-block"><span class="ql-token hljs-meta">#include "boost/beast/http.hpp"</span> </div> <div class="ql-code-block"><span class="ql-token hljs-meta">#include "boost/beast/version.hpp"</span> </div> <div class="ql-code-block"><span class="ql-token hljs-meta">#include "spdlog/spdlog.h"</span> </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"><span class="ql-token hljs-keyword">namespace</span> beast = boost::beast; <span class="ql-token hljs-comment">// from &lt;boost/beast.hpp&gt;</span> </div> <div class="ql-code-block"><span class="ql-token hljs-keyword">namespace</span> http = beast::http; <span class="ql-token hljs-comment">// from &lt;boost/beast/http.hpp&gt;</span> </div> <div class="ql-code-block"><span class="ql-token hljs-keyword">namespace</span> net = boost::asio; <span class="ql-token hljs-comment">// from &lt;boost/asio.hpp&gt;</span> </div> <div class="ql-code-block"><span class="ql-token hljs-keyword">using</span> tcp = net::ip::tcp; <span class="ql-token hljs-comment">// from &lt;boost/asio/ip/tcp.hpp&gt;</span> </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"><span class="ql-token hljs-keyword">namespace</span> buried { </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"><span class="ql-token hljs-type">static</span> boost::asio::io_context ioc; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> HttpReporter::<span class="ql-token hljs-built_in">HttpReporter</span>(std::shared_ptr&lt;spdlog::logger&gt; logger) </div> <div class="ql-code-block"> : <span class="ql-token hljs-built_in">logger_</span>(logger) {} </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"><span class="ql-token hljs-function">bool HttpReporter::Report() </span>{ </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">try</span> { </div> <div class="ql-code-block"> <span class="ql-token hljs-type">int</span> version = <span class="ql-token hljs-number">11</span>; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">// These objects perform our I/O</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-function">tcp::resolver resolver(ioc)</span>; </div> <div class="ql-code-block"> <span class="ql-token hljs-function">beast::tcp_stream stream(ioc)</span>; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> boost::asio::ip::tcp::<span class="ql-token hljs-function">resolver::query query(host_, port_)</span>; </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">// Look up the domain name</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">auto</span> <span class="ql-token hljs-type">const</span> results = resolver.<span class="ql-token hljs-built_in">resolve</span>(query); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">// Make the connection on the IP address we get from a lookup</span> </div> <div class="ql-code-block"> stream.<span class="ql-token hljs-built_in">connect</span>(results); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">// Set up an HTTP POST request message</span> </div> <div class="ql-code-block"> http::request&lt;http::string_body&gt; req{http::verb::post, topic_, version}; </div> <div class="ql-code-block"> req.<span class="ql-token hljs-built_in">set</span>(http::field::host, host_); </div> <div class="ql-code-block"> req.<span class="ql-token hljs-built_in">set</span>(http::field::user_agent, BOOST_BEAST_VERSION_STRING); </div> <div class="ql-code-block"> req.<span class="ql-token hljs-built_in">set</span>(http::field::content_type, <span class="ql-token hljs-string">"application/json"</span>); </div> <div class="ql-code-block"> req.<span class="ql-token hljs-built_in">body</span>() = body_; </div> <div class="ql-code-block"> req.<span class="ql-token hljs-built_in">prepare_payload</span>(); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">// Send the HTTP request to the remote host</span> </div> <div class="ql-code-block"> http::<span class="ql-token hljs-built_in">write</span>(stream, req); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">// This buffer is used for reading and must be persisted</span> </div> <div class="ql-code-block"> beast::flat_buffer buffer; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">// Declare a container to hold the response</span> </div> <div class="ql-code-block"> http::response&lt;http::dynamic_body&gt; res; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">// Receive the HTTP response</span> </div> <div class="ql-code-block"> http::<span class="ql-token hljs-built_in">read</span>(stream, buffer, res); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">// Gracefully close the socket</span> </div> <div class="ql-code-block"> beast::error_code ec; </div> <div class="ql-code-block"> stream.<span class="ql-token hljs-built_in">socket</span>().<span class="ql-token hljs-built_in">shutdown</span>(tcp::socket::shutdown_both, ec); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">// not_connected happens sometimes</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">// so don't bother reporting it.</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">//</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">if</span> (ec &amp;&amp; ec != beast::errc::not_connected) <span class="ql-token hljs-keyword">throw</span> beast::system_error{ec}; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">auto</span> res_status = res.<span class="ql-token hljs-built_in">result</span>(); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">if</span> (res_status != http::status::ok) { </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">SPDLOG_LOGGER_ERROR</span>(logger_, </div> <div class="ql-code-block"> <span class="ql-token hljs-string">"report error "</span> + std::<span class="ql-token hljs-built_in">to_string</span>(res.<span class="ql-token hljs-built_in">result_int</span>())); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> <span class="ql-token hljs-literal">false</span>; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> std::string res_body = boost::beast::<span class="ql-token hljs-built_in">buffers_to_string</span>(res.<span class="ql-token hljs-built_in">body</span>().<span class="ql-token hljs-built_in">data</span>()); </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">// Write the message to log</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">SPDLOG_LOGGER_TRACE</span>(logger_, <span class="ql-token hljs-string">"report success"</span> + res_body); </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">// If we get here then the connection is closed gracefully</span> </div> <div class="ql-code-block"> } <span class="ql-token hljs-built_in">catch</span> (std::exception <span class="ql-token hljs-type">const</span>&amp; e) { </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">SPDLOG_LOGGER_ERROR</span>(logger_, <span class="ql-token hljs-string">"report error "</span> + std::<span class="ql-token hljs-built_in">string</span>(e.<span class="ql-token hljs-built_in">what</span>())); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> <span class="ql-token hljs-literal">false</span>; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> <span class="ql-token hljs-literal">true</span>; </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p>接口都已经实现好之后,我们就可以方便的发起http请求。</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-function">bool BuriedReportImpl::ReportData_(const std::string&amp; data) </span>{ </div> <div class="ql-code-block"> <span class="ql-token hljs-function">HttpReporter reporter(logger_)</span>; </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> reporter.<span class="ql-token hljs-built_in">Host</span>(host) </div> <div class="ql-code-block"> .<span class="ql-token hljs-built_in">Topic</span>(topic) </div> <div class="ql-code-block"> .<span class="ql-token hljs-built_in">Port</span>(port) </div> <div class="ql-code-block"> .<span class="ql-token hljs-built_in">Body</span>(data) </div> <div class="ql-code-block"> .<span class="ql-token hljs-built_in">Report</span>(); </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p>这样简单一调用就OK了。</p> <p><br></p> <p>如何测试http的代码?</p> <p><br></p> <p>这里大家可以自己搭建一个server,也可以使用我在项目中使用Rust开发的简易server。</p> <p><br></p> <p>直接进入server目录(<a href="https://github.com/chengxumiaodaren/BuriedPoint/tree/main/server%EF%BC%89%EF%BC%8C%E6%89%A7%E8%A1%8Ccargo" target="_blank">https://github.com/chengxumiaodaren/BuriedPoint/tree/main/server),执行cargo</a> run命令即可。</p> <p><br></p> <p>无论你选择哪种方法,都需要先安装Rust开发环境。</p> <p><br></p> <p>到这里,本节内容已经介绍完毕。在下一节中,我将详细介绍项目中加解密功能的实现。下周再见!</p> <p><br></p> <p>本节相关代码见:</p> <p><br></p> <ol> <li data-list="bullet"><span class="ql-ui"></span><a href="https://github.com/chengxumiaodaren/BuriedPoint/blob/main/src/report/http_report.h" target="_blank">https://github.com/chengxumiaodaren/BuriedPoint/blob/main/src/report/http_report.h</a></li> <li data-list="bullet"><span class="ql-ui"></span><a href="https://github.com/chengxumiaodaren/BuriedPoint/blob/main/src/report/http_report.cc" target="_blank">https://github.com/chengxumiaodaren/BuriedPoint/blob/main/src/report/http_report.cc</a></li> <li data-list="bullet"><span class="ql-ui"></span><a href="https://github.com/chengxumiaodaren/BuriedPoint/blob/main/tests/test_http.cc" target="_blank">https://github.com/chengxumiaodaren/BuriedPoint/blob/main/tests/test_http.cc</a></li> <li data-list="bullet"><span class="ql-ui"></span><a href="https://github.com/chengxumiaodaren/BuriedPoint/tree/main/server" target="_blank">https://github.com/chengxumiaodaren/BuriedPoint/tree/main/server</a></li> </ol> <p><br></p> </div> </body> </html>

9. 数据库设计与使用

<html> <head></head> <body> <div class="content ql-editor"> <p>大家好,我是程序喵。</p> <p><br></p> <p>上一节介绍了上报协议的设计,这一节我会主要介绍埋点项目中,数据库的结构设计与使用。</p> <p><br></p> <p>提到数据库,大家肯定会想到MySQL、Postgresql,它们确实好用,不过对于客户端来说有些重。</p> <p><br></p> <p>一般客户端都是使用sqlite或者leveldb这种轻量数据库,sqlite会更常用一些。我们这里也会使用sqlite数据库。</p> <p><br></p> <p>正常在C++中使用sqlite数据库,比如增删改查,都需要写对应的数据库sql语句,相当麻烦。有其他编程经验的朋友可能会想到,有没有ORM相关的封装呢?类似于Rust中的Diesel。</p> <p><br></p> <p>C++中ORM相关的sqlite三方库很少,我找了好久,只找到了一个相对好用而且star也比较多的三方库 sqlite_orm( <a href="https://github.com/fnc12/sqlite_orm" target="_blank">https://github.com/fnc12/sqlite_orm</a> )</p> <p><br></p> <p>sqlite_orm的使用非常简单,它只有一个头文件,我们只需include一个头文件就可以:<a href="https://github.com/fnc12/sqlite_orm/blob/master/include/sqlite_orm/sqlite_orm.h" target="_blank">https://github.com/fnc12/sqlite_orm/blob/master/include/sqlite_orm/sqlite_orm.h</a></p> <p><br></p> <p>写代码之前,我们需要先设计数据库的结构。</p> <p><br></p> <p>数据库中需要存储哪些内容?</p> <p><br></p> <p>其实只需要四个字段:</p> <p><br></p> <ol> <li data-list="bullet"><span class="ql-ui"></span>ID:每条数据都有个对应的ID,作为数据的唯一标识,这个应该不用多说</li> <li data-list="bullet"><span class="ql-ui"></span>priority:表示每条数据的优先级,从数据库中取数据的时候,会按优先级从高到低读取</li> <li data-list="bullet"><span class="ql-ui"></span>timestamp:表示每条数据上报的时间戳</li> <li data-list="bullet"><span class="ql-ui"></span>content:表示实际要上传的文本内容,不过我们这里数据库中存储的数据肯定是加密过的数据,取出来需要解密后再上传到后端。否则数据都被用户看见了,不安全。</li> </ol> <p><br></p> <p>我们数据库相关的需求应该很明确,就是增删查,那相关的类如何设计呢?</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-comment">// database.h</span> </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"><span class="ql-token hljs-keyword">class</span> <span class="ql-token hljs-title">BuriedDbImpl</span>; </div> <div class="ql-code-block"><span class="ql-token hljs-keyword">class</span> <span class="ql-token hljs-title">BuriedDb</span> { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">public</span>: </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">struct</span> <span class="ql-token hljs-title">Data</span> { </div> <div class="ql-code-block"> <span class="ql-token hljs-type">int32_t</span> id; </div> <div class="ql-code-block"> <span class="ql-token hljs-type">int32_t</span> priority; </div> <div class="ql-code-block"> <span class="ql-token hljs-type">uint64_t</span> timestamp; </div> <div class="ql-code-block"> std::vector&lt;<span class="ql-token hljs-type">char</span>&gt; content; </div> <div class="ql-code-block"> }; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">public</span>: </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">BuriedDb</span>(std::string db_path); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> ~<span class="ql-token hljs-built_in">BuriedDb</span>(); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-function">void InsertData(const Data&amp; data)</span>; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-function">void DeleteData(const Data&amp; data)</span>; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-function">void DeleteDatas(const std::vector&lt;Data&gt;&amp; datas)</span>; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-function">std::vector&lt;Data&gt; QueryData(int32_t limit)</span>; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">private</span>: </div> <div class="ql-code-block"> std::unique_ptr&lt;BuriedDbImpl&gt; impl_; </div> <div class="ql-code-block"> }; </div> </div> <p><br></p> <p>对应的类叫BuriedDb,里面有几个成员函数,可以直观的看到,功能就是增删查。</p> <p><br></p> <p>类中有一个Data的struct定义,就是我们上面介绍的数据库结构需要的字段,其中有一点不太一样,正常来说content一般是string类型,而这里我们使用的是vector&lt;char&gt;类型,因为sqlite_orm这个库中文本类型需要是vector&lt;char&gt;,否则会导致数据库功能异常。</p> <p><br></p> <p>注意我们这里只使用了一个成员变量,即 std::unique_ptr&lt;BuriedDbImpl&gt; impl,而且这个BuriedDbImpl只有前置声明,没有具体的定义,这里使用的是C++中比较常见的pimpl模式,即头文件中不做具体定义和实现,只保留一个指针,具体的定义和实现放在源文件中。关于pimpl,我写过一篇文章,可以看看:<a href="https://t.zsxq.com/14r7qn8UQ" target="_blank">https://t.zsxq.com/14r7qn8UQ</a></p> <p><br></p> <p>如果不用这种方式,我们就需要在头文件database.h中引入sqlite_orm.h,如果其他好多头文件都引入database.h,会导致重复引入sqlite_orm.h,可能会导致代码段体积过大。</p> <p><br></p> <p>再看源文件中BuriedDb成员函数的实现:</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"> BuriedDb::<span class="ql-token hljs-built_in">BuriedDb</span>(std::string db_path) </div> <div class="ql-code-block"> : impl_{std::<span class="ql-token hljs-built_in">make_unique</span>&lt;BuriedDbImpl&gt;(std::<span class="ql-token hljs-built_in">move</span>(db_path))} {} </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> BuriedDb::~<span class="ql-token hljs-built_in">BuriedDb</span>() {} </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"><span class="ql-token hljs-function">void BuriedDb::InsertData(const Data&amp; data) </span>{ impl_-&gt;<span class="ql-token hljs-built_in">InsertData</span>(data); } </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"><span class="ql-token hljs-function">void BuriedDb::DeleteData(const Data&amp; data) </span>{ impl_-&gt;<span class="ql-token hljs-built_in">DeleteData</span>(data); } </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"><span class="ql-token hljs-function">void BuriedDb::DeleteDatas(const std::vector&lt;Data&gt;&amp; datas) </span>{ </div> <div class="ql-code-block"> impl_-&gt;<span class="ql-token hljs-built_in">DeleteDatas</span>(datas); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"><span class="ql-token hljs-function">std::vector&lt;BuriedDb::Data&gt; BuriedDb::QueryData(int32_t limit) </span>{ </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> impl_-&gt;<span class="ql-token hljs-built_in">QueryData</span>(limit); </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p>BuriedDb的所有成员函数的实现,都是调用的impl的对应方法,实际的定义都在BuriedDbImpl中。</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">class</span> <span class="ql-token hljs-title">BuriedDbImpl</span> { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">public</span>: </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">using</span> DBStorage = <span class="ql-token hljs-keyword">decltype</span>(<span class="ql-token hljs-built_in">InitStorage</span>(<span class="ql-token hljs-string">""</span>)); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">public</span>: </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">BuriedDbImpl</span>(std::string db_path) : <span class="ql-token hljs-built_in">db_path_</span>(db_path) { </div> <div class="ql-code-block"> storage_ = std::<span class="ql-token hljs-built_in">make_unique</span>&lt;DBStorage&gt;(<span class="ql-token hljs-built_in">InitStorage</span>(db_path_)); </div> <div class="ql-code-block"> storage_-&gt;<span class="ql-token hljs-built_in">sync_schema</span>(); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> ~<span class="ql-token hljs-built_in">BuriedDbImpl</span>() {} </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-function">void InsertData(const BuriedDb::Data&amp; data) </span>{ </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">auto</span> guard = storage_-&gt;<span class="ql-token hljs-built_in">transaction_guard</span>(); </div> <div class="ql-code-block"> storage_-&gt;<span class="ql-token hljs-built_in">insert</span>(data); </div> <div class="ql-code-block"> guard.<span class="ql-token hljs-built_in">commit</span>(); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-function">void DeleteData(const BuriedDb::Data&amp; data) </span>{ </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">auto</span> guard = storage_-&gt;<span class="ql-token hljs-built_in">transaction_guard</span>(); </div> <div class="ql-code-block"> storage_-&gt;<span class="ql-token hljs-built_in">remove_all</span>&lt;BuriedDb::Data&gt;( </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">where</span>(<span class="ql-token hljs-built_in">c</span>(&amp;BuriedDb::Data::id) == data.id)); </div> <div class="ql-code-block"> guard.<span class="ql-token hljs-built_in">commit</span>(); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-function">void DeleteDatas(const std::vector&lt;BuriedDb::Data&gt;&amp; datas) </span>{ </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">auto</span> guard = storage_-&gt;<span class="ql-token hljs-built_in">transaction_guard</span>(); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">for</span> (<span class="ql-token hljs-type">const</span> <span class="ql-token hljs-keyword">auto</span>&amp; data : datas) { </div> <div class="ql-code-block"> storage_-&gt;<span class="ql-token hljs-built_in">remove_all</span>&lt;BuriedDb::Data&gt;( </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">where</span>(<span class="ql-token hljs-built_in">c</span>(&amp;BuriedDb::Data::id) == data.id)); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> guard.<span class="ql-token hljs-built_in">commit</span>(); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-function">std::vector&lt;BuriedDb::Data&gt; QueryData(int32_t limit_size) </span>{ </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">auto</span> limited = storage_-&gt;<span class="ql-token hljs-built_in">get_all</span>&lt;BuriedDb::Data&gt;( </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">order_by</span>(&amp;BuriedDb::Data::priority).<span class="ql-token hljs-built_in">desc</span>(), <span class="ql-token hljs-built_in">limit</span>(limit_size)); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> limited; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">private</span>: </div> <div class="ql-code-block"> std::string db_path_; </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> std::unique_ptr&lt;DBStorage&gt; storage_; </div> <div class="ql-code-block"> }; </div> </div> <p><br></p> <p>这里的增删查操作都是使用的storage相关方法。</p> <p><br></p> <p>比如插入方法的三个步骤:</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-function">void InsertData(const BuriedDb::Data&amp; data) </span>{ </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">auto</span> guard = storage_-&gt;<span class="ql-token hljs-built_in">transaction_guard</span>(); </div> <div class="ql-code-block"> storage_-&gt;<span class="ql-token hljs-built_in">insert</span>(data); </div> <div class="ql-code-block"> guard.<span class="ql-token hljs-built_in">commit</span>(); </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p>先开启事务,再插入数据,再提交事务,整个插入流程非常简单而且方便,这也正是ORM数据库的优势。</p> <p><br></p> <p>代码中使用了transaction_guard开启事务,又创建了一个guard对象,并在最后使用<a href="http://guard.commit" target="_blank">guard.commit</a>提交事务,这里使用了RAII概念,假如中间有哪个步骤失败,return后,会在guard的析构函数中触发回滚事务的操作。</p> <p><br></p> <p>删除查询操作,也是类似,这里就不多介绍,大家直接看代码应该就可以明白。</p> <p><br></p> <p>这里重点在介绍下storage成员变量的类型,相关代码是:</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-function">inline auto InitStorage(const std::string&amp; path) </span>{ </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> <span class="ql-token hljs-built_in">make_storage</span>( </div> <div class="ql-code-block"> path, <span class="ql-token hljs-built_in">make_table</span>(<span class="ql-token hljs-string">"buried_data"</span>, </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">make_column</span>(<span class="ql-token hljs-string">"id"</span>, &amp;BuriedDb::Data::id, </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">primary_key</span>().<span class="ql-token hljs-built_in">autoincrement</span>()), </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">make_column</span>(<span class="ql-token hljs-string">"priority"</span>, &amp;BuriedDb::Data::priority), </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">make_column</span>(<span class="ql-token hljs-string">"timestamp"</span>, &amp;BuriedDb::Data::timestamp), </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">make_column</span>(<span class="ql-token hljs-string">"content"</span>, &amp;BuriedDb::Data::content))); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"><span class="ql-token hljs-keyword">using</span> DBStorage = <span class="ql-token hljs-keyword">decltype</span>(<span class="ql-token hljs-built_in">InitStorage</span>(<span class="ql-token hljs-string">""</span>)); </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> std::unique_ptr&lt;DBStorage&gt; storage_; </div> </div> <p><br></p> <p>首先,在InitStorage函数中,使用make_storage函数创建了一个数据库存储对象。在这个函数中,使用make_table函数定义了一个名为"buried_data"的表,其中包含了"ID"、"priority"、"timestamp"和"content"四个字段。每个字段都使用了make_column函数进行定义,指定了字段的名称以及对应的数据类型。其中,"id"字段被定义为主键并自增。</p> <p><br></p> <p>接着,使用using关键字定义了一个名为DBStorage的类型,它是通过调用InitStorage函数的返回类型推断得到的。</p> <p><br></p> <p>如果不建表的话,我们还不知道storage的类型,代码中使用InitStorage函数建表,返回值就是对象类型,我们也不知道具体类型是什么,如果自己推断,那是相当麻烦。所以这里使用了inline auto + decltype关键字来推断出整个storage的类型。</p> <p><br></p> <p>最后,使用std::unique_ptr定义了一个名为storage_的成员变量,它是DBStorage类型的指针,用于操作数据库存储对象。</p> <p><br></p> <p>到这里,数据库相关的使用已经介绍完毕,下一章节我会介绍如何发起http请求,下节见。</p> <p><br></p> <p>本节相关代码详见:<a href="https://github.com/chengxumiaodaren/BuriedPoint/blob/main/src/database/database.cc" target="_blank">https://github.com/chengxumiaodaren/BuriedPoint/blob/main/src/database/database.cc</a></p> <p><br></p> </div> </body> </html>

下载 APP