
في هذه المقالة سأوضح كيفية إنشاء تطبيق ويب باستخدام Node.js ، والذي يسمح لك بتتبع نتائج مباريات NHL في الوقت الحقيقي. يتم تحديث المؤشرات وفقًا للتغيرات في النتيجة خلال المباريات.
لقد استمتعت حقًا بكتابة هذا المقال ، لأن العمل عليه تضمن شيئين أحببتهما: تطوير البرمجيات والرياضة.
في سياق العمل ، سنستخدم الأدوات التالية:
- Node.js ؛
- Socket.io ؛
- MySportsFeed.com.
إذا لم يكن Node.js مثبتًا لديك ، فانتقل إلى صفحة التنزيل وتثبيته ، وبعد ذلك سنستمر.
ما هو socket.io؟
هذه هي التقنية التي تربط العميل بالخادم. في المثال الحالي ، يكون العميل مستعرض ويب ، والخادم هو Node.js. يمكن للخادم العمل في وقت واحد مع العديد من العملاء في أي وقت.
بمجرد إنشاء الاتصال ، يمكن للخادم إرسال رسائل إلى جميع العملاء أو واحد منهم فقط. وهذا بدوره يمكن أن يرسل رسائل إلى الخادم ، ويوفر الاتصال في اتجاهين.
قبل Socket.io ، كانت تطبيقات الويب تعمل عادةً على AJAX. وينص استخدامه على حاجة العميل لاستطلاع الخادم والعكس بالعكس بحثًا عن أحداث جديدة. على سبيل المثال ، يمكن إجراء هذه الاستطلاعات كل 10 ثوانٍ للتحقق من الرسائل الجديدة.
هذا أعطى عبئا إضافيا ، حيث تم البحث عن رسائل جديدة حتى عندما لم تكن على الإطلاق.
عند استخدام Socket.io ، يتم تلقي الرسائل في الوقت الفعلي دون الحاجة إلى التحقق باستمرار من وجودها ، مما يقلل من الحمل.
تطبيق عينة Socket.io
قبل أن نبدأ في جمع بيانات المنافسة في الوقت الفعلي ، فلنقم بإنشاء تطبيق توضيحي يوضح كيفية عمل Socket.io.
أولاً ، سأقوم بإنشاء تطبيق Node.js. في نافذة وحدة التحكم ، تحتاج إلى الانتقال إلى دليل C: \ GitHub \ NodeJS ، وإنشاء مجلد جديد للتطبيق وفيه تطبيق جديد:
cd \GitHub\NodeJS mkdir SocketExample cd SocketExample npm init
تركت الإعدادات الافتراضية ، يمكنك أن تفعل الشيء نفسه.
نظرًا لأننا نبني تطبيق ويب ، سأستخدم حزمة NPM تسمى Express لتبسيط التثبيت. في موجه الأوامر ، قم بتشغيل الأوامر التالية: npm install express - حفظ.
بالطبع ، يجب علينا تثبيت حزمة Socket.io أيضًا: npm install socket.io - حفظ
الآن أنت بحاجة إلى بدء خادم الويب. للقيام بذلك ، قم بإنشاء ملف index.js جديد وضع قسم التعليمات البرمجية التالي:
var app = require('express')(); var http = require('http').Server(app); app.get('/', function(req, res){ res.sendFile(__dirname + '/index.html'); }); http.listen(3000, function(){ console.log('HTTP server started on port 3000'); });
إذا لم يكن Express مألوفًا لك ، فإن مثال الكود أعلاه يتضمن مكتبة Express ، وهنا نقوم بإنشاء خادم HTTP جديد. في المثال ، يستمع خادم HTTP على المنفذ 3000 ، أي
المضيف المحلي : 3000. يذهب المسار إلى الجذر ، "/". يتم إرجاع النتيجة كملف HTML index.html.
قبل إنشاء هذا الملف ، دعنا ننهي بدء تشغيل الخادم من خلال تكوين Socket.io. لإنشاء خادم Socket ، قم بتشغيل الأوامر التالية:
var io = require('socket.io')(http); io.on('connection', function(socket){ console.log('Client connection received'); });
كما هو الحال مع Express ، يبدأ الرمز باستيراد مكتبة Socket.io. يشار إلى ذلك بواسطة متغير io. بعد ذلك ، باستخدام هذا المتغير ، قم بإنشاء معالج حدث باستخدام الوظيفة on. يتم تشغيل هذا الحدث في كل مرة يتصل فيها العميل بالخادم.
الآن لنقم بإنشاء عميل بسيط. للقيام بذلك ، قم بعمل ملف index.html وضع الكود التالي في الداخل:
<!doctype html> <html> <head> <title>Socket.IO Example</title> </head> <body> <script src="/socket.io/socket.io.js"></script> <script> var socket = io(); </script> </body> </html>
يقوم HTML أعلاه بتحميل عميل JavaScript Socket.io ويهيئ الاتصال بالخادم. لمشاهدة مثال ، قم بتشغيل Node: node index.js.
بعد ذلك ، في المستعرض ، أدخل
localhost : 3000. ستظل الصفحة فارغة ، ولكن إذا نظرت إلى وحدة التحكم أثناء تشغيل العقدة ، فستظهر رسالتان:
بدأ خادم HTTP على المنفذ 3000
تلقى اتصال العميلالآن بعد أن وصلنا بنجاح ، دعنا نواصل العمل. على سبيل المثال ، أرسل رسالة إلى العميل من الخادم. بعد ذلك ، عندما يستلمها العميل ، سيتم إرسال رسالة استجابة:
io.on('connection', function(socket){ console.log('Client connection received'); socket.emit('sendToClient', { hello: 'world' }); socket.on('receivedFromClient', function (data) { console.log(data); }); });
تم تحديث وظيفة io.on السابقة لتشمل عدة أسطر جديدة من التعليمات البرمجية. يرسل الأول ، socket.emit ، رسالة إلى العميل. sendToClient هو اسم الحدث. من خلال تسمية الأحداث ، يمكنك إرسال أنواع مختلفة من الرسائل ، حتى يتمكن العميل من تفسيرها بشكل مختلف. تحديث آخر هو socket.on ، والذي يحتوي أيضًا على اسم الحدث: ReceivedFromClient. كل هذا يخلق وظيفة تتلقى البيانات من العميل. في هذه الحالة ، يتم تسجيلها أيضًا في نافذة وحدة التحكم.
تكمل الخطوات المكتملة إعداد الخادم. الآن يمكنه استقبال وإرسال البيانات من أي عميل متصل.
لننهي هذا المثال عن طريق تحديث العميل من خلال تلقي حدث sendToClient. عندما يتم تلقي حدث ، يتم تلقي استجابة FromClient.
بهذا ينتهي جزء JavaScript من HTML. في index.html ، أضف:
var socket = io(); socket.on('sendToClient', function (data) { console.log(data); socket.emit('receivedFromClient', { my: 'data' }); });
باستخدام متغير المقبس المدمج ، نحصل على منطق مماثل على الخادم مع وظيفة socket.on. يستمع العميل لحدث sendToClient. بمجرد اتصال العميل ، يرسل الخادم هذه الرسالة. يقوم العميل باستلامه بتسجيل الحدث في وحدة تحكم المستعرض. بعد ذلك ، يستخدم العميل نفس socket.emit مثل الخادم المستخدم لإرسال الحدث الأصلي. في هذه الحالة ، يرسل العميل الحدث FromClient المستلم إلى الخادم. عندما يتلقى رسالة ، يتم تسجيلها في نافذة وحدة التحكم.
جربها بنفسك. أولاً ، في وحدة التحكم ، قم بتشغيل تطبيق Node الخاص بك: node index.js. ثم قم بتحميل
localhost : 3000 في المتصفح.
تحقق من وحدة تحكم المتصفح الخاص بك وسترى ما يلي في سجلات JSON: {hello: "world"}
بعد ذلك ، أثناء تشغيل تطبيق العقدة ، سترى ما يلي:
بدأ خادم HTTP على المنفذ 3000
تلقى اتصال العميل
{my: 'data'}يمكن لكل من العميل والخادم استخدام بيانات JSON لأداء مهام محددة. دعونا نرى كيف يمكنك العمل مع بيانات المنافسة في الوقت الحقيقي.
معلومات المسابقة
بعد أن نفهم مبادئ إرسال البيانات وتلقيها من قبل العميل والخادم ، يجدر محاولة التأكد من إجراء التحديثات في الوقت الفعلي. لقد استخدمت بيانات المنافسة ، على الرغم من أنه لا يمكن فعل نفس الشيء ليس فقط بالمعلومات الرياضية. ولكن بما أننا نعمل معها ، فنحن بحاجة إلى العثور على المصدر. أنها سوف تخدم
MySportsFeeds . الخدمة مدفوعة - من $ 1 في الشهر ، ضع في اعتبارك.
بمجرد إعداد حسابك ، يمكنك البدء باستخدام واجهة برمجة التطبيقات الخاصة بهم. يمكنك استخدام حزمة NPM لهذا: npm install mysportsfeeds-node --save.
بعد تثبيت الحزمة ، نقوم بتوصيل API:
var MySportsFeeds = require("mysportsfeeds-node"); var msf = new MySportsFeeds("1.2", true); msf.authenticate("********", "*********"); var today = new Date(); msf.getData('nhl', '2017-2018-regular', 'scoreboard', 'json', { fordate: today.getFullYear() + ('0' + parseInt(today.getMonth() + 1)).slice(-2) + ('0' + today.getDate()).slice(-2), force: true });
في المثال أعلاه ، استبدل بياناتي ببياناتك.
يقوم الرمز بإجراء مكالمة API للحصول على نتائج مسابقة NHL اليوم. متغير التاريخ هو ما يحدد التاريخ. لقد استخدمت أيضًا القوة والصواب من أجل استعادة البيانات حتى إذا كانت النتائج هي نفسها.
مع الإعداد الحالي ، تتم كتابة نتائج مكالمة API إلى ملف نصي. في المثال الأخير ، سنقوم بتغيير هذا ؛ لأغراض العرض التوضيحي ، يمكن عرض ملف النتيجة في محرر نص لفهم محتويات الإجابة. في نتيجتنا ، نرى كائن جدول النتائج. يحتوي هذا الكائن على صفيف يسمى gameScore. يحفظ نتيجة كل لعبة. يحتوي كل كائن بدوره على كائن تابع يسمى لعبة. يوفر هذا الكائن معلومات حول من يلعب.
خارج كائن اللعبة ، هناك العديد من المتغيرات التي تعرض الحالة الحالية للعبة. تتغير البيانات حسب نتائجها. عندما لم تبدأ اللعبة بعد ، يتم استخدام المتغيرات التي توفر معلومات حول وقت حدوث ذلك. عند بدء اللعبة ، يتم توفير بيانات إضافية عن النتائج ، بما في ذلك معلومات عن الفترة الحالية ومقدار الوقت المتبقي. من أجل فهم أفضل لما هو على المحك ، دعنا ننتقل إلى القسم التالي.
تحديثات في الوقت الحقيقي
لدينا كل قطع اللغز ، لذلك دعونا نجمعها! لسوء الحظ ، تتمتع MySportsFeeds بدعم محدود لإصدار البيانات ، لذا يتعين عليك طلب المعلومات باستمرار. هناك نقطة إيجابية هنا: نحن نعلم أن البيانات تتغير مرة واحدة فقط كل 10 دقائق ، لذلك ليس من الضروري استجواب الخدمة كثيرًا. يمكن إرسال البيانات المستلمة من الخادم إلى جميع العملاء المتصلين.
للحصول على البيانات اللازمة ، سأستخدم وظيفة setInterval JavaScript ، والتي تتيح لك الوصول إلى API (في حالتي) كل 10 دقائق للعثور على التحديثات. عند وصول البيانات ، يتم إرسال الحدث إلى جميع العملاء المتصلين. ثم يتم تحديث النتائج عبر JavaScript في متصفح الويب.
يتم استدعاء MySportsFeeds أيضًا عند تشغيل تطبيق العقدة أولاً. سيتم استخدام النتائج لأي عملاء يتصلون قبل أول 10 دقائق. يتم تخزين المعلومات حول هذا في متغير عام. يتم تحديثه ، بدوره ، كجزء من المسح الفاصل. وهذا يضمن حصول كل عميل على نتائج ذات صلة.
لكي يكون كل شيء على ما يرام في ملف index.js الرئيسي ، قمت بإنشاء ملف جديد يسمى data.js. يحتوي على دالة تم تصديرها من index.js التي تقوم بإجراء مكالمة سابقة إلى MySportsFeeds API. فيما يلي المحتويات الكاملة لهذا الملف:
var MySportsFeeds = require("mysportsfeeds-node"); var msf = new MySportsFeeds("1.2", true, null); msf.authenticate("*******", "******"); var today = new Date(); exports.getData = function() { return msf.getData('nhl', '2017-2018-regular', 'scoreboard', 'json', { fordate: today.getFullYear() + ('0' + parseInt(today.getMonth() + 1)).slice(-2) + ('0' + today.getDate()).slice(-2), force: true }); };
يتم تصدير دالة getData وترجع نتائج المكالمة. دعونا نلقي نظرة على ما لدينا في ملف index.js.
var app = require('express')(); var http = require('http').Server(app); var io = require('socket.io')(http); var data = require('./data.js');
تعمل السطور السبعة الأولى من الكود أعلاه على تهيئة المكتبات المطلوبة واستدعاء أحدث متغير عام للبيانات. أحدث قائمة من المكتبات المستخدمة هي: Express ، Http Server ، تم إنشاؤها باستخدام Express ، Socket.io بالإضافة إلى ملف data.js الذي تم إنشاؤه للتو.
مع مراعاة الاحتياجات ، يملأ التطبيق أحدث البيانات (latestData) للعملاء الذين يتصلون عند بدء تشغيل الخادم لأول مرة:
تحدد الأسطر القليلة التالية مسار الصفحة الرئيسية للموقع (في حالتنا
localhost : 3000 /) وتوجه خادم HTTP للاستماع على المنفذ 3000.
ثم يتم تكوين Socket.io للبحث عن اتصالات جديدة. عند اكتشافها ، يرسل الخادم بيانات الأحداث مع محتويات أحدث متغير للبيانات.
وأخيرًا ، يُنشئ الجزء الأخير من الرمز فترة الاستقصاء المطلوبة. عندما يتم الكشف عن ذلك ، يتم تحديث متغير latestData بنتائج استدعاء API. ثم تقوم هذه البيانات بتمرير نفس الحدث لجميع العملاء.
كما نرى ، عندما يتصل العميل ويتم تعريف الحدث ، يتم إصداره مع متغير مأخذ التوصيل. يتيح لك هذا إرسال حدث إلى عميل متصل محدد. داخل الفاصل الزمني ، يستخدم io العالمي لإرسال الحدث. يرسل البيانات لجميع العملاء. اكتمل إعداد الخادم.
كيف ستبدو
الآن دعنا نعمل على الواجهة الأمامية للعميل. في مثال مبكر ، قمت بإنشاء ملف index.html الأساسي ، الذي ينشئ اتصالاً مع العميل لتسجيل أحداث الخادم وإرسالها. الآن سأقوم بتوسيع قدرات الملف.
نظرًا لأن الخادم يرسل إلينا كائن JSON ، فسأستخدم jQuery وامتداد jQuery المسمى JsRender. هذه مكتبة قوالب. سيسمح لي بإنشاء قالب HTML سيتم استخدامه لعرض محتويات كل لعبة NHL بطريقة مناسبة. الآن يمكنك أن ترى اتساع قدراتها. يحتوي الكود على أكثر من 40 سطرًا ، لذلك سوف أقسمه إلى عدة أقسام أصغر ، وفي النهاية سأعرض كل محتوى HTML.
إليك ما يتم استخدامه لعرض بيانات اللعبة:
<script id="gameTemplate" type="text/x-jsrender"> <div class="game"> <div> {{:game.awayTeam.City}} {{:game.awayTeam.Name}} at {{:game.homeTeam.City}} {{:game.homeTeam.Name}} </div> <div> {{if isUnplayed == "true" }} Game starts at {{:game.time}} {{else isCompleted == "false"}} <div>Current Score: {{:awayScore}} - {{:homeScore}}</div> <div> {{if currentIntermission}} {{:~ordinal_suffix_of(currentIntermission)}} Intermission {{else currentPeriod}} {{:~ordinal_suffix_of(currentPeriod)}}<br/> {{:~time_left(currentPeriodSecondsRemaining)}} {{else}} 1st {{/if}} </div> {{else}} Final Score: {{:awayScore}} - {{:homeScore}} {{/if}} </div> </div> </script>
يتم تعريف القالب باستخدام علامة البرنامج النصي. يحتوي على معرف قالب ونوع برنامج نصي خاص يسمى text / x-jsrender. يحدد القالب حاوية div لكل لعبة تحتوي على فئة لعبة لتطبيق نمط أساسي محدد. داخل هذا div هو بداية القالب.
يعرض القسم التالي فريق الضيف والفريق المضيف. يتم تنفيذ ذلك من خلال الجمع بين اسم المدينة واسم الفريق مع كائن اللعبة من بيانات MySportsFeeds.
{{: game.awayTeam.City}} هي كيفية تحديد كائن سيتم استبداله بقيمة مادية عند عرض القالب. يتم تعريف بناء الجملة هذا بواسطة مكتبة JsRender.
عندما تكون اللعبة في وضع عدم التشغيل ، يظهر سطر تبدأ اللعبة به {{: game.time}}.
حتى اكتمال اللعبة ، يتم عرض النتيجة الحالية: {{: awayScore}} - {{: homeScore}}. وأخيرًا ، خدعة صغيرة ستحدد ما هي الفترة الآن ، وتوضيح ما إذا كان هناك استراحة الآن.
إذا ظهر متغير الإدخال الحالي في النتائج ، فعندئذٍ أستخدم الدالة I ، المحددة بواسطة ordinal_suffix_of ، والتي تحول رقم الفترة إلى النص التالي: الفاصل الأول (الثاني ، الثالث ، إلخ).
عندما لا يكون هناك استراحة ، أبحث عن قيمة currentPeriod. Ordinal_suffix_of يستخدم هنا أيضًا لإظهار أن اللعبة في الفترات الأولى (الثانية ، الثالثة ، إلخ).
بالإضافة إلى ذلك ، يتم استخدام دالة أخرى قمت بتعريفها على أنها time_left لتحويل عدد الثواني المتبقية حتى نهاية الفترة. على سبيل المثال: 10:12.
يعرض الجزء الأخير من الرمز النتيجة النهائية عند اكتمال اللعبة.
في ما يلي مثال لكيفية ظهوره عندما تكون هناك قائمة مختلطة من الألعاب المكتملة والألعاب التي لم تنته بعد والألعاب التي لم تبدأ بعد (لست مصممًا جيدًا جدًا ، لذلك تبدو النتيجة كما ينبغي عندما ينشئ المطور لعبة مخصصة واجهة تطبيق افعلها بنفسك):

