مكتبة Socket.io

socket.io هي من أكثر المكتبات شعبية في بيئة Node.js، نظرا لكونها سهلت كثيرا على مطوري الويب بناء تطبيقات تزامنية بين الخادم والعميل بالإعتماد بشكل خاص على بروتوكول Websocket. وعندما نقول تطبيقات تزامنية أو آنية (Real Time) فإننا نقصد بها كل تطبيق يحتاج فيه العملاء (Clients) للحصول على أحدث البيانات من الخادم (Server) من دون إعادة تحميل الصفحة أو الضغط على أي زر في التطبيق.

ما هي آلية عمل WebSockets ؟

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

هذه العملية ثنائية الإتجاه (Bi-directional)، أي أن الخادم كذلك قادر على التنصت على العميل وانتظار معلومات جديدة منه ليقوم بإرسالها للعملاء الآخرين. هذا يحدث على سبيل المثال في تطبيقات الدردشة أو الشات، حيث تقوم بكتابة رسالتك ومباشرة عند الضغط على زر الإرسال تظهر في الطرف الآخر عند صديقك.

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

بنية HTTP التقليدية

في بنية HTTP التقليدية، العميل (المتصفح) دائما يطلب بشكل صريح (Explicitly) والخادم يستجيب، هكذا كانت دائما تطبيقات الويب تعمل حتى ظهرت واجهة Websocket API الخاصة بجافاسكربت بالتزامن مع بروز إصدار HTML5 الجديد قبل سنوات.

Http Communication Between Client and Server

في البداية كان هذا كافيا، ولكن مع التطور الحاصل في مجال تطبيقات الويب تبينت محدودية هذه البنية وعدم استجابتها لتطلعات مطوري الويب وأصحاب المشاريع الريادية على الإنترنت. السبب كما قلنا، أنه في هذه الحالة لا يستطيع الخادم من تلقاء نفسه أن يرسل شيئا إلى العميل المتصل، فهو دائما ينتظر طلبا (Http Request) من الأخير لكي يقدم جوابا له (Http Response).

تقنية WebSocket الجديدة تمكن من فتح اتصال أو قناة مفتوحة بين الخادم والعميل طيلة مدة الجلسة، ومن خلالها يستطيع الطرفان تبادل الرسائل في الإتجاهين، يعني حتى الخادم يستطيع إرسال ما يريد من بيانات إلى العميل من دون أن يطلب منه الأخير ذلك بشكل صريح، مع إمكانيات معالجة وعرض تلك البيانات في الزمن الحقيقي (Real time) بدون إعادة تحميل الصفحة.

لا يجب الخلط بين WebSockets وأجاكس

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

علاقة مكتبة Socket.io ب WebSockets

تتيح لنا مكتبة socket.io الإستعانة بتقنيات Websocket بسهولة عن طريق توفير واجهة برمجية واضحة وبسيطة، كما أنها تستعين بوسائل وتقنيات أخرى من أجل ضمان توافقية كاملة مع المتصفحات القديمة التي لا تدعم ال WebSocket.

فعلى سبيل المثال، إذا كان المتصفح لا يدعم WebSocket وفي نفس الوقت Flash مثبت، تقوم socket.io بالإستعانة ب Adobe Flash Socket للقيام بالإتصال. وفي الحالات الأخرى تستخدم تقنيات مختلفة مثل Ajax Long Polling أو Forever Iframe.

كل هذه التقنيات تلجأ إليها المكتبة لإستهداف أكبر قد ممكن من الأجهزة والمتصفحات، وليس علينا كمطورين فهمها لأنه في النهاية نحن نتعامل مع واجهة socket.io الموحدة 🙂

الجميل كذلك مع هذه المكتبة أنها تستهدف كذلك ناحية الخادم (Node.js) إلى جانب المتصفحات، وبالتالي لن نحتاج لأي مكتبة أخرى لإدارة العمليات جهة الخادم. وهنا تجدر الإشارة إلى أنه لا يمكن استخدام مكتبة socket.io جهة الخادم إلا مع مكتبة socket.io نفسها جهة العميل، المكتبات الأخرى المعروفة التي بنيت حول WebSockets (مثلا مكتبة ws) غير متوافقة مع socket.io إلا إذا تمت الإشارة لعكس ذلك.

