0Pricing
Real-Time Streaming Systems (WebRTC + Live Data) · درس

تنفيذ منطق الإشارة

طوّر المنطق من جهة الخادم لمعالجة طلبات الاتصال وتبادل عروض وإجابات SDP ومرشحي ICE بين النظراء.

تنفيذ منطق الإشارة درس مجاني في Real-Time Streaming Systems (WebRTC + Live Data) على CoddyKit. هذا هو الدرس 2 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Real-Time Streaming Systems (WebRTC + Live Data)، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Real-Time Streaming Systems (WebRTC + Live Data) 4 دروس في المجموع.

بعض أجزاء هذا الدرس لم تُترجم بعد وتظهر باللغة الإنجليزية.

Signaling Logic: The WebRTC Matchmaker

Welcome to the heart of WebRTC connection setup! Before peers can talk directly, they need a way to exchange crucial setup information. This is where signaling logic comes in.

A signaling server acts as a temporary matchmaker, facilitating the initial handshake. It doesn't handle media streams directly, but it's vital for establishing the connection.

  • Discover Peers: Helps peers find each other.
  • Exchange Metadata: Passes Session Description Protocol (SDP) offers/answers.
  • Share Network Info: Relays ICE candidates (network addresses).

Setting Up Our Signaling Server

Our signaling server will use WebSockets for real-time, bidirectional communication. Here's how to set up a basic server using Node.js and the popular ws library.

This server will listen for incoming WebSocket connections on port 8080, forming the foundation for our signaling logic.

const WebSocket = require('ws');

// Create a WebSocket server instance
const wss = new WebSocket.Server({ port: 8080 });

wss.on('listening', () => {
  console.log('Signaling server listening on port 8080');
});

wss.on('connection', ws => {
  console.log('A new peer connected!');

  ws.on('message', message => {
    console.log(`Received message: ${message}`);
    // We'll add more logic here later
  });

  ws.on('close', () => {
    console.log('A peer disconnected.');
  });

  ws.on('error', error => {
    console.error('WebSocket error:', error);
  });
});

console.log('Server setup complete. Waiting for connections...');

Managing Connected Peers

When a peer connects, our server needs to give it a unique ID and keep track of it. This allows us to send messages to specific clients later.

We'll use a Map to store active WebSocket connections, mapping each peer's ID to its WebSocket object. The server also sends the assigned ID back to the client.

const WebSocket = require('ws');

const wss = new WebSocket.Server({ port: 8080 });

// Store active connections: Map<peerId, WebSocket>
const connectedPeers = new Map();

wss.on('listening', () => {
  console.log('Signaling server listening on port 8080');
});

wss.on('connection', ws => {
  // Generate a unique ID for the new peer
  const peerId = Math.random().toString(36).substring(2, 10);
  connectedPeers.set(peerId, ws);
  console.log(`Peer ${peerId} connected. Total: ${connectedPeers.size}`);

  // Send the assigned ID back to the client
  ws.send(JSON.stringify({ type: 'yourId', id: peerId }));

  ws.on('message', message => {
    console.log(`Received from ${peerId}: ${message}`);
    // Message routing logic will go here
  });

  ws.on('close', () => {
    connectedPeers.delete(peerId);
    console.log(`Peer ${peerId} disconnected. Total: ${connectedPeers.size}`);
  });

  ws.on('error', error => {
    console.error(`WebSocket error for ${peerId}:`, error);
  });
});

console.log('Server setup complete. Waiting for connections...');

The Core: Message Routing

The main job of our signaling server is to route messages between peers. A client will send a message to the server, specifying who the intended target peer is.

The server then looks up the target peer's WebSocket connection and forwards the message. If the target isn't found, it might send an error back to the sender.

const WebSocket = require('ws');

const wss = new WebSocket.Server({ port: 8080 });
const connectedPeers = new Map();

wss.on('listening', () => {
  console.log('Signaling server listening on port 8080');
});

