Realtime-streamingsystemen (WebRTC + live gegevens) · Les

Signalinglogica implementeren

Ontwikkel de serverlogica voor het afhandelen van verbindingsverzoeken en het uitwisselen van SDP-aanbiedingen en -antwoorden en ICE-candidates tussen peers.

Les 2 van 411 stappen

Signalinglogica implementeren is een gratis Realtime-streamingsystemen (WebRTC + live gegevens)-les op CoddyKit. Dit is les 2 van 4. Je kunt de volledige les hieronder gratis lezen en daarna in de browser praktisch oefenen met een ingebouwde code-editor en een AI-begeleider die 24/7 beschikbaar is. Deze les maakt deel uit van het leertraject Realtime-streamingsystemen (WebRTC + live gegevens). Je voortgang wordt gesynchroniseerd op het web en in de CoddyKit-app. De cursus Realtime-streamingsystemen (WebRTC + live gegevens) bevat in totaal 4 lessen.

Signaleringslogica: de WebRTC-koppelaar

Welkom bij de kern van het opzetten van WebRTC-verbindingen! Voordat peers rechtstreeks met elkaar kunnen communiceren, hebben ze een manier nodig om cruciale configuratie-informatie uit te wisselen. Daar komt signaleringslogica om de hoek kijken.

Een signaleringsserver fungeert als een tijdelijke koppelaar die de eerste handshake begeleidt. De server verwerkt mediastromen niet rechtstreeks, maar is wel essentieel voor het tot stand brengen van de verbinding.

  • Peers ontdekken: Helpt peers elkaar te vinden.
  • Metadata uitwisselen: Stuurt aanbiedingen en antwoorden van het Session Description Protocol (SDP) door.
  • Netwerkinformatie delen: Stuurt ICE-kandidaten (netwerkadressen) door.

Onze signaleringsserver instellen

Onze signaleringsserver gebruikt WebSockets voor realtimecommunicatie in twee richtingen. Zo stel je een eenvoudige server in met Node.js en de populaire bibliotheek ws.

Deze server luistert op poort 8080 naar binnenkomende WebSocket-verbindingen en vormt de basis voor onze signaleringslogica.

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...');

Verbonden peers beheren

Wanneer een peer verbinding maakt, moet onze server een unieke ID toewijzen en deze bijhouden. Zo kunnen we later berichten naar specifieke clients sturen.

We gebruiken een Map om actieve WebSocket-verbindingen op te slaan. Daarbij koppelen we de ID van elke peer aan het WebSocket-object ervan. De server stuurt de toegewezen ID ook terug naar de 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...');

De kern: berichten routeren

De belangrijkste taak van onze signaleringsserver is berichten tussen peers routeren. Een client stuurt een bericht naar de server en geeft aan wie de beoogde doelpeer is.

De server zoekt vervolgens de WebSocket-verbinding van de doelpeer op en stuurt het bericht door. Als de doelpeer niet wordt gevonden, kan de server een fout terugsturen naar de afzender.

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...');

SDP-aanbiedingen verwerken

Het eerste belangrijke bericht bij het instellen van WebRTC is de SDP-aanbieding. De peer die 'belt' genereert een aanbieding waarin de medi mogelijkheden worden beschreven en stuurt deze naar de signaleringsserver.

De taak van onze server is deze aanbieding te ontvangen, het type ervan vast te stellen en deze vervolgens door te sturen naar de beoogde 'gebelde' peer. De server wijzigt de SDP niet, maar stuurt deze alleen door.

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...');

SDP-antwoorden verwerken

Zodra de gebelde peer een SDP-aanbieding ontvangt, genereert deze een SDP-antwoord waarin de eigen mediamogelijkheden worden beschreven, en stuurt dit terug naar de signaleringsserver.

De server ontvangt dit antwoord en stuurt het terug naar de oorspronkelijke 'beller'. Hiermee wordt de SDP-uitwisseling voltooid en het basis-mediacontract tussen de twee peers vastgelegd.

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...');

ICE-kandidaten verwerken

Na de SDP-uitwisseling moeten peers ook de beste netwerkpaden vinden. Ze genereren ICE-kandidaten, oftewel mogelijke netwerkadressen.

Deze kandidaten worden vaak verstuurd en moeten ook door de signaleringsserver worden doorgestuurd. De server stuurt elke kandidaat eenvoudig door van de afzender naar de doelpeer zodra deze binnenkomt.

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...');

Alles samenbrengen: een eenvoudige signaleringsstroom

Laten we een gebruikelijke oproepinstelling met onze serverlogica volgen:

  • Peer A (beller) maakt verbinding en krijgt een ID.
  • Peer B (gebelde) maakt verbinding en krijgt een ID.
  • Peer A maakt een SDP-aanbieding, stuurt deze naar de server en richt deze aan Peer B.
  • De server ontvangt de aanbieding en stuurt deze door naar Peer B.
  • Peer B ontvangt de aanbieding, maakt een SDP-antwoord en stuurt dit naar de server met Peer A als doel.
  • De server ontvangt het antwoord en stuurt dit door naar Peer A.
  • Zowel Peer A als Peer B genereert ICE-kandidaten en stuurt deze naar de server, met elkaar als doel.
  • De server ontvangt de kandidaten en stuurt ze door naar de juiste peer.