لنمر إلى التطبيق

الآن وبعد أن تعرفنا على دور وفائدة مكتبة socket.io، سيكون من الجيد إذا مررنا للتطبيق العملي حتى نستوعب جيدا كل الأمور النظرية التي قلناها.

أولا، لنقم بإنشاء مجلد جديد نسميه socketio-example وندخل إليه من خلال نافذة الأوامر السطرية.

إليكم الطريقة المعتادة لفعل ذلك:

بعد ذلك نقوم بتهيئة مشروع Node.js جديد داخل المجلد عن طريق الأمر التالي:

هذا سيؤدي لإنشاء ملف package.json الإعتيادي. سنستخدم هذا الملف لاحقا لإضافة أوامر تمكننا من تشغيل خادم Node.js.

الخطوة التالية هي خطوة اختيارية ولكنها محبدة، وهي تحميل الأداة nodemon التي تمكننا من تشغيل خادم Node.js وإعادة تشغيله (Restart) بعد كل تغيير على مستوى ملفات المشروع (Watching changes)، لكي لا نظطر لفعل ذلك يدويا.

الآن حان الوقت لتحميل مكتبة socket.io، ودائما عن طريق مدير الحزم npm 🙂

بعد تحميل المكتبة، سننشئ ملفين اثنين :

  • index.js: الملف الذي تتم على مستواه إدارة الإتصال والعمليات التي تتم على مستوى الخادم (Server).
  • index.html: هذا الملف يمثل العميل (Client) الذي يتصل بالخادم ويتولى عرض النتائج للمستخدم.

ملف الخادم (index.js)

محتوى الملف index.js سيكون بسيطا، لاحظوا :

هنا قمنا بشيئين :

  • أولا أنشأنا خادم http وعن طريقه نقوم بإرسال الصفحة index.html إلى العميل.
  • ثانيا أنشأنا متغيرا اسمه io انطلاقا من الوحدة socket.io التي قمنا بتثبيتها سابقا، وعن طريق هذا المتغير يمكننا التنصت على الحدث connection الذي يتم إطلاقه من طرف خادم Socket.io عند اتصال أي عميل (Client) به. في حالتنا طلبنا من الخادم أن يقوم بعرض رسالة “A client is connected” عند كل اتصال جديد.

إذن في الأخير سيكون هناك اتصالين اثنين :

  • اتصال http يفتح لنا صفحة index.html على المتصفح.
  • من داخل صفحة index.html سنقوم بإنشاء قناة اتصال متزامن نحو الخادم عن طريق مكتبة socket.io جهة العميل، فكما قلت في السابق هذه المكتبة تعمل في كلتا الناحيتين (Server و Client).

ملف العميل (index.html)

تلاحظون أن هذا الملف غاية في البساطة كذلك، فقط قمنا باستدعاء مكتبة العميل الخاصة ب Socket.io من خلال المسار socket.io/socket.io.js/ الذي توفره لنا الوحدة socket.io التي استخدمناها في ملف الخادم.

بعد استدعاء مكتبة العميل، سنقوم بفتح قناة الإتصال مع الخادم من خلال الكائن io الذي توفره مكتبة socket.io.jsوبطبيعة الحال في المشروع الحقيقي يجب تعويض localhost باسم النطاق الخاص بموقعك.

الآن لم يتبق لنا سوى إطلاق الخادم من خلال nodemon، وأفضل طريقة للقيام بذلك هي عن طريق أوامر npm.

يمكننا إضافة هذه الأوامر كما رأينا في دروس سابقة في ملف package.json بهذه الطريقة :

نستطيع الآن تشغيل الخادم عن طريق تنفيذ الأمر npm run dev (بحيث dev هو اسم الأمر الذي اخترناه أعلاه في ملف package.json).

