WebSockets والتطبيقات الفورية
إعداد عميل Socket.io
إعداد عميل Socket.io
تسمح مكتبة عميل Socket.io لمتصفحات الويب وتطبيقات Node.js بالاتصال بخوادم Socket.io. تتعامل مع إدارة الاتصال، منطق إعادة الاتصال، وتوفر واجهة برمجة تطبيقات بديهية للاتصال في الوقت الفعلي.
تثبيت مكتبة العميل
هناك طرق متعددة لتضمين عميل Socket.io في مشروعك:
<!-- الخيار 1: CDN (الأسهل للاختبار) -->
<script src="https://cdn.socket.io/4.6.0/socket.io.min.js"></script>
<!-- الخيار 2: NPM (موصى به للإنتاج) -->
npm install socket.io-client
<!-- الخيار 3: يقدمه خادم Socket.io -->
<script src="/socket.io/socket.io.js"></script>
ملاحظة: عند استخدام أداة تجميع (Vite، Webpack، إلخ)، قم دائمًا بالتثبيت عبر npm. طريقة CDN مناسبة فقط للنماذج الأولية السريعة والتعلم.
الاتصال الأساسي للعميل
إليك كيفية إنشاء اتصال من المتصفح:
<!-- HTML -->
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<title>عميل Socket.io</title>
</head>
<body>
<h1>عميل Socket.io</h1>
<div id="status">غير متصل</div>
<div id="messages"></div>
<script src="https://cdn.socket.io/4.6.0/socket.io.min.js"></script>
<script>
// الاتصال بالخادم
const socket = io('http://localhost:3000');
// الاتصال ناجح
socket.on('connect', () => {
console.log('متصل بمعرف:', socket.id);
document.getElementById('status').textContent = 'متصل';
});
// فشل الاتصال
socket.on('connect_error', (error) => {
console.error('خطأ في الاتصال:', error);
document.getElementById('status').textContent = 'خطأ';
});
// قطع الاتصال
socket.on('disconnect', (reason) => {
console.log('قطع الاتصال:', reason);
document.getElementById('status').textContent = 'غير متصل';
});
</script>
</body>
</html>
استخدام عميل Socket.io مع JavaScript الحديث
في مشروع Vite أو React مع تثبيت npm:
// main.js أو app.js
import { io } from 'socket.io-client';
// الاتصال بالخادم
const socket = io('http://localhost:3000', {
autoConnect: true, // الاتصال تلقائيًا (افتراضي)
reconnection: true, // تمكين إعادة الاتصال (افتراضي)
reconnectionDelay: 1000, // الانتظار ثانية واحدة قبل إعادة الاتصال
reconnectionAttempts: 5 // المحاولة 5 مرات
});
// أحداث الاتصال
socket.on('connect', () => {
console.log('متصل! معرف السوكيت:', socket.id);
});
socket.on('disconnect', (reason) => {
console.log('قطع الاتصال بسبب:', reason);
if (reason === 'io server disconnect') {
// الخادم قطع الاتصال بالقوة، أعد الاتصال يدويًا
socket.connect();
}
});
socket.on('connect_error', (error) => {
console.error('فشل الاتصال:', error.message);
});
خيارات الاتصال
يقبل العميل العديد من الخيارات لتخصيص السلوك:
const socket = io('http://localhost:3000', {
// خيارات الاتصال
autoConnect: true, // الاتصال فورًا
reconnection: true, // تمكين إعادة الاتصال التلقائي
reconnectionDelay: 1000, // التأخير الأولي (ميلي ثانية)
reconnectionDelayMax: 5000, // الحد الأقصى للتأخير (ميلي ثانية)
reconnectionAttempts: Infinity, // عدد المحاولات
// خيارات النقل
transports: ['websocket', 'polling'], // وسائل النقل المفضلة
upgrade: true, // الترقية من polling إلى WebSocket
// المصادقة
auth: {
token: 'your-auth-token',
userId: '12345'
},
// معاملات الاستعلام (ترسل في URL)
query: {
room: 'general',
username: 'أحمد'
},
// إعدادات المهلة
timeout: 20000, // مهلة الاتصال (ميلي ثانية)
// المسار (إذا كان الخادم يستخدم مسار مخصص)
path: '/socket.io/'
});
نصيحة: استخدم
auth للبيانات الحساسة مثل الرموز (ترسل في المصافحة)، و query للبيانات غير الحساسة مثل أسماء الغرف (مرئية في URL).
إرسال الأحداث من العميل
أرسل أحداث مخصصة للخادم باستخدام emit():
// حدث بسيط بسلسلة نصية
socket.emit('greeting', 'مرحبًا أيها الخادم!');
// حدث بكائن
socket.emit('chatMessage', {
room: 'general',
message: 'مرحبًا بالجميع!',
timestamp: Date.now()
});
// حدث بمعاملات متعددة
socket.emit('userAction', 'click', 'button-1', { x: 100, y: 200 });
// حدث مع رد استدعاء (إقرار)
socket.emit('saveData', { name: 'أحمد' }, (response) => {
if (response.success) {
console.log('تم حفظ البيانات!');
} else {
console.error('خطأ:', response.error);
}
});
الاستماع للأحداث من الخادم
استقبل الأحداث من الخادم باستخدام on():
// الاستماع لرسالة الترحيب
socket.on('welcome', (message) => {
console.log('الخادم يقول:', message);
alert(message);
});
// الاستماع لرسائل الدردشة
socket.on('chatMessage', (data) => {
const messagesDiv = document.getElementById('messages');
messagesDiv.innerHTML += `
<div class="message">
<strong>${data.id}:</strong> ${data.message}
<span class="time">${new Date(data.timestamp).toLocaleTimeString()}</span>
</div>
`;
});
// الاستماع لتحديثات عدد المستخدمين
socket.on('userCount', (count) => {
document.getElementById('user-count').textContent = count;
});
// الاستماع لمؤشر الكتابة
socket.on('userTyping', (data) => {
console.log(`المستخدم ${data.userId} ${data.isTyping ? 'يكتب' : 'توقف عن الكتابة'}`);
});
التعامل مع قطع الاتصال
تعامل بشكل صحيح مع سيناريوهات قطع الاتصال:
socket.on('disconnect', (reason) => {
console.log('قطع الاتصال:', reason);
switch(reason) {
case 'io server disconnect':
// الخادم قطع اتصال هذا السوكيت بالقوة
// تحتاج إلى إعادة الاتصال يدويًا
console.log('الخادم أخرجنا. إعادة الاتصال...');
socket.connect();
break;
case 'io client disconnect':
// نحن قطعنا الاتصال يدويًا
console.log('قطعنا الاتصال بأنفسنا');
break;
case 'ping timeout':
// الخادم لم يستجب لـ ping في الوقت المحدد
console.log('انتهت مهلة الاتصال. سيعيد الاتصال تلقائيًا');
break;
case 'transport close':
// فقدان الاتصال (مشكلة في الشبكة)
console.log('فقدان الاتصال. سيعيد الاتصال تلقائيًا');
break;
case 'transport error':
// خطأ في النقل (مثل CORS)
console.log('خطأ في النقل. سيعيد الاتصال تلقائيًا');
break;
}
});
// تتبع محاولات إعادة الاتصال
socket.io.on('reconnect_attempt', (attemptNumber) => {
console.log(`محاولة إعادة الاتصال ${attemptNumber}`);
});
socket.io.on('reconnect', (attemptNumber) => {
console.log(`أعيد الاتصال بعد ${attemptNumber} محاولات`);
});
socket.io.on('reconnect_failed', () => {
console.log('فشل إعادة الاتصال بعد كل المحاولات');
alert('تعذر الاتصال بالخادم. يرجى تحديث الصفحة.');
});
معاملات استعلام الاتصال
مرر البيانات أثناء مصافحة الاتصال:
// العميل يرسل معاملات الاستعلام
const socket = io('http://localhost:3000', {
query: {
username: 'أحمد',
room: 'عام',
token: 'abc123'
}
});
// الخادم يستقبل معاملات الاستعلام
io.on('connection', (socket) => {
console.log('الاستعلام:', socket.handshake.query);
// { username: 'أحمد', room: 'عام', token: 'abc123' }
const username = socket.handshake.query.username;
const room = socket.handshake.query.room;
// الانضمام التلقائي للغرفة بناءً على الاستعلام
socket.join(room);
// إرسال ترحيب شخصي
socket.emit('welcome', `مرحبًا ${username} في ${room}!`);
});
تحذير: معاملات الاستعلام مرئية في URL وسجلات الشبكة. لا تستخدمها لرموز المصادقة الحساسة. استخدم خيار
auth بدلاً من ذلك.
التحكم اليدوي في الاتصال
الاتصال وقطع الاتصال برمجيًا:
// إنشاء سوكيت بدون اتصال تلقائي
const socket = io('http://localhost:3000', {
autoConnect: false
});
// الاتصال يدويًا عند الحاجة
document.getElementById('connect-btn').addEventListener('click', () => {
socket.connect();
});
// قطع الاتصال يدويًا
document.getElementById('disconnect-btn').addEventListener('click', () => {
socket.disconnect();
});
// التحقق من حالة الاتصال
if (socket.connected) {
console.log('متصل بالفعل');
} else {
console.log('غير متصل');
}
مثال كامل للعميل
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<title>عميل الدردشة</title>
<style>
#status { padding: 10px; margin: 10px 0; border-radius: 5px; }
.connected { background: #d4edda; color: #155724; }
.disconnected { background: #f8d7da; color: #721c24; }
#messages { height: 300px; overflow-y: auto; border: 1px solid #ccc; padding: 10px; }
.message { margin: 5px 0; padding: 5px; background: #f0f0f0; border-radius: 3px; }
</style>
</head>
<body>
<h1>تطبيق الدردشة</h1>
<div id="status" class="disconnected">جاري الاتصال...</div>
<div>المستخدمون المتصلون: <span id="user-count">0</span></div>
<div id="messages"></div>
<input type="text" id="message-input" placeholder="اكتب رسالة...">
<button id="send-btn">إرسال</button>
<script src="https://cdn.socket.io/4.6.0/socket.io.min.js"></script>
<script>
const socket = io('http://localhost:3000');
const statusDiv = document.getElementById('status');
const messagesDiv = document.getElementById('messages');
const messageInput = document.getElementById('message-input');
const sendBtn = document.getElementById('send-btn');
// حالة الاتصال
socket.on('connect', () => {
statusDiv.textContent = `متصل (${socket.id})`;
statusDiv.className = 'connected';
});
socket.on('disconnect', () => {
statusDiv.textContent = 'غير متصل';
statusDiv.className = 'disconnected';
});
// تحديثات عدد المستخدمين
socket.on('userCount', (count) => {
document.getElementById('user-count').textContent = count;
});
// استقبال الرسائل
socket.on('message', (data) => {
messagesDiv.innerHTML += `
<div class="message">
<strong>${data.id}:</strong> ${data.message}
</div>
`;
messagesDiv.scrollTop = messagesDiv.scrollHeight;
});
// إرسال رسالة
sendBtn.addEventListener('click', () => {
const message = messageInput.value.trim();
if (message) {
socket.emit('message', message);
messageInput.value = '';
}
});
// الإرسال عند الضغط على Enter
messageInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
sendBtn.click();
}
});
</script>
</body>
</html>
تمرين: أنشئ عميل Socket.io يقوم بـ:
- الاتصال بالخادم الخاص بك مع اسم مستخدم مخصص يمرر عبر معاملات الاستعلام
- عرض حالة الاتصال مع مؤشرات بصرية (أخضر/أحمر)
- إظهار عدد المستخدمين المتصلين في الوقت الفعلي
- إرسال رسائل الدردشة إلى الخادم
- استقبال وعرض الرسائل من المستخدمين الآخرين
- التعامل مع إعادة الاتصال بشكل سلس مع ملاحظات المستخدم
- تضمين زر لقطع الاتصال يدويًا