WebSockets والتطبيقات الفورية

معالجة الأخطاء وإعادة الاتصال

20 دقيقة الدرس 12 من 35

معالجة الأخطاء وإعادة الاتصال

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

أنواع أخطاء الاتصال

يمكن أن تفشل اتصالات WebSocket لأسباب مختلفة:

  • فشل الشبكة: فقدان الاتصال بالإنترنت، مشاكل DNS، حظر جدار الحماية
  • أخطاء الخادم: تعطل الخادم أو إعادة التشغيل أو الحمل الزائد
  • فشل المصادقة: رموز غير صالحة أو منتهية الصلاحية
  • أخطاء النقل: فشل ترقية WebSocket، أخطاء HTTP polling
  • أخطاء المهلة: مهلة ping، مهلة الاتصال

معالجة الأخطاء من جانب العميل

توفر Socket.io عدة أحداث لمعالجة أخطاء الاتصال من جانب العميل:

<script> const socket = io('http://localhost:3000'); // خطأ الاتصال (قبل إنشاء الاتصال) socket.on('connect_error', (error) => { console.error('خطأ في الاتصال:', error.message); if (error.message === 'Invalid token') { // إعادة التوجيه لتسجيل الدخول window.location.href = '/login'; } else if (error.message === 'timeout') { showNotification('انتهت مهلة الاتصال. إعادة المحاولة...', 'warning'); } }); // حدث خطأ عام socket.on('error', (error) => { console.error('خطأ في المقبس:', error); showNotification('حدث خطأ: ' + error.message, 'error'); }); // فقدان الاتصال بعد إنشائه socket.on('disconnect', (reason) => { console.log('قطع الاتصال:', reason); if (reason === 'io server disconnect') { // الخادم قطع الاتصال بالقوة، يلزم إعادة الاتصال يدويًا showNotification('تم قطع اتصالك من قبل الخادم', 'error'); socket.connect(); } else if (reason === 'io client disconnect') { // قطع الاتصال يدويًا، لا تعيد الاتصال showNotification('تم قطع الاتصال', 'info'); } else { // ستتم محاولة إعادة الاتصال التلقائي showNotification('فقد الاتصال. إعادة الاتصال...', 'warning'); } }); // محاولة إعادة الاتصال socket.io.on('reconnect_attempt', (attemptNumber) => { console.log(`محاولة إعادة الاتصال #${attemptNumber}`); updateStatusBadge('reconnecting', attemptNumber); }); // فشل إعادة الاتصال socket.io.on('reconnect_error', (error) => { console.error('خطأ في إعادة الاتصال:', error); }); // تمت إعادة الاتصال بنجاح socket.on('connect', () => { console.log('متصل/أعيد الاتصال'); showNotification('متصل!', 'success'); updateStatusBadge('connected'); }); </script>
ملاحظة: يوفر حدث disconnect معامل reason يساعدك في تحديد ما إذا كان يجب أن تكون إعادة الاتصال تلقائية أو يدوية.

معالجة الأخطاء من جانب الخادم

تعامل مع الأخطاء بشكل سلس على الخادم لمنع التعطل وتوفير ملاحظات ذات معنى:

// معالجة الأخطاء جانب الخادم io.on('connection', (socket) => { console.log('العميل متصل:', socket.id); // معالجة الأخطاء في معالجات الأحداث socket.on('sendMessage', async (data, callback) => { try { // التحقق من صحة البيانات if (!data.message || typeof data.message !== 'string') { throw new Error('تنسيق رسالة غير صالح'); } if (data.message.length > 1000) { throw new Error('الرسالة طويلة جدًا (بحد أقصى 1000 حرف)'); } // معالجة الرسالة const result = await saveMessageToDatabase(data); // إرسال استجابة النجاح callback({ success: true, messageId: result.id }); } catch (error) { console.error('خطأ في sendMessage:', error); // إرسال خطأ للعميل callback({ success: false, error: error.message }); // اختياريًا بث حدث خطأ socket.emit('messageError', { message: error.message, timestamp: Date.now() }); } }); // معالج خطأ عام لهذا المقبس socket.on('error', (error) => { console.error('خطأ في المقبس:', socket.id, error); }); socket.on('disconnect', (reason) => { console.log('قطع اتصال العميل:', socket.id, reason); }); }); // معالج خطأ غير مُلتقط عام io.engine.on('connection_error', (error) => { console.error('خطأ في الاتصال:', error); });

