Sortir de votre zone de confort: de nodejs à dlang

En 2017, j'ai commencé à écrire un projet sur nodejs - une implémentation du protocole Weinzierl ObjectServer pour accéder aux valeurs KNX. Au cours du processus d'écriture, nous avons étudié: travailler avec des protocoles binaires, présenter des données, travailler avec des sockets (sockets unix en particulier), travailler avec la base de données redis et les canaux pub / sub.


Le projet a atteint une version stable. À cette époque, je choisis lentement d'autres langues, en particulier Dart et Flutter comme application. Sur l'étagère dépoussiérée sans action achetée lors du manuel de l'étudiant G. Schildt.


Une pensée persistante pour réécrire le projet en C s'est installée dans ma tête. Je considère les options Go, Rust, repoussant d'autres constructions syntaxiques. Il n'y a aucun moyen de commencer, l'idée est reportée pendant un certain temps.


En mai de cette année, j'ai décidé de regarder le langage D, convaincu pour une raison que la lettre D signifie dynamique. Je me suis longtemps demandé où et pourquoi cette pensée était dans ma tête, alors je n'ai pas trouvé de réponse. MAIS ce n'est plus important, puisque j'ai été emporté par la réécriture pendant tout l'été.


L'essence du projet


Les modules KNX BAOS 830/832/838 sont connectés via UART à un ordinateur, le protocole ObjectServer est encapsulé dans FT1.2. L'application établit une connexion avec /dev/ttyXXX , traite les données entrantes, envoie les octets de la demande utilisateur provenant du canal PUB / SUB à la même file d'attente ou à la file d'attente des travaux basée sur les listes Redis (pour nodejs, les files d'attente sont implémentées dans le package de file d'attente d'abeilles). )


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

Dynamisme


JSON en js est une chose native; je n'avais aucune idée de la façon dont le traitement se produit dans les langages typés statiquement. En fait, une petite différence. Par exemple, prenez la méthode get value . Comme arguments, il prend soit un nombre - le numéro de point de date, soit un tableau de nombres.


Dans js, les contrôles sont effectués:


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

Essentiellement la même chose sur D:


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

Pour une raison quelconque, au moment de Rust-une considération, c'est le manque de compréhension de travailler avec JSON qui m'a ralenti. Un autre point lié au dynamisme: les tableaux. En js, on s'habitue au fait qu'il suffit d'appeler la méthode push pour ajouter un élément. En C, le dynamisme est implémenté par l'allocation manuelle de mémoire, mais je ne voulais pas vraiment y monter. Il s'est avéré que Dlang prend en charge les tableaux dynamiques.


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

Les données UART entrantes dans js ont été converties en Object . À ces fins, les structures, les énumérations avec des valeurs et les jointures sont excellentes en 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; }; } 

Avec un message entrant:


 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; // ... 

Dans js, j'ai stocké les valeurs d'octets dans un tableau, avec les données entrantes, j'ai recherché et renvoyé une chaîne avec le nom du service. Les structures, les énumérations et les associations semblent plus strictes.


Travailler avec des tableaux de données d'octets


Node.js J'aime l'abstraction de Buffer . Par exemple: il est pratique d'effectuer la conversion de deux octets en un entier non signé en utilisant la readUInt16BE(offset) , pour l'écriture - writeUInt16BE(value, offset) , des tampons activement utilisés pour travailler avec le protocole binaire. Pour dlang, j'ai d'abord commencé les packages de référentiel laineux avec quelque chose de similaire. La réponse a été trouvée dans la bibliothèque standard std.bitmanip . Pour les entiers non signés de 2 octets de long: ushort start = data.read!ushort() , pour l'écriture: result.write!ushort(start, 2); où le 2ème argument est le décalage.


EE, promet, asynchrone / attend.


Le pire était la programmation sans EventEmitter . Dans node.js, les fonctions d'écoute sont simplement enregistrées, et lors d'un événement, elles sont appelées. Ainsi, il n'est pas nécessaire de réfléchir sérieusement. Les packages tinylis et serialport dlang (mes dépendances d'application) ont des méthodes non bloquantes pour le traitement des messages. La solution est simple: pour l'instant, il est vrai de recevoir tour à tour les messages du port série et des canaux pub / sub. Dans le cas d'une demande d'utilisateur entrante au canal pub / sub, le programme devrait envoyer un message au port série, obtenir le résultat et renvoyer l'utilisateur au pub / sub. Il a été décidé de rendre les méthodes de blocage des requêtes en série.


 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; 

Dans une boucle while, les données sont interrogées par la méthode non bloquante processIncomingData() . La probabilité que le module KNX soit redémarré (déconnecté et reconnecté au bus KNX ou au logiciel) est également fournie. En outre, le gestionnaire processIncomingInterrupts() vérifie la demande de reset canal de publication / sous-service. Pas de promesses ou de fonctions asynchrones, contrairement aux implémentations js précédentes. J'ai dû penser à la structure du programme (à savoir, la séquence des fonctions d'appel), mais, en raison du manque d'abstractions inutiles, il est devenu plus facile de programmer. En fait, lorsque l' await someAsyncMethod est appelée dans le code js, la fonction asynchrone est appelée en tant que blocage, en passant par la boucle d'événement. La possibilité même de la langue est bonne, mais vous pouvez vous en passer.


Les différences


File d'attente des travaux. L'implémentation node.js utilise le package de bee-queue à cet effet. Dans une implémentation sur D, les demandes sont envoyées uniquement via pub / sub.
Sinon, tout est presque identique.


La version compilée consomme 10 fois moins de RAM, ce qui peut être important pour les ordinateurs à carte unique.


Compilation


La compilation a été effectuée à l'aide de ldc sur la plate-forme aarch64.


Pour installer ldc:


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

Une carte mère a été assemblée, composée de trois composants principaux: NanoPi Neo Core2 en tant qu'ordinateur, module KNX BAOS 830 pour la communication avec le bus KNX, et Silvertel Ag9205 pour l'alimentation PoE, sur lesquels la programmation a été effectuée.


Apparence du conseil d'administration


Conclusion


Je ne jugerai pas quelle langue est meilleure ou pire. A chacun ses goûts: js est idéal pour étudier, le niveau d'abstractions (promesses, émetteurs) vous permet de construire facilement et rapidement la structure de l'application. J'ai abordé la mise en œuvre sur dlang avec un plan clair et mémorisé pendant un an et demi, que faire. Lorsque vous savez quelles données doivent être traitées et comment, la saisie statique n'est pas effrayante. Les méthodes non bloquantes vous permettent d'organiser un cycle de travail. Ce fut mon premier travail sur D, un travail fascinant et instructif.


Quant à quitter la zone de confort (comme indiqué dans le titre): dans mon cas, la peur avait de grands yeux, ce qui m'a longtemps empêché d'essayer autre chose que nodejs.


Les codes source sont ouverts et peuvent être trouvés sur github.com/dobaos/dobaos

Source: https://habr.com/ru/post/fr459014/


All Articles