بعد تشغيل الخادم، ومع دخولنا للرابط http://localhost:8080 على المتصفح سنرى بأن الرسالة “A client is connected” قد طبعت في نافذة الأوامر السطرية.

هذا يعني بأن اتصال Socket.io قد تم بنجاح بين الخادم والعميل 🙂 وكلما فتحنا نافذة جديدة في المتصفح ودخلنا للرابط مرة أخرى سنلاحظ بأن الرسالة تطبع من جديد لأن هناك عميلا جديدا ربط اتصالا جديدا مع الخادم.

الآن بعد أن عرفنا كيف نربط اتصالا بين الخادم والمتصفح، سنتقدم قليلا نحو الأمام لنرى كيف يمكن للخادم أن يخاطب العميل، وكيف للأخير أن يخاطب الأول كذلك.

إرسال واستقبال الرسائل بين الخادم والعميل

1. الخادم يرسل رسالة للعميل

في المثال السابق، كنا طلبنا من الخادم أن يقوم بطباعة رسالة “A client is connected” عن طريق console.log، وهذا فقط للتحقق من أن الإتصال تم بنجاح، إذ أن المستخدم لا يرى تلك الرسالة على المتصفح. لهذا، سنقوم الآن بإرسال رسالة لهذا المستخدم لنخبره بأنه متصل، وسيتمكن العميل (المتصفح) من التقاط تلك الرسالة وعرضها على شكل alert.

الطريقة هي كالتالي :

هنا قمنا بإرسال رسالة للعميل عن طريق الوظيفة ()socket.emit التي تقبل معاملين اثنين:

  • اسم أو نوع الرسالة، هنا استعملنا connected ويمكن اختيار أي اسم نريد.
  • المعامل الثاني يمثل محتوى الرسالة ويمكن أن يكون نصا (string) كما في حالتنا أو كائن جافاسكريبت (Object) إذا أردنا إرسال أكثر من معلومة واحدة.

في ناحية العميل (Client) سنتنصت على قدوم الرسالة عن طريق الوظيفة ()socket.on :

تلاحظون أن الوظيفة ()on تقبل بارامترين: الأول هو نوع الرسالة (connected) والثاني هو دالة Callback تقبل بدورها بارامترا واحدا هو محتوى الرسالة القادمة من الخادم.

الآن عندما نعود لفتح الرابط http://localhost/8080 على متصفحنا سنجد بأنه يعرض الرسالة في نافذة alert.

2. العميل يرسل رسالة للخادم

لكي نقوم بإرسال رسالة من العميل نحو الخادم، سنقوم بإضافة زر لصفحة index.html وعند النقر عليه سنقوم بإرسال رسالة للخادم. الطريقة هي نفسها تقريبا.

العميل يقوم بإرسال المعلومات للخادم بدالة ()emit تحمل كما تلاحظون نفس اسم الدالة أو الوظيفة التي يستخدمها الخادم لنفس الغرض، لهذا قلت سابقا بأن الواجهة البرمجية التي توفرها Socket.io بسيطة وبديهية 🙂

هنا اخترنا الإسم message لتحديد نوع الرسالة.

الآن يجب علينا أن نطلب من الخادم أن يتنصت على الرسائل من هذا النوع، وذلك عن طريق الدالة ()socket.on:

بعد القيام بكل هذه الخطوات بشكل صحيح، وبعد أن ننقر على الزر الجديد في صفحتنا سنرى بأن الرسالة قد تمت طباعتها في نافذة الأوامر السطرية (Terminal) الخاصة بالخادم، ما يعني أن الأخير قد استقبل بشكل جيد رسالة ال Client.

الإتصال بمجوعة من العملاء

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

لنتخيل مثلا أنه لدينا غرفة دردشة يشارك فيها مجموعة من المستخدمين الذين يدردشون فيما بينهم. عندما يقوم مستخدم بإرسال رسالة نحو الخادم، من المفترض أن يقوم الخادم بتحليلها وإرسالها لباقي المستخدمين حتى يعمل نظام الشات بشكل جيد.

