前段时间专门抽空做了一个小工具,也就是这里要讲的主题:anvil
一开始做 anvil 的动力很简单:某次尝试体验一下 Linux SignalFD 功能时想直接使用 ezio 的 EventLoop
作为基础事件循环,同时项目使用 cmake 管理。
为了省事,我直接从 ezio 的项目里抠出来 CMakeLists.txt
和几个自己写的 .cmake
文件,就地修改。
但事实证明哪怕这样,改动量也不小,原因大体是因为:cmake 里(非函数内定义的)变量作用域是全局的,通过 fetch-content 功能引入的依赖在 add_subdirectory()
后的模块里也能看到上一层定义的变量,因此为了防止一些控制型变量发生冲突,我都在前面加上了对应的模块前缀。
所以我面对的就是一大坨变量名的更换,以及少部分声明/属性的调整。
考虑到大部分的文件内容都是可以模板化的,而手动“实例化”不仅费事还很容易出错,所以我就很自然地萌生了写一个工具自动化这个过程的想法。
在经过一两天的短暂思考后,我大致确定了这个工具的定位和需要实现的基本目标,总结起来有三个核心点:
首先,第一点是重中之重。
C++ 11之后的 destructor 默认是 noexcept
,如果有 active exception 逃逸出 dtor 会直接触发 std::terminate()
,即使外部有 catch handler。可以用 noexcept(false)
显式关闭。
因为 stack unwinding 是可以嵌套的,一个精心设计的场合下(见文中例子),可以做到多个 active exception 不在一个层次里,因此也不会触发 double-exception situation。
an exception leaves out from main function
an exception leaves out from initial function of a thread
an exception leaves out from dtor (since c++ 11 with noexcept guarantee)
大部分人应该都知道 DNS 协议,以及它的用处。但是考虑过自己动手写 DNS message transmission 的应该不占多数。
我们不妨考虑如何自己实现一个简单的 DNS client,完成基本的 domain name query and response parsing。
因为 DNS 协议通常使用 UDP 作为其传输层协议,而且数据包是二进制包,所以实现一个这样简单的 demo 考虑用 golang 可能会省事儿不少。
大体上 DNS 不是一个复杂的协议(虽然各类坑实在不少),所有的消息,不管是 query 还是 reply,都共享一个消息格式:
1 | +---------------------+ |
对于一个 query message,Answer
section 是空的;而对于一个 reply message,Question
保存着对应 query 的数据。
一般而言,终端设备收到的 reply 消息里,Authority
和 Additional
是空的。所以下面会跳过这两个 section 的描述。
ezio Buffer 一开始的时候只为 read, write 和 peek 提供了从 int8_t
到 int64_t
的函数重载,如果需要处理 unsigned integers,那么就需要自己额外做 static_cast
。
ezio 的主要客户藏心同学早前抱怨过这个问题,并且同时建议我加上对 float/double
的浮点数支持。
对于这个建议我一开始是抵触的:
于是藏心同学一开始开的 issue 我一直没理他,于是最后他自己关掉了…
等到我自己动手写一个 socks4a proxy 的时候我发现,自己 cast 真的是…太蛋疼了…而且代码看上去还非常丑,大面积的 static_cast
制造了相当一部分内容噪音。那会儿我大概有点理解藏心同学的内心感受。
于是我思考良久,打算改造 Buffer 的这部分接口,以支持绝大多数 integer types,顺带也增加入 floating piont types 的支持,这样 read, write, peek 就基本支持了绝大多数 arithmetic types。
通过直接增加重载是我极力避免的,因为除了接口签名外,大部分实现几乎是一样的,不外乎:
于是自然而然的想到直接将函数做成 function templates 来增强语义。