الخروج من منطقة الراحة الخاصة بك: من nodejs إلى dlang

في عام 2017 ، بدأت في كتابة مشروع على nodejs - تطبيق بروتوكول Weinzierl ObjectServer للوصول إلى قيم KNX. أثناء عملية الكتابة ، درسنا: العمل مع البروتوكولات الثنائية ، وتقديم البيانات ، والعمل مع مآخذ (مآخذ يونيكس على وجه الخصوص) ، والعمل مع قاعدة بيانات redis وقنوات pub / sub.


لقد وصل المشروع إلى نسخة مستقرة. في هذا الوقت ، اخترت ببطء لغات أخرى ، ولا سيما دارت ورفرفة كتطبيق له. على الرف غبار دون إجراء شراء في وقت دليل الطالب G. شيلدت.


الفكر المستمر لإعادة كتابة المشروع في C استقر في رأسي. أنا أفكر في خيارات الذهاب ، الصدأ ، صد الإنشاءات النحوية الأخرى. لا توجد طريقة للبدء ، تم تأجيل الفكرة لفترة من الوقت.


في أيار (مايو) من هذا العام ، قررت إلقاء نظرة على اللغة D ، لسبب ما مقتنعًا أن الحرف D يعني ديناميكيًا. لقد تساءلت لفترة طويلة عن مكان وسبب هذا الفكر في رأسي ، لذلك لم أجد إجابة. لكن هذا لم يعد مهمًا ، حيث تم ترحيلي من جديد طوال الصيف.


جوهر المشروع


يتم توصيل الوحدات KNX BAOS 830/832/838 عبر UART إلى جهاز كمبيوتر ، يتم لف بروتوكول ObjectServer في FT1.2. ينشئ التطبيق اتصالًا بـ /dev/ttyXXX ، ويعالج البيانات الواردة ، ويرسل البايتات من طلب المستخدم المحول إلى قناة PUB / SUB إلى قائمة الانتظار نفسها من رسائل JSON ، أو إلى قائمة انتظار المهام استنادًا إلى قوائم Redis (بالنسبة للعقدة ، يتم تنفيذ قوائم الانتظار باستخدام حزمة bee-queue ).


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

ديناميكية


JSON في js شيء أصلي ؛ لم يكن لدي أي فكرة عن كيفية حدوث المعالجة باللغات المكتوبة بشكل ثابت. كما اتضح ، فرق بسيط. على سبيل المثال ، get value طريقة 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 

تم تحويل بيانات UART الواردة في js إلى 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); حيث الوسيطة الثانية هي الإزاحة.


هاء ، الوعود ، متزامن / تنتظر.


أسوأ جزء كان البرمجة دون EventEmitter . في node.js ، يتم تسجيل وظائف المستمع ببساطة ، ويطلق عليها اسم حدث. وبالتالي ، لا يتعين على المرء أن يفكر مليا. تحتوي حزم tinylis و serialport dlang (تبعيات تطبيقي) على أساليب غير محظورة لمعالجة الرسائل. الحل بسيط: في الوقت الحالي ، من الصحيح تلقي رسائل المنفذ التسلسلي وقناة الحانة / الفرعية بدورها. في حالة طلب مستخدم وارد إلى قناة 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; 

في حلقة من الوقت ، يتم استقصاء البيانات عن طريق عملية non-blocking processIncomingData() . يتم توفير احتمال إعادة تشغيل وحدة KNX (قطع الاتصال وإعادة توصيلها إلى ناقل KNX أو البرنامج). أيضًا ، يتحقق معالج processIncomingInterrupts() من قناة الخدمة / القناة الفرعية لطلب reset . لا توجد وعود أو وظائف غير متزامنة ، على عكس تطبيقات js السابقة. كان علي أن أفكر في بنية البرنامج (أي تسلسل استدعاءات الوظائف) ، ولكن بسبب عدم وجود تجريدات غير ضرورية ، أصبح البرنامج أكثر سهولة. في الواقع ، عند await someAsyncMethod في كود js ، فإن الوظيفة غير المتزامنة تسمى الحجب وتمرير حلقة الحدث. إن إمكانية اللغة جيدة ، لكن يمكنك الاستغناء عنها.


الخلافات


طابور الوظيفة. يستخدم تطبيق node.js حزمة bee-queue لهذا الغرض. عند التنفيذ على D ، يتم إرسال الطلبات فقط من خلال pub / sub.
خلاف ذلك ، كل شيء مماثل تقريبا.


يستهلك الإصدار المترجم أقل من ذاكرة الوصول العشوائي بمقدار 10 مرات ، مما قد يكون مهمًا لأجهزة الكمبيوتر ذات اللوحة الواحدة.


مجموعة


تم إجراء التجميع باستخدام ldc على النظام الأساسي aarch64.


لتثبيت ldc:


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

تم تجميع اللوحة الأم ، التي تتألف من ثلاثة مكونات رئيسية: NanoPi Neo Core2 كجهاز كمبيوتر ، وحدة KNX BAOS 830 للتواصل مع ناقل KNX ، و Silvertel Ag9205 for PoE power ، التي تم تنفيذ البرمجة عليها.


مظهر المجلس


استنتاج


لن أحكم على اللغة التي هي أفضل أو أسوأ. لكل منهم: js رائع للتعلم ، ومستوى التجريد (الوعود ، والبواعث) يجعل من السهل والسريع بناء بنية التطبيق. اقتربت من التنفيذ على dlang من خلال خطة واضحة ومحفوظة لمدة عام ونصف ، ما يجب القيام به. عندما تعرف البيانات التي يجب معالجتها وكيف ، فإن الكتابة الثابتة ليست مخيفة. تسمح لك الطرق غير المحظورة بتنظيم دورة عمل. كان هذا أول عمل لي في D ، وهو عمل رائع وغني بالمعلومات.


بالنسبة لترك منطقة الراحة (كما هو موضح في العنوان): في حالتي ، كان الخوف ذو عيون كبيرة ، وهو ما حالني لفترة طويلة منعتني من تجربة شيء آخر غير العقدة.


أكواد المصدر مفتوحة ويمكن الاطلاع عليها على github.com/dobaos/dobaos

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


All Articles