غالبًا ما تخبر قصص الرعب عن تقنية websocket ، على سبيل المثال ، أنها غير مدعومة من متصفحات الويب ، أو أن مقدمي الخدمة / المشرفين يحجبون حركة مرور websocket - لذلك لا يمكن استخدامها في التطبيقات. من ناحية أخرى ، لا يتوقع المطورون دائمًا المآزق التي تمتلكها تقنية طبقة الويب ، مثل أي تقنية أخرى. بالنسبة للقيود المزعومة ، سأقول على الفور أن 96.8٪ من متصفحات الويب تدعم تقنية websocket اليوم. يمكنك القول أن 3.2 ٪ المتبقية في البحر هو الكثير ، هؤلاء هم الملايين من المستخدمين. أنا أتفق معك تمامًا. فقط كل شيء معروف في المقارنة. نفس XmlHttpRequest ، الذي يستخدمه الجميع في Ajax لسنوات عديدة ، يدعم 97.17 ٪ من متصفحات الويب (ليس أكثر بكثير ، أليس كذلك؟) ، وجلب بشكل عام ، 93.08 ٪ من متصفحات الويب. على عكس websocket ، فإن هذه النسبة المئوية (وقبل ذلك كانت أقل) لم توقف أي شخص لفترة طويلة عند استخدام تقنية Ajax. لذا فإن استخدام تراجع في الاقتراع الطويل ليس له أي معنى. إذا كان ذلك فقط لأن متصفحات الويب التي لا تدعم websocket هي نفس متصفحات الويب التي لا تدعم XmlHttpRequest ، وفي الواقع لن يحدث أي تراجع.
قصة الرعب الثانية ، حظر على websocket من موفري أو مدراء شبكات الشركات ، هي أيضا غير معقولة ، لأن الجميع يستخدم الآن بروتوكول https ، ومن المستحيل أن نفهم أن اتصال websocket مفتوح (دون كسر https).
بالنسبة للقيود الحقيقية وطرق التغلب عليها ، سأقول في هذا المنشور ، على سبيل المثال تطوير منطقة مسؤول الويب للتطبيق.
لذلك ، يحتوي كائن WebSocket في مستعرض الويب ، بصراحة ، على مجموعة موجزة للغاية من الطرق: send () و close () ، وكذلك addEventListener () و removeEventListener () و أساليب dispatchEvent () الموروثة من كائن EventTarget. لذلك ، يجب على المطور استخدام المكتبات (عادةً) أو بشكل مستقل (يكاد يكون من المستحيل) لحل العديد من المشكلات.
لنبدأ بالمهمة الأكثر مفهومة. تمت مقاطعة الاتصال بالخادم بشكل دوري. إعادة الاتصال سهلة بما فيه الكفاية. ولكن إذا كنت تتذكر أن الرسائل من كل من العميل والخادم تستمر في العمل في هذا الوقت ، فسيصبح كل شيء على الفور وأكثر تعقيدًا. بشكل عام ، يمكن فقد رسالة إذا لم يتم توفير آلية تأكيد للرسالة المستلمة ، أو أعيد تسليمها (حتى عدة مرات) إذا تم توفير آلية التأكيد ، ولكن حدث الفشل في اللحظة التي تعقب الاستلام وقبل تأكيد الرسالة.
إذا كنت بحاجة إلى تسليم مضمون للرسائل و / أو تسليم الرسائل بدون إرسال ، فهناك بروتوكولات خاصة لتنفيذ ذلك ، على سبيل المثال ، AMQP و MQTT ، والتي تعمل مع النقل عبر الويب. لكن اليوم لن نفكر فيها.
تدعم معظم مكتبات العمل مع websocket للمبرمج إعادة الاتصال بالخادم. يعد استخدام هذه المكتبة دائمًا أكثر موثوقية من تطوير تطبيقك.
بعد ذلك ، تحتاج إلى تنفيذ البنية التحتية لإرسال واستقبال الرسائل غير المتزامنة. للقيام بذلك ، استخدم معالج الأحداث onmessage "العاري" دون ربط إضافي ، وهي مهمة ناكر للجميل. يمكن أن تكون هذه البنية التحتية ، على سبيل المثال ، استدعاء الإجراء عن بعد (RPC). تم تقديم معرّف الهوية في مواصفات json-rpc ، خاصةً للعمل مع نقل websocket ، والذي يتيح لك تعيين مكالمة الإجراء البعيد من قبل العميل إلى رسالة الاستجابة من خادم الويب. أفضل هذا البروتوكول على كل الاحتمالات الأخرى ، لكن حتى الآن لم أجد التنفيذ الناجح لهذا البروتوكول لجزء الخادم على node.js.
وأخيرا ، تحتاج إلى تنفيذ التحجيم. تذكر أن الاتصال بين العميل والخادم يحدث بشكل دوري. إذا كانت قوة خادم واحد ليست كافية بالنسبة لنا ، فيمكننا رفع عدد آخر من الخوادم. في هذه الحالة ، بعد قطع الاتصال ، لا يمكن ضمان الاتصال بنفس الخادم. عادة ، يتم استخدام خادم redis أو مجموعة من خوادم redis لتنسيق خوادم websocket متعددة.
ولسوء الحظ ، عاجلاً أم آجلاً ، سنواجه أداء النظام على أي حال ، لأن قدرات node.js في عدد اتصالات طبقة الويب المفتوحة في وقت واحد (لا تخلط بين هذا والأداء) أقل بكثير من الخوادم المتخصصة مثل قوائم انتظار الرسائل والوسطاء. والحاجة إلى التبادل بين جميع مثيلات خوادم websocket من خلال مجموعة خوادم redis ، بعد بعض النقاط المهمة ، لن تعطي زيادة كبيرة في عدد الاتصالات المفتوحة. تتمثل طريقة حل هذه المشكلة في استخدام خوادم متخصصة ، مثل AMQP و MQTT ، والتي تعمل ، بما في ذلك مع النقل عبر الويب. لكن اليوم لن نفكر فيها.
كما ترون من قائمة المهام المدرجة ، فإن ركوب الدراجات أثناء العمل باستخدام websocket يستهلك الكثير من الوقت ، وحتى مستحيل إذا كنت بحاجة إلى توسيع نطاق الحل لعدة خوادم websocket.
لذلك ، أقترح النظر في العديد من المكتبات الشائعة التي تنفذ العمل باستخدام websocket.
سأستبعد على الفور من المكتبات تلك التي تطبق احتياطيًا حصريًا على وسائط النقل المتقادمة ، نظرًا لأن هذه الوظيفة ليست ذات صلة اليوم ، والمكتبات التي تقوم بتنفيذ وظائف أوسع ، كقاعدة عامة ، تقوم أيضًا بتنفيذ النسخ الاحتياطي على وسائط النقل القديمة.
سأبدأ مع مكتبة الأكثر شعبية - socket.io. الآن يمكنك سماع الرأي ، على الأرجح عادل ، أن هذه المكتبة بطيئة ومكلفة من حيث الموارد. على الأرجح ، وهو يعمل بشكل أبطأ من websocket الأصلي. ومع ذلك ، فهي اليوم المكتبة الأكثر تطوراً بوسائلها. ومرة أخرى ، عند العمل مع websocket ، فإن العامل المحدد ليس هو السرعة ، ولكن عدد الاتصالات المفتوحة في وقت واحد مع العملاء الفريدين. ويتم حل هذا السؤال بالفعل عن طريق إجراء اتصالات مع العملاء بخوادم متخصصة.
لذلك ، تنفذ soket.io استردادًا موثوقًا عند قطع الاتصال بالخادم والتحجيم باستخدام خادم أو مجموعة من خوادم redis. في الواقع ، ينفذ socket.io بروتوكول الرسائل الفردية الخاص به ، والذي يسمح لك بتنفيذ المراسلة بين العميل والخادم دون ربط لغة برمجة معينة.
ميزة مثيرة للاهتمام من socket.io هي تأكيد معالجة الأحداث ، حيث يمكن إرجاع كائن تعسفي من الخادم إلى العميل ، والذي يسمح بإجراء مكالمات من بعد (على الرغم من أنه لا يتوافق مع معيار json-rpc).
أيضًا ، أوليًا ، درست مكتبتين أكثر إثارة للاهتمام ، والتي سأناقشها بإيجاز أدناه.
مكتبة فاي
faye.jcoglan.com . وهي تنفذ بروتوكول بايو ، الذي تم تطويره في مشروع CometD وتنفذ الاشتراك / توزيع الرسائل على قنوات الرسائل. كما يدعم هذا المشروع التوسع باستخدام خادم أو مجموعة من خوادم redis. لم تنجح محاولة العثور على طريقة لتطبيق RPC لأنها لا تتناسب مع نظام بروتوكول بايو.
في مشروع socketcluster
socketcluster.io ، يتم التركيز على توسيع نطاق خادم websocket. في الوقت نفسه ، لا يتم إنشاء مجموعة خادم websocket على أساس خادم redis ، كما هو الحال في أول مكتبتين مذكورتين ، ولكن على أساس node.js. في هذا الصدد ، عند نشر المجموعة ، كان من الضروري إطلاق بنية تحتية معقدة إلى حد ما من السماسرة والعمال.
الآن دعنا ننتقل إلى تنفيذ RPC على socket.io. كما قلت أعلاه ، طبقت هذه المكتبة بالفعل القدرة على تبادل الأشياء بين العميل والخادم:
import io from 'socket.io-client'; const socket = io({ path: '/ws', transports: ['websocket'] }); const remoteCall = data => new Promise((resolve, reject) => { socket.emit('remote-call', data, (response) => { if (response.error) { reject(response); } else { resolve(response); } }); });
const server = require('http').createServer(); const io = require('socket.io')(server, { path: '/ws' }); io.on('connection', (socket) => { socket.on('remote-call', async (data, callback) => { handleRemoteCall(socket, data, callback); }); }); server.listen(5000, () => { console.log('dashboard backend listening on *:5000'); }); const handleRemoteCall = (socket, data, callback) => { const response =... callback(response) }
هذا هو المخطط العام. الآن سننظر في كل جزء من الأجزاء فيما يتعلق بتطبيق معين. لبناء لوحة المشرف ، استخدمت مكتبة رد الفعل
github.com/marmelab/react-admin . يتم تنفيذ تبادل البيانات مع الخادم في هذه المكتبة باستخدام موفر بيانات ، يحتوي على نظام مناسب جدًا ، وهو نوع من المعايير تقريبًا. على سبيل المثال ، للحصول على قائمة ، تسمى الطريقة:
dataProvider( 'GET_LIST', ' ', { pagination: { page: {int}, perPage: {int} }, sort: { field: {string}, order: {string} }, filter: { Object } }
هذه الطريقة في استجابة غير متزامنة بإرجاع كائن:
{ data: [ ], total: }
يوجد حاليًا عدد مثير للإعجاب من تطبيقات موفر بيانات رد فعل المشرف للعديد من الخوادم والأطر (مثل firebase ، والإقلاع النابض ، و graphql ، إلخ). في حالة RPC ، تبين أن التطبيق هو الأكثر إيجازًا ، حيث يتم نقل الكائن في شكله الأصلي إلى استدعاء دالة emit:
import io from 'socket.io-client'; const socket = io({ path: '/ws', transports: ['websocket'] }); export default (action, collection, payload = {}) => new Promise((resolve, reject) => { socket.emit('remote-call', {action, collection, payload}, (response) => { if (response.error) { reject(response); } else { resolve(response); } }); });
لسوء الحظ ، كان يجب القيام بالمزيد من العمل على جانب الخادم. لتنظيم تعيين الوظائف التي تتعامل مع المكالمة عن بُعد ، تم تطوير جهاز توجيه مشابه لـ express.js. فقط بدلاً من توقيع الوسيطة (req ، الدقة ، التالي) ، يعتمد التنفيذ على التوقيع (مقبس ، حمولة ، رد اتصال). نتيجة لذلك ، حصلنا جميعًا على الكود المعتاد:
const Router = require('./router'); const router = Router(); router.use('GET_LIST', (socket, payload, callback) => { const limit = Number(payload.pagination.perPage); const offset = (Number(payload.pagination.page) - 1) * limit return callback({data: users.slice(offset, offset + limit ), total: users.length}); }); router.use('GET_ONE', (socket, payload, callback) => { return callback({ data: users[payload.id]}); }); router.use('UPDATE', (socket, payload, callback) => { users[payload.id] = payload.data return callback({ data: users[payload.id] }); }); module.exports = router; const users = []; for (let i = 0; i < 10000; i++) { users.push({ id: i, name: `name of ${i}`}); }
يمكن العثور
على تفاصيل تنفيذ جهاز التوجيه
في مستودع المشروع.كل ما تبقى هو تعيين موفر لمكون المسؤول:
import React from 'react'; import { Admin, Resource, EditGuesser } from 'react-admin'; import UserList from './UserList'; import dataProvider from './wsProvider'; const App = () => <Admin dataProvider={dataProvider}> <Resource name="users" list={UserList} edit={EditGuesser} /> </Admin>; export default App;
روابط مفيدة
1.
www.infoq.com/articles/Web-Sockets-Proxy-Serversapapacy@gmail.com
14 يوليو 2019