تكوين إعادة الاتصال التلقائي

تحاول Socket.io تلقائيًا إعادة الاتصال عند فقدان الاتصال. يمكنك تكوين سلوك إعادة الاتصال:

<script> const socket = io('http://localhost:3000', { reconnection: true, // تمكين إعادة الاتصال التلقائي reconnectionAttempts: 5, // الحد الأقصى لمحاولات إعادة الاتصال reconnectionDelay: 1000, // التأخير الأولي (ثانية واحدة) reconnectionDelayMax: 5000, // الحد الأقصى للتأخير (5 ثوانٍ) randomizationFactor: 0.5, // عشوائية التأخير (±50%) timeout: 20000, // مهلة الاتصال (20 ثانية) }); // مراقبة تقدم إعادة الاتصال let reconnectCount = 0; socket.io.on('reconnect_attempt', () => { reconnectCount++; updateUI({ status: 'reconnecting', attempt: reconnectCount, maxAttempts: 5 }); }); socket.io.on('reconnect_failed', () => { updateUI({ status: 'failed', message: 'فشل إعادة الاتصال بعد 5 محاولات' }); // عرض زر إعادة الاتصال يدويًا showReconnectButton(); }); socket.on('connect', () => { reconnectCount = 0; updateUI({ status: 'connected', message: 'تم الاتصال بنجاح' }); }); </script>
نصيحة: استخدم التراجع الأسي (زيادة التأخير) لتجنب إرهاق الخادم بمحاولات إعادة الاتصال. تفعل Socket.io هذا تلقائيًا.

منطق إعادة الاتصال المخصص

نفذ استراتيجيات إعادة اتصال مخصصة لسيناريوهات محددة:

<script> class SmartReconnection { constructor(socket) { this.socket = socket; this.maxAttempts = 10; this.attemptCount = 0; this.baseDelay = 1000; this.isReconnecting = false; this.setupListeners(); } setupListeners() { this.socket.on('disconnect', (reason) => { if (reason === 'io server disconnect') { // الخادم طردنا، انتظر أطول قبل إعادة الاتصال this.reconnectWithDelay(5000); } else { // مشكلة شبكة، أعد الاتصال بسرعة this.reconnectWithDelay(1000); } }); this.socket.on('connect', () => { this.attemptCount = 0; this.isReconnecting = false; console.log('تمت إعادة الاتصال بنجاح'); }); } reconnectWithDelay(delay) { if (this.isReconnecting || this.attemptCount >= this.maxAttempts) { return; } this.isReconnecting = true; this.attemptCount++; // التراجع الأسي const waitTime = Math.min( delay * Math.pow(2, this.attemptCount - 1), 30000 // بحد أقصى 30 ثانية ); console.log(`إعادة الاتصال في ${waitTime}ms (محاولة ${this.attemptCount}/${this.maxAttempts})`); setTimeout(() => { if (!this.socket.connected) { this.socket.connect(); this.isReconnecting = false; } }, waitTime); } manualReconnect() { this.attemptCount = 0; this.socket.connect(); } } const socket = io('http://localhost:3000', { autoConnect: false, reconnection: false // تعطيل إعادة الاتصال التلقائي }); const reconnection = new SmartReconnection(socket); socket.connect(); </script>

إدارة حالة الاتصال

تتبع وعرض حالة الاتصال لإبقاء المستخدمين على اطلاع:

<script> class ConnectionStatus { constructor(socket) { this.socket = socket; this.state = 'disconnected'; this.statusElement = document.getElementById('connection-status'); this.setupListeners(); this.updateUI(); } setupListeners() { this.socket.on('connect', () => { this.setState('connected'); }); this.socket.on('disconnect', () => { this.setState('disconnected'); }); this.socket.io.on('reconnect_attempt', (attempt) => { this.setState('reconnecting', attempt); }); this.socket.io.on('reconnect_failed', () => { this.setState('failed'); }); this.socket.on('connect_error', (error) => { this.setState('error', null, error.message); }); } setState(state, attempt = null, message = null) { this.state = state; this.attempt = attempt; this.message = message; this.updateUI(); } updateUI() { const statusConfig = { connected: { color: '#10b981', text: 'متصل', icon: '●' }, disconnected: { color: '#ef4444', text: 'غير متصل', icon: '●' }, reconnecting: { color: '#f59e0b', text: `إعادة الاتصال (${this.attempt})`, icon: '○' }, failed: { color: '#ef4444', text: 'فشل الاتصال', icon: '×' }, error: { color: '#ef4444', text: this.message || 'خطأ', icon: '!', } }; const config = statusConfig[this.state]; this.statusElement.style.color = config.color; this.statusElement.textContent = `${config.icon} ${config.text}`; } } const socket = io('http://localhost:3000'); const status = new ConnectionStatus(socket); </script>

التعامل مع فقدان الشبكة المؤقت

نفذ استراتيجيات للتعامل مع الانقطاعات المؤقتة بشكل سلس:

<script> class OfflineQueue { constructor(socket) { this.socket = socket; this.queue = []; this.isOnline = socket.connected; this.socket.on('connect', () => { this.isOnline = true; this.processQueue(); }); this.socket.on('disconnect', () => { this.isOnline = false; }); } emit(event, data, callback) { if (this.isOnline) { this.socket.emit(event, data, callback); } else { // قم بوضع الحدث في قائمة الانتظار لاحقًا this.queue.push({ event, data, callback }); console.log(`حدث في قائمة الانتظار: ${event} (${this.queue.length} في القائمة)`); } } processQueue() { console.log(`معالجة ${this.queue.length} أحداث في قائمة الانتظار`); while (this.queue.length > 0) { const { event, data, callback } = this.queue.shift(); this.socket.emit(event, data, callback); } } } const socket = io('http://localhost:3000'); const queue = new OfflineQueue(socket); // استخدم queue بدلاً من socket مباشرة queue.emit('sendMessage', { message: 'مرحبا' }, (response) => { console.log('تم إرسال الرسالة:', response); }); </script>
تحذير: كن حذرًا مع وضع العمليات الحساسة في قائمة الانتظار مثل المدفوعات أو حذف البيانات. يجب أن تفشل بعض العمليات فورًا عند عدم الاتصال بدلاً من وضعها في قائمة الانتظار.

نبضات القلب ومراقبة Ping

راقب صحة الاتصال باستخدام آليات ping/pong:

<script> const socket = io('http://localhost:3000', { pingInterval: 10000, // أرسل ping كل 10 ثوانٍ pingTimeout: 5000 // انتظر 5 ثوانٍ للحصول على pong }); let lastPing = Date.now(); socket.io.on('ping', () => { lastPing = Date.now(); }); socket.io.on('pong', (latency) => { console.log(`زمن الاستجابة: ${latency}ms`); updateLatencyDisplay(latency); // تنبيه إذا كان زمن الاستجابة مرتفعًا جدًا if (latency > 1000) { showWarning('تم اكتشاف زمن استجابة مرتفع. قد يكون الاتصال غير مستقر.'); } }); // التحقق من الاتصال القديم setInterval(() => { const timeSinceLastPing = Date.now() - lastPing; if (timeSinceLastPing > 30000) { console.warn('لم يتم استلام ping لمدة 30 ثانية. قد يكون الاتصال ميتًا.'); socket.disconnect(); socket.connect(); } }, 5000); </script>
تمرين: أنشئ تطبيق دردشة مرن:
  1. عرض حالة الاتصال الحالية (متصل/غير متصل/إعادة الاتصال)
  2. عرض عدد محاولات إعادة الاتصال والتقدم
  3. وضع الرسائل المرسلة أثناء عدم الاتصال في قائمة انتظار وإرسالها عند إعادة الاتصال
  4. عرض زمن استجابة الشبكة في الوقت الفعلي
  5. توفير زر يدوي "إعادة الاتصال" عند فشل إعادة الاتصال التلقائي
  6. إخطار المستخدمين عند تدهور جودة الاتصال (زمن استجابة مرتفع)

اختبر بإيقاف الخادم وإرسال الرسائل وإعادة تشغيل الخادم للتحقق من إرسال الرسائل في قائمة الانتظار.