التالي هو مقتطف جافا سكريبت الذي يقوم بإنشاء مأخذ توصيل ، ووظائف المساعد ، ordinal_suffix_of و time_left ، ومتغير يشير إلى قالب jQuery الذي تم إنشاؤه.
<script> var socket = io(); var tmpl = $.templates("#gameTemplate"); var helpers = { ordinal_suffix_of: function(i) { var j = i % 10, k = i % 100; if (j == 1 && k != 11) { return i + "st"; } if (j == 2 && k != 12) { return i + "nd"; } if (j == 3 && k != 13) { return i + "rd"; } return i + "th"; }, time_left: function(time) { var minutes = Math.floor(time / 60); var seconds = time - minutes * 60; return minutes + ':' + ('0' + seconds).slice(-2); } }; </script>
الجزء الأخير هو رمز لتلقي حدث مأخذ التوصيل وعرض القالب:
socket.on('data', function (data) { console.log(data); $('#data').html(tmpl.render(data.scoreboard.gameScore, helpers)); });
لدي محدد له معرف بيانات. نتيجة تقديم نموذج (tmpl.render) يكتب HTML إلى هذه الحاوية. وهو أمر رائع حقًا - يمكن أن تأخذ مكتبة JsRender مجموعة من البيانات ، في هذه الحالة data.scoreboard.gameScore ، والتي تتكرر من خلال كل عنصر في المصفوفة وتنشئ لعبة واحدة لكل عنصر.
فيما يلي النسخة النهائية الموعودة أعلاه ، حيث يتم تجميع HTML و JavaScript معًا:
<!doctype html> <html> <head> <title>Socket.IO Example</title> </head> <body> <div id="data"> </div> <script id="gameTemplate" type="text/x-jsrender"> <div class="game"> <div> {{:game.awayTeam.City}} {{:game.awayTeam.Name}} at {{:game.homeTeam.City}} {{:game.homeTeam.Name}} </div> <div> {{if isUnplayed == "true" }} Game starts at {{:game.time}} {{else isCompleted == "false"}} <div>Current Score: {{:awayScore}} - {{:homeScore}}</div> <div> {{if currentIntermission}} {{:~ordinal_suffix_of(currentIntermission)}} Intermission {{else currentPeriod}} {{:~ordinal_suffix_of(currentPeriod)}}<br/> {{:~time_left(currentPeriodSecondsRemaining)}} {{else}} 1st {{/if}} </div> {{else}} Final Score: {{:awayScore}} - {{:homeScore}} {{/if}} </div> </div> </script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jsrender/0.9.90/jsrender.min.js"></script> <script src="/socket.io/socket.io.js"></script> <script> var socket = io(); var helpers = { ordinal_suffix_of: function(i) { var j = i % 10, k = i % 100; if (j == 1 && k != 11) { return i + "st"; } if (j == 2 && k != 12) { return i + "nd"; } if (j == 3 && k != 13) { return i + "rd"; } return i + "th"; }, time_left: function(time) { var minutes = Math.floor(time / 60); var seconds = time - minutes * 60; return minutes + ':' + ('0' + seconds).slice(-2); } }; var tmpl = $.templates("#gameTemplate"); socket.on('data', function (data) { console.log(data); $('#data').html(tmpl.render(data.scoreboard.gameScore, helpers)); }); </script> <style> .game { border: 1px solid #000; float: left; margin: 1%; padding: 1em; width: 25%; } </style> </body> </html>
حان الوقت لإطلاق تطبيق Node وفتح
localhost : 3000 من أجل رؤية النتيجة!
في كل دقيقة X ، يرسل الخادم حدثًا إلى العميل. سيقوم العميل بدوره بإعادة رسم عناصر اللعبة بالبيانات المحدثة. لذلك ، عندما تترك الموقع مفتوحًا ، سيتم تحديث نتائج الألعاب باستمرار.
الخلاصة
يستخدم المنتج النهائي Socket.io لإنشاء خادم يتصل به العملاء. يقوم الخادم باسترداد البيانات وإرسالها إلى العميل. عندما يتلقى العميل البيانات ، يمكنه تحديث النتائج تدريجيًا. هذا يقلل من الحمل على الخادم لأن العميل يعمل فقط عندما يتلقى حدثًا من الخادم.
يمكن للخادم إرسال رسائل إلى العميل ، والعميل بدوره إلى الخادم. عندما يتلقى الخادم الرسالة ، فإنه يقوم بمعالجة البيانات.
تعمل تطبيقات الدردشة بنفس الطريقة تقريبًا. سيتلقى الخادم رسالة من العميل ، ثم يرسل جميع البيانات إلى العملاء المتصلين للإشارة إلى أن شخصًا ما قد أرسل رسالة جديدة.
آمل أن تكون قد استمتعت بهذا المقال ، لأنه عندما قمت بتطوير هذا التطبيق الرياضي في الوقت الفعلي ، فقد حصلت للتو على مجموعة من المتعة التي أردت مشاركتها مع علمي. بعد كل شيء ، الهوكي هي واحدة من الألعاب الرياضية المفضلة!