Zodra al deze informatie is uitgewisseld, kan WebRTC proberen een rechtstreekse peer-to-peerverbinding tot stand te brengen!

Checklist voor signaleringslogica

Zorg er bij het ontwerpen of implementeren van de logica van je signaleringsserver altijd voor dat deze de volgende belangrijke aspecten verwerkt:

  • Unieke peer-ID's: Elke verbonden client moet een unieke identificatie hebben.
  • Verbindingsbeheer: Voeg peers toe en verwijder ze wanneer ze verbinding maken of verbreken.
  • Berichten parseren: Interpreteer binnenkomende JSON-berichten correct (type, targetId, payload).
  • Berichten routeren: Stuur berichten efficiënt door naar de beoogde ontvanger.
  • Fouten afhandelen: Verwerk situaties zoals onbekende doel-ID's of onjuist opgemaakte berichten op een nette manier.

Een robuuste signaleringsserver is cruciaal voor betrouwbare WebRTC-toepassingen.

Korte controle: stroom van signaleringsberichten

Een client wil een WebRTC-verbinding tot stand brengen. Eerst maakt de client verbinding met de signaleringsserver en stuurt vervolgens een SDP-aanbieding om de oproep te starten. Welke van de volgende opties beschrijft de juiste volgende actie van de signaleringsserver?

Samenvatting: signaleringslogica bouwen

In deze les hebben we de essentiële logica aan de serverzijde voor een WebRTC-signaleringsserver onderzocht. We hebben geleerd hoe je:

  • Een eenvoudige WebSocket-server instelt.
  • Unieke ID's voor verbonden peers beheert.
  • Berichtenroutering implementeert om SDP-aanbiedingen, antwoorden en ICE-kandidaten door te sturen.

Deze server fungeert als een cruciale tussenpersoon, waarmee peers de benodigde metadata kunnen uitwisselen om uiteindelijk een rechtstreekse peer-to-peer-WebRTC-verbinding tot stand te brengen. Een goed ontworpen signaleringsserver vormt de ruggengraat van elke robuuste WebRTC-toepassing.

Gratis beginnen

Leer Realtime-streamingsystemen (WebRTC + live gegevens) met een AI-tutor — gratis

Schrijf echte code en voer die uit in je browser, krijg direct hulp van een AI-tutor die 24/7 beschikbaar is en ga verder waar je gebleven bent op het web of in de app.

Cursussen
12
Lessen
48

Veelgestelde vragen

Is de les “Signalinglogica implementeren” gratis?

Ja — de volledige tekst van “Signalinglogica implementeren” kun je hier gratis op het web lezen. Als je interactief wilt oefenen met een ingebouwde code-editor en een AI-begeleider die 24/7 beschikbaar is, en de rest van de cursus Realtime-streamingsystemen (WebRTC + live gegevens) wilt ontgrendelen, kun je upgraden naar CoddyKit PRO. De cursus Realtime-streamingsystemen (WebRTC + live gegevens) bevat in totaal 4 lessen.

Wat leer ik in “Signalinglogica implementeren”?

Ontwikkel de serverlogica voor het afhandelen van verbindingsverzoeken en het uitwisselen van SDP-aanbiedingen en -antwoorden en ICE-candidates tussen peers. Je oefent met Realtime-streamingsystemen (WebRTC + live gegevens) door code rechtstreeks in de browser uit te voeren. Een AI-begeleider die 24/7 beschikbaar is beantwoordt je vragen terwijl je de les doorwerkt.

Heb ik ervaring nodig om met Realtime-streamingsystemen (WebRTC + live gegevens) te beginnen?

Ervaring vooraf is niet nodig. Realtime-streamingsystemen (WebRTC + live gegevens) op CoddyKit is opgebouwd voor beginners tot gevorderden, zodat je hier of bij het begin kunt starten en in je eigen tempo kunt leren. Dit is les 2 van 4.

Hoe lang duurt de les “Signalinglogica implementeren”?

De meeste lessen van CoddyKit duren ongeveer 5–10 minuten. Elke les is kort en interactief, zodat je gestaag vooruitgaat en op het web en in de app precies verdergaat waar je was gebleven.

Kan ik code schrijven en uitvoeren in deze les over Realtime-streamingsystemen (WebRTC + live gegevens)?

Ja. Elke les over Realtime-streamingsystemen (WebRTC + live gegevens) bevat een ingebouwde code-editor, zodat je rechtstreeks in je browser echte code kunt schrijven en uitvoeren en direct feedback van AI krijgt — lokale installatie is niet nodig.

Alle lessen in deze cursus

  1. Een backend voor signaling kiezen
  2. Signalinglogica implementeren
  3. Signaling implementeren en testen
  4. Signaling schalen met rooms en Redis
← Terug naar Realtime-streamingsystemen (WebRTC + live gegevens)