Streaming-systemer i realtid (WebRTC og live-data) · Lektion

Implementering af signaleringslogik

Udvikl logikken på serversiden til håndtering af forbindelsesanmodninger samt udveksling af SDP-tilbud, -svar og ICE-kandidater mellem peers.

Lektion 2 af 411 trin

Implementering af signaleringslogik er en gratis Streaming-systemer i realtid (WebRTC og live-data)-lektion på CoddyKit. Dette er lektion 2 af 4. Du kan læse hele lektionen gratis nedenfor — og derefter øve dig praktisk i browseren med en indbygget kodeeditor og en AI-vejleder, der er tilgængelig døgnet rundt. Den er en del af læringsforløbet i Streaming-systemer i realtid (WebRTC og live-data), og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. Streaming-systemer i realtid (WebRTC og live-data)-kurset indeholder 4 lektioner i alt.

Signaleringens logik: WebRTC-matchmakeren

Velkommen til kernen i opsætningen af WebRTC-forbindelser! Før peers kan kommunikere direkte, har de brug for en måde at udveksle vigtige opsætningsoplysninger på. Det er her, signaleringens logik kommer ind i billedet.

En signaleringsserver fungerer som en midlertidig matchmaker, der hjælper med det indledende håndtryk. Den håndterer ikke mediestrømme direkte, men er afgørende for at oprette forbindelsen.

  • Find peers: Hjælper peers med at finde hinanden.
  • Udveksl metadata: Videregiver tilbud og svar i Session Description Protocol (SDP).
  • Del netværksoplysninger: Videregiver ICE-kandidater (netværksadresser).

Opsætning af vores signaleringsserver

Vores signaleringsserver bruger WebSockets til kommunikation i realtid i begge retninger. Sådan opsætter du en grundlæggende server med Node.js og det populære bibliotek ws.

Denne server lytter efter indgående WebSocket-forbindelser på port 8080 og danner grundlaget for vores signaleringslogik.

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

Håndtering af forbundne peers

Når en peer opretter forbindelse, skal vores server give den et entydigt ID og holde styr på den. Det gør det muligt for os senere at sende meddelelser til bestemte klienter.

Vi bruger en Map til at gemme aktive WebSocket-forbindelser og knytte hver peers ID til dens WebSocket-objekt. Serveren sender også det tildelte ID tilbage til klienten.

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

Kernen: Dirigering af meddelelser

Vores signaleringsservers vigtigste opgave er at dirigere meddelelser mellem peers. En klient sender en meddelelse til serveren og angiver, hvem den tilsigtede mål-peer er.

Serveren slår derefter mål-peerens WebSocket-forbindelse op og videresender meddelelsen. Hvis mål-peer'en ikke findes, kan serveren sende en fejl tilbage til afsenderen.

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

Håndtering af SDP-tilbud

Den første vigtige meddelelse i WebRTC-opsætningen er SDP-tilbuddet. Den peer, der ringer op, genererer et tilbud, som beskriver dens mediefunktioner, og sender det til signaleringsserveren.

Vores servers opgave er at modtage dette tilbud, identificere dets type og derefter dirigere det til den tilsigtede peer, der modtager opkaldet. Serveren ændrer ikke SDP'en; den videresender den blot.

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

Håndtering af SDP-svar

Når den peer, der modtager opkaldet, modtager et SDP-tilbud, genererer den et SDP-svar, der beskriver dens egne mediefunktioner, og sender det tilbage til signaleringsserveren.

Serveren modtager derefter dette svar og dirigerer det tilbage til den oprindelige peer, der ringede op. Det fuldfører SDP-udvekslingen og etablerer den grundlæggende mediekontrakt mellem de to 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...');

Håndtering af ICE-kandidater

Efter SDP-udvekslingen skal peers også finde de bedste netværksstier. De genererer ICE-kandidater, som er mulige netværksadresser.

Disse kandidater sendes ofte og skal også videresendes af signaleringsserveren. Serveren videresender blot hver kandidat fra afsenderen til mål-peer'en, når den ankommer.

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

Samlet overblik: Et simpelt signaleringsforløb