في حالة استخدمنا طريقة ()socket.emit فإن الخادم سيقوم بإرجاع الرسالة للمستخدم الذي قام بإرسالها فقط، وهذا غير منطقي، لأنه ليس هناك من فائدة في أن تدردش مع نفسك 🙂 🙂

يجب علينا أن نطلب من الخادم أن يقوم بإرسال تلك الرسالة لكل العملاء والمستخدمين الذين تربطهم في الوقت الحالي قناة socket.io مفتوحة مع الخادم.

الطريقة هي ()io.emit

الآن كلما فتحنا نافذة جديدة في المتصفح (http://localhost:8080) ستقوم الصفحات في النوافذ الأخرى بعرض الرسالة “Another client is connected to chat room” في نافذة alert (لأنه سبق أن طلبنا من العميل أن يتنصت على الرسائل من نوع connected)، وهذا بالضبط ما نريده.

في حالات أخرى، نكون في حاجة لإرسال البيانات من الخادم إلى جميع العملاء باستثناء العميل المرسِل، في تلك الحالة نقوم باستخدام ()socket.broadcast.emit عوضا عن ()io.emit.

الختام

هذه مقدمة بسيطة لمعرفة خصائص ومميزات مكتبة Socket.io.

إنشاء تطبيقات الزمن الحقيقي (Real-time Web Applications) أصبح أسهل وفي متناول جميع المطورين بمختلف مستوياتهم بفضل هذه المكتبة الرائعة.

الأمثلة التي أوردناها في هذا الدرس غاية في البساطة، وليست إلا إشارة يسيرة لما يمكننا فعله بواسطتها.

في أحد الدروس القادمة إن شاء الله، سأقوم بإنجاز مثال عملي أكثر تقدما، عبارة عن تطبيق للدردشة نقوم ببنائه بواسطة Socket.io وأحد أطر عمل جافاسكريبت (غالبا سيكون React.js).

إلى ذلك الحين لا تبخلوا علينا بتعليقاتكم الجميلة، وملاحظاتهم وكذا اقتراحاتكم للدروس المقبلة.

11 تعليقات

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

  2. بروتوكول Websocket وضع عام ٢٠١١..
    السؤال , كيف كانت تتم عمليات real time مثل ال notifications و chatting و غيرها قبل هذا التاريخ..
    هل كانوا يقومون بإرسال http request كل فترة فترة زمنة قصيرة؟
    وشكراً جزيلاً على المقال الرائع كالعادة دائماً.

    • كما ذكرت أخي الكريم، هناك طرق قديمة وتقليدية لمحاكاة عمل ال WebSockets مثل Ajax Long Polling و Adobe Flash Socket بالنسبة للمواقع التي كانت تعتمد على فلاش.
      هذا رابط للتعمق أكثر في موضوع Ajax Long Polling.

  3. جميل جدا
    مقال واضح
    بصراحة كنت قد قرأت عدة مقالات اجنبية عن الموضوع
    لكن لم افهم بشكل جيد وظيفة socket.on و socket.emit
    والان فهمت ان emit لأرسال رسالة سواء من جهة الخادم او العميل
    و on لاستقبال تلك الرسالة ايضا من الجهتين
    في انتظار المقال التالي وخاصة ان اشهر تطبيقات websocket هو الشات

  4. محظور ip الخاص بالسودان

    استلمت التنبيه على الايميل الخاص بالقائمة البريدية. بوجود التدوينة.
    فالرابط لايفتح .

    حاليا انا داخل عبرvpn من هولندا.

    • غريب أخي محمد،
      من فضلك، قم بإعطاء رابط المدونة لأحد أصدقائك في السودان لكي نتأكد 🙂
      شكرا لك على التنبيه.

        • عفوا أخي،
          أنت من السودان، صحيح ؟
          عدد من الإخوة السودانيين والسوريين نبهوني إلى أن الموقع لا يعمل عندهم ولكن لم أجد إلى الآن مصدر المشكل. سأتصل بالمستضيف لكي يشرح لي السبب.

ترك الرد

Please enter your comment!
Please enter your name here