wss.on('connection', ws => {
  const peerId = Math.random().toString(36).substring(2, 10);
  connectedPeers.set(peerId, ws);
  console.log(`Peer ${peerId} connected. Total: ${connectedPeers.size}`);
  ws.send(JSON.stringify({ type: 'yourId', id: peerId }));

  ws.on('message', message => {
    let parsedMessage;
    try {
      parsedMessage = JSON.parse(message);
    } catch (e) {
      console.error('Failed to parse message:', message); return;
    }

    const { targetId, type, payload } = parsedMessage;

    // If a targetId is specified, try to route the message
    if (targetId) {
      const targetPeerWs = connectedPeers.get(targetId);
      if (targetPeerWs) {
        // Forward the message to the target peer
        targetPeerWs.send(JSON.stringify({ senderId: peerId, type, payload }));
        console.log(`Routed ${type} from ${peerId} to ${targetId}`);
      } else {
        console.log(`Target peer ${targetId} not found.`);
        ws.send(JSON.stringify({ type: 'error', message: `Peer ${targetId} not found.` }));
      }
    } else {
      console.log(`Message from ${peerId} has no targetId: ${type}`);
      // Handle messages without a targetId (e.g., 'yourId' response already handled)
    }
  });

  ws.on('close', () => {
    connectedPeers.delete(peerId);
    console.log(`Peer ${peerId} disconnected. Total: ${connectedPeers.size}`);
  });

  ws.on('error', error => {
    console.error(`WebSocket error for ${peerId}:`, error);
  });
});

console.log('Server setup complete. Waiting for connections...');

Handling SDP Offers

The first key message in WebRTC setup is the SDP Offer. The 'calling' peer generates an offer describing its media capabilities and sends it to the signaling server.

Our server's job is to receive this offer, identify its type, and then route it to the intended 'callee' peer. The server doesn't modify the SDP; it just relays it.

const WebSocket = require('ws');

const wss = new WebSocket.Server({ port: 8080 });
const connectedPeers = new Map();

wss.on('listening', () => {
  console.log('Signaling server listening on port 8080');
});

wss.on('connection', ws => {
  const peerId = Math.random().toString(36).substring(2, 10);
  connectedPeers.set(peerId, ws);
  console.log(`Peer ${peerId} connected. Total: ${connectedPeers.size}`);
  ws.send(JSON.stringify({ type: 'yourId', id: peerId }));

  ws.on('message', message => {
    let parsedMessage;
    try {
      parsedMessage = JSON.parse(message);
    } catch (e) {
      console.error('Failed to parse message:', message); return;
    }

    const { targetId, type, payload } = parsedMessage;

    if (targetId) {
      const targetPeerWs = connectedPeers.get(targetId);
      if (targetPeerWs) {
        // --- NEW LOGIC: Handle SDP Offer ---
        if (type === 'offer') {
          console.log(`Received SDP Offer from ${peerId} for ${targetId}`);
          // Forward the offer to the target peer
          targetPeerWs.send(JSON.stringify({ senderId: peerId, type: 'offer', sdp: payload.sdp }));
        } else {
          // Generic forwarding for other message types (e.g., answer, ICE)
          targetPeerWs.send(JSON.stringify({ senderId: peerId, type, payload }));
          console.log(`Routed ${type} from ${peerId} to ${targetId}`);
        }
      } else {
        console.log(`Target peer ${targetId} not found.`);
        ws.send(JSON.stringify({ type: 'error', message: `Peer ${targetId} not found.` }));
      }
    } else {
      console.log(`Message from ${peerId} has no targetId: ${type}`);
    }
  });

  ws.on('close', () => {
    connectedPeers.delete(peerId);
    console.log(`Peer ${peerId} disconnected. Total: ${connectedPeers.size}`);
  });

  ws.on('error', error => {
    console.error(`WebSocket error for ${peerId}:`, error);
  });
});

console.log('Server setup complete. Waiting for connections...');

Handling SDP Answers

Once the callee receives an SDP Offer, it generates an SDP Answer, describing its own media capabilities, and sends it back to the signaling server.

The server then receives this answer and routes it back to the original 'caller' peer. This completes the SDP exchange, establishing the basic media contract between the two peers.

const WebSocket = require('ws');

const wss = new WebSocket.Server({ port: 8080 });
const connectedPeers = new Map();