Lad os gennemgå et typisk opsætningsforløb for et opkald med vores serverlogik:

  • Peer A (den, der ringer op) opretter forbindelse og får et ID.
  • Peer B (den, der modtager opkaldet) opretter forbindelse og får et ID.
  • Peer A opretter et SDP-tilbud, sender det til serveren og angiver Peer B som mål.
  • Serveren modtager tilbuddet og dirigerer det til Peer B.
  • Peer B modtager tilbuddet, opretter et SDP-svar og sender det til serveren med Peer A som mål.
  • Serveren modtager svaret og dirigerer det til Peer A.
  • Både Peer A og Peer B genererer ICE-kandidater og sender dem til serveren med hinanden som mål.
  • Serveren modtager kandidaterne og dirigerer dem til den korrekte peer.

Når alle disse oplysninger er udvekslet, kan WebRTC forsøge at oprette en direkte peer-to-peer-forbindelse!

Tjekliste for signaleringslogik

Når du designer eller implementerer logikken i din signaleringsserver, skal du altid sikre dig, at den håndterer disse vigtige aspekter:

  • Entydige peer-ID'er: Hver forbundne klient skal have en særskilt identifikator.
  • Forbindelseshåndtering: Tilføj og fjern peers, når de opretter eller afbryder forbindelsen.
  • Fortolkning af meddelelser: Fortolk indgående JSON-meddelelser korrekt (type, targetId, payload).
  • Dirigering af meddelelser: Videresend meddelelser effektivt til den tilsigtede modtager.
  • Fejlhåndtering: Håndter situationer som ukendte mål-ID'er eller fejlformaterede meddelelser på en robust måde.

En robust signaleringsserver er afgørende for pålidelige WebRTC-applikationer.

Hurtigt tjek: Signaleringsforløb for meddelelser

En klient vil oprette en WebRTC-forbindelse. Den opretter først forbindelse til signaleringsserveren og sender derefter et SDP-tilbud for at starte opkaldet. Hvilken af følgende handlinger beskriver signaleringsserverens næste korrekte trin?

Opsummering: Opbygning af signaleringslogik

I denne lektion undersøgte vi den nødvendige serversidelogik for en WebRTC-signaliseringsserver. Vi lærte, hvordan du:

  • Opsætter en grundlæggende WebSocket-server.
  • Håndterer entydige ID'er for forbundne peers.
  • Implementerer dirigering af meddelelser for at videresende SDP-tilbud, -svar og ICE-kandidater.

Denne server fungerer som et vigtigt mellemled, der gør det muligt for peers at udveksle de nødvendige metadata og til sidst oprette en direkte peer-to-peer-WebRTC-forbindelse. En veldesignet signaleringsserver er rygraden i enhver robust WebRTC-applikation.

Gratis at komme i gang

Lær Streaming-systemer i realtid (WebRTC og live-data) med en AI-underviser — gratis

Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.

Kurser
12
Lektioner
48

Ofte stillede spørgsmål

Er lektionen “Implementering af signaleringslogik” gratis?

Ja — hele teksten til “Implementering af signaleringslogik” kan læses gratis her på nettet. Hvis du vil øve dig interaktivt med en indbygget kodeeditor og en AI-vejleder døgnet rundt og få adgang til resten af Streaming-systemer i realtid (WebRTC og live-data)-kurset, skal du opgradere til CoddyKit PRO. Streaming-systemer i realtid (WebRTC og live-data)-kurset indeholder 4 lektioner i alt.

Hvad lærer jeg i “Implementering af signaleringslogik”?

Udvikl logikken på serversiden til håndtering af forbindelsesanmodninger samt udveksling af SDP-tilbud, -svar og ICE-kandidater mellem peers. Du øver dig i Streaming-systemer i realtid (WebRTC og live-data) med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.

Skal jeg have erfaring for at begynde på Streaming-systemer i realtid (WebRTC og live-data)?

Der kræves ingen tidligere erfaring. Streaming-systemer i realtid (WebRTC og live-data) på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 2 af 4.

Hvor lang tid tager lektionen “Implementering af signaleringslogik”?

De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.

Kan jeg skrive og køre kode i denne Streaming-systemer i realtid (WebRTC og live-data)-lektion?

Ja. Alle Streaming-systemer i realtid (WebRTC og live-data)-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.

Alle lektioner i dette kursus

  1. Valg af backend til signalering
  2. Implementering af signaleringslogik
  3. Udrulning og test af signalering
  4. Skalering af signaling med rooms og Redis
← Tilbage til Streaming-systemer i realtid (WebRTC og live-data)