走出舒适区:从nodejs到dlang

在2017年,我开始在nodejs上编写一个项目-用于访问KNX值的Weinzierl ObjectServer协议的实现。 在编写过程中,我们研究了:使用二进制协议,呈现数据,使用套接字(特别是Unix套接字),使用Redis数据库和pub / sub通道。


该项目已达到稳定版本。 这时,我慢慢选择了其他语言,尤其是Dart和Flutter作为他的应用程序。 在架子上除尘时,没有购买任何学生手册G. Schildt的时间。


一个用C语言重写项目的持久想法浮现在我的脑海。 我考虑选择Go,Rust,排斥其他语法构造。 没有办法开始,这个想法被推迟了一段时间。


今年5月,由于某种原因,我决定使用D语言,因为确信D是动态的。 我一直想知道这个想法在哪里以及为什么在我的脑海中,所以我没有找到答案。 但是,这不再重要了,因为整个夏天我都被重写而烦恼。


项目的实质


KNX BAOS 830/832/838模块通过UART连接到计算机,ObjectServer协议包装在FT1.2中。 应用程序与/dev/ttyXXX建立连接,处理传入数据,将来自PUB / SUB通道的用户请求字节发送到同一队列,或基于Redis列表发送到作业队列(对于nodejs,队列在bee-queue包中实现。 )


 queue.on("job", data => { //   : //  ,     //  ,      }); baos.on("data", data => { // ,  :    //  ,      //   -     pub/sub }); 

活力


js中的JSON是天生的事物;我不知道如何在静态类型的语言中进行处理。 事实证明,有点不同。 例如,采用get value方法。 作为参数,它可以使用数字-日期点数字或数字数组。


在js中,执行检查:


 if (Array.isArray(payload)) { //     return values; } if (typeof id === "number") { //     return value; } throw new Error(" id"); 

在D上基本相同:


 if (payload.type() == JSONType.integer) { //    } else if (payload.type() === JSONType.array) { //    } else { throw Errors.wrong_payload_type; } 

出于某种原因,在考虑Rust的时候,正是由于缺乏对使用JSON的理解而使我放慢了速度。 与动态有关的另一点:数组。 在js中,您已经习惯了调用push方法添加元素就足够了。 在C语言中,动态性是通过手动分配内存来实现的,但是我真的不想爬到那里。 事实证明,Dlang支持动态数组。


 ubyte[] res; //   -     res.length = 1000; //        res.length = count; //        1 

js中的传入UART数据已转换为Object 。 出于这些目的,D中的结构,带有值的枚举和连接非常有用。


 enum OS_Services { unknown, GetServerItemReq = 0x01, GetServerItemRes = 0x81, SetServerItemReq = 0x02, SetServerItemRes = 0x82, // ... } // ... struct OS_Message { OS_Services service; OS_MessageDirection direction; bool success; union { // union of possible service returned structs // DatapointDescriptions/DatapointValues/ServerItems/ParameterBytes OS_DatapointDescription[] datapoint_descriptions; OS_DatapointValue[] datapoint_values; OS_ServerItem[] server_items; Exception error; }; } 

带有传入消息:


 ubyte mainService = data.read!ubyte(); ubyte subService = data.read!ubyte(); try { if (mainService == OS_MainService) { switch(subService) { case OS_Services.GetServerItemRes: result.direction = OS_MessageDirection.response; result.service= OS_Services.GetServerItemRes; result.success = true; result.server_items = _processServerItemRes(data); break; case OS_Services.SetServerItemRes: result.direction = OS_MessageDirection.response; // ... 

在js中,我将字节值存储在数组中,对传入的数据进行了搜索,并返回了带有服务名称的字符串。 结构,枚举和关联看起来更严格。


使用字节数据数组


Node.js我喜欢Buffer的抽象。 例如:使用readUInt16BE(offset)方法将两个字节转换为无符号整数很方便,以便写入-活跃使用的缓冲区与writeUInt16BE(value, offset)配合使用二进制协议。 对于dlang,我最初启动了类似的类似仓库的软件包。 答案在std.bitmanip标准库中找到。 对于2字节长的无符号整数: ushort start = data.read!ushort() ,用于写入: result.write!ushort(start, 2); 其中第二个参数是偏移量。


EE,promise,异步/等待。


最糟糕的部分是没有EventEmitter编程。 在node.js中,侦听器函数只是简单地注册,并在事件发生时被调用。 因此,不必刻苦思考。 tinylis和serialport dlang程序包(我的应用程序的依赖性)具有用于处理消息的非阻塞方法。 解决方案很简单:到目前为止,轮流接收串行端口和pub / sub通道消息是正确的。 在向pub / sub通道发送用户请求的情况下,程序应向串行端口发送一条消息,获取结果,然后将用户发送回pub / sub。 决定采用阻止串行请求的方法。


 while(!(_responseReceived || _resetInd || _interrupted)) { try { processIncomingData(); processIncomingInterrupts(); if (_resetInd || _interrupted) { _response.success = false; _response.service = OS_Services.unknown; _response.error = Errors.interrupted; _responseReceived = true; _ackReceived = true; } // ... // ... return _response; 

在while循环中,数据是通过非阻塞方法processIncomingData()轮询的。 还提供了重新启动KNX模块(断开并重新连接到KNX总线或软件)的可能性。 另外, processIncomingInterrupts()处理程序还会检查服务pub / sub通道是否有reset请求。 与以前的js实现不同,没有承诺或异步功能。 我不得不考虑程序的结构(即函数调用的顺序),但是由于缺少不必要的抽象,因此编程变得更加容易。 实际上,当在js代码中调用await someAsyncMethod时,异步函数被称为阻塞,通过事件循环。 语言的可能性很大,但是您可以不用语言。


差异性


作业队列。 为此,node.js实现使用bee-queue包。 在D的实现中,仅通过pub / sub发送请求。
否则,一切几乎都是相同的。


编译版本消耗的RAM少10倍,这对于单板计算机可能很重要。


合编


在aarch64平台上使用ldc进行了编译。


要安装lcd:


 curl -fsS https://dlang.org/install.sh | bash -s ldc 

组装好的主板由三个主要组件组成:作为计算机的NanoPi Neo Core2,用于与KNX总线通信的KNX BAOS模块830和用于PoE电源的Silvertel Ag9205,在其上进行了编程。


板外观


结论


我不会判断哪种语言更好或更差。 对每个人来说:js非常适合学习,抽象级别(承诺,发射器)使构建应用程序结构变得容易而快捷。 我在dlang上实施了一个清晰的,记住了一年半的计划,该做什么。 当您知道需要处理哪些数据以及如何处理这些数据时,静态键入就不会令人恐惧。 非阻塞方法使您可以组织工作周期。 这是我关于D的第一篇作品,这是一部引人入胜且内容丰富的作品。


至于离开舒适区(如标题所示):在我的情况下,恐惧的眼睛很大,这很长一段时间以来一直使我无法尝试除nodejs之外的其他方法。


源代码是开放的,可以在github.com/dobaos/dobaos中找到

Source: https://habr.com/ru/post/zh-CN459014/


All Articles