wss.on('listening', () => {
  console.log('Signaling server listening on port 8080');
});

wss.on('connection', ws => {
  const peerId = Math.random().toString(36).substring(2, 10);
  connectedPeers.set(peerId, ws);
  console.log(`Peer ${peerId} connected. Total: ${connectedPeers.size}`);
  ws.send(JSON.stringify({ type: 'yourId', id: peerId }));

  ws.on('message', message => {
    let parsedMessage;
    try {
      parsedMessage = JSON.parse(message);
    } catch (e) {
      console.error('Failed to parse message:', message); return;
    }

    const { targetId, type, payload } = parsedMessage;

    if (targetId) {
      const targetPeerWs = connectedPeers.get(targetId);
      if (targetPeerWs) {
        if (type === 'offer') {
          targetPeerWs.send(JSON.stringify({ senderId: peerId, type: 'offer', sdp: payload.sdp }));
          console.log(`Routed SDP Offer from ${peerId} to ${targetId}`);
        // --- NEW LOGIC: Handle SDP Answer ---
        } else if (type === 'answer') {
          console.log(`Received SDP Answer from ${peerId} for ${targetId}`);
          // Forward the answer to the target peer
          targetPeerWs.send(JSON.stringify({ senderId: peerId, type: 'answer', sdp: payload.sdp }));
        } else {
          // Generic forwarding for other message types (e.g., ICE)
          targetPeerWs.send(JSON.stringify({ senderId: peerId, type, payload }));
          console.log(`Routed ${type} from ${peerId} to ${targetId}`);
        }
      } else {
        console.log(`Target peer ${targetId} not found.`);
        ws.send(JSON.stringify({ type: 'error', message: `Peer ${targetId} not found.` }));
      }
    } else {
      console.log(`Message from ${peerId} has no targetId: ${type}`);
    }
  });

  ws.on('close', () => {
    connectedPeers.delete(peerId);
    console.log(`Peer ${peerId} disconnected. Total: ${connectedPeers.size}`);
  });

  ws.on('error', error => {
    console.error(`WebSocket error for ${peerId}:`, error);
  });
});

console.log('Server setup complete. Waiting for connections...');

Handling ICE Candidates

After the SDP exchange, peers also need to find the best network paths. They generate ICE Candidates, which are potential network addresses.

These candidates are sent frequently and must also be relayed by the signaling server. The server simply forwards each candidate from the sender to the target peer as it arrives.

const WebSocket = require('ws');

const wss = new WebSocket.Server({ port: 8080 });
const connectedPeers = new Map();

wss.on('listening', () => {
  console.log('Signaling server listening on port 8080');
});

wss.on('connection', ws => {
  const peerId = Math.random().toString(36).substring(2, 10);
  connectedPeers.set(peerId, ws);
  console.log(`Peer ${peerId} connected. Total: ${connectedPeers.size}`);
  ws.send(JSON.stringify({ type: 'yourId', id: peerId }));

  ws.on('message', message => {
    let parsedMessage;
    try {
      parsedMessage = JSON.parse(message);
    } catch (e) {
      console.error('Failed to parse message:', message); return;
    }

    const { targetId, type, payload } = parsedMessage;

    if (targetId) {
      const targetPeerWs = connectedPeers.get(targetId);
      if (targetPeerWs) {
        if (type === 'offer') {
          targetPeerWs.send(JSON.stringify({ senderId: peerId, type: 'offer', sdp: payload.sdp }));
          console.log(`Routed SDP Offer from ${peerId} to ${targetId}`);
        } else if (type === 'answer') {
          targetPeerWs.send(JSON.stringify({ senderId: peerId, type: 'answer', sdp: payload.sdp }));
          console.log(`Routed SDP Answer from ${peerId} to ${targetId}`);
        // --- NEW LOGIC: Handle ICE Candidate ---
        } else if (type === 'iceCandidate') {
          console.log(`Received ICE Candidate from ${peerId} for ${targetId}`);
          // Forward the ICE candidate to the target peer
          targetPeerWs.send(JSON.stringify({ senderId: peerId, type: 'iceCandidate', candidate: payload.candidate }));
        } else {
          console.log(`Unknown message type: ${type} from ${peerId}`);
        }
      } else {
        console.log(`Target peer ${targetId} not found.`);
        ws.send(JSON.stringify({ type: 'error', message: `Peer ${targetId} not found.` }));
      }
    } else {
      console.log(`Message from ${peerId} has no targetId: ${type}`);
    }
  });

  ws.on('close', () => {
    connectedPeers.delete(peerId);
    console.log(`Peer ${peerId} disconnected. Total: ${connectedPeers.size}`);
  });

  ws.on('error', error => {
    console.error(`WebSocket error for ${peerId}:`, error);
  });
});

console.log('Server setup complete. Waiting for connections...');

Putting It All Together: A Simple Signaling Flow

Let's trace a typical call setup with our server logic:

  • Peer A (caller) connects, gets an ID.
  • Peer B (callee) connects, gets an ID.
  • Peer A creates an SDP Offer, sends it to the server, targeting Peer B.
  • Server receives Offer, routes it to Peer B.
  • Peer B receives Offer, creates SDP Answer, sends it to server, targeting Peer A.
  • Server receives Answer, routes it to Peer A.
  • Both Peer A and Peer B generate ICE Candidates, send them to the server, targeting each other.
  • Server receives Candidates, routes them to the correct peer.

Once all this information is exchanged, WebRTC can attempt a direct peer-to-peer connection!

Signaling Logic Checklist

When designing or implementing your signaling server logic, always ensure it handles these key aspects:

  • Unique Peer IDs: Each connected client must have a distinct identifier.
  • Connection Management: Add and remove peers as they connect/disconnect.
  • Message Parsing: Correctly interpret incoming JSON messages (type, targetId, payload).
  • Message Routing: Efficiently forward messages to the intended recipient.
  • Error Handling: Gracefully manage cases like unknown target IDs or malformed messages.

A robust signaling server is crucial for reliable WebRTC applications.

Quick Check: Signaling Message Flow

A client wants to establish a WebRTC connection. It first connects to the signaling server and then sends an SDP Offer to initiate the call. Which of the following describes the signaling server's next correct action?

Recap: Building Signaling Logic

In this lesson, we explored the essential server-side logic for a WebRTC signaling server. We learned how to:

  • Set up a basic WebSocket server.
  • Manage unique IDs for connected peers.
  • Implement message routing to forward SDP offers, answers, and ICE candidates.

This server acts as a crucial intermediary, enabling peers to exchange the necessary metadata to eventually establish a direct peer-to-peer WebRTC connection. A well-designed signaling server is the backbone of any robust WebRTC application.

الأسئلة الشائعة

هل درس «تنفيذ منطق الإشارة» مجاني؟

نعم — نص درس «تنفيذ منطق الإشارة» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Real-Time Streaming Systems (WebRTC + Live Data)، انتقل إلى CoddyKit PRO. تتضمن دورة Real-Time Streaming Systems (WebRTC + Live Data) 4 دروس في المجموع.

ماذا ستتعلم في «تنفيذ منطق الإشارة»؟

طوّر المنطق من جهة الخادم لمعالجة طلبات الاتصال وتبادل عروض وإجابات SDP ومرشحي ICE بين النظراء. تتمرن على Real-Time Streaming Systems (WebRTC + Live Data) مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ Real-Time Streaming Systems (WebRTC + Live Data)؟

لا تُشترط خبرة سابقة. Real-Time Streaming Systems (WebRTC + Live Data) على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 4.

كم من الوقت يستغرق درس «تنفيذ منطق الإشارة»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس Real-Time Streaming Systems (WebRTC + Live Data) هذا؟

نعم. كل درس في Real-Time Streaming Systems (WebRTC + Live Data) يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. اختيار الواجهة الخلفية للإشارة
  2. تنفيذ منطق الإشارة
  3. نشر الإشارة واختبارها
  4. توسيع نطاق الإشارة باستخدام الغرف وRedis
← العودة إلى Real-Time Streaming Systems (WebRTC + Live Data)