WebRTC - Segnalazione

La maggior parte delle applicazioni WebRTC non è solo in grado di comunicare tramite video e audio. Hanno bisogno di molte altre funzionalità. In questo capitolo costruiremo un server di segnalazione di base.

Segnalazione e negoziazione

Per connetterti a un altro utente devi sapere dove si trova sul web. L'indirizzo IP del dispositivo consente ai dispositivi abilitati a Internet di inviare dati direttamente tra loro. L' oggetto RTCPeerConnection è responsabile di ciò. Non appena i dispositivi sanno come trovarsi su Internet, iniziano a scambiare dati sui protocolli e codec supportati da ciascun dispositivo.

Per comunicare con un altro utente è sufficiente scambiare le informazioni di contatto e il resto sarà fatto da WebRTC. Il processo di connessione all'altro utente è noto anche come segnalazione e negoziazione. Consiste in pochi passaggi:

  • Crea un elenco di potenziali candidati per una connessione peer.

  • L'utente o un'applicazione seleziona un utente con cui stabilire una connessione.

  • Il livello di segnalazione notifica a un altro utente che qualcuno desidera connettersi a lui. Può accettare o rifiutare.

  • Il primo utente viene informato dell'accettazione dell'offerta.

  • Il primo utente avvia RTCPeerConnection con un altro utente.

  • Entrambi gli utenti si scambiano informazioni software e hardware tramite il server di segnalazione.

  • Entrambi gli utenti si scambiano informazioni sulla posizione.

  • La connessione ha esito positivo o negativo.

La specifica WebRTC non contiene standard sullo scambio di informazioni. Quindi tieni presente che quanto sopra è solo un esempio di come può accadere la segnalazione. Puoi utilizzare qualsiasi protocollo o tecnologia che preferisci.

Costruire il server

Il server che stiamo per costruire sarà in grado di connettere due utenti che non si trovano sullo stesso computer. Creeremo il nostro meccanismo di segnalazione. Il nostro server di segnalazione consentirà a un utente di chiamarne un altro. Una volta che un utente ha chiamato un altro, il server passa l'offerta, la risposta, i candidati ICE tra di loro e imposta una connessione WebRTC.

Il diagramma sopra è il flusso di messaggistica tra gli utenti quando si utilizza il server di segnalazione. Prima di tutto, ogni utente si registra al server. Nel nostro caso, questo sarà un semplice nome utente stringa. Una volta che gli utenti si sono registrati, possono chiamarsi a vicenda. L'utente 1 fa un'offerta con l'identificativo utente che desidera chiamare. L'altro utente dovrebbe rispondere. Infine, i candidati ICE vengono inviati tra gli utenti finché non riescono a stabilire una connessione.

Per creare una connessione WebRTC, i client devono essere in grado di trasferire messaggi senza utilizzare una connessione peer WebRTC. Qui è dove utilizzeremo HTML5 WebSocket, una connessione socket bidirezionale tra due endpoint: un server web e un browser web. Ora iniziamo a utilizzare la libreria WebSocket. Crea il file server.js e inserisci il seguente codice:

//require our websocket library 
var WebSocketServer = require('ws').Server; 

//creating a websocket server at port 9090 
var wss = new WebSocketServer({port: 9090}); 
 
//when a user connects to our sever 
wss.on('connection', function(connection) { 
   console.log("user connected");
	
   //when server gets a message from a connected user 
   connection.on('message', function(message){ 
      console.log("Got message from a user:", message); 
   }); 
	
   connection.send("Hello from server"); 
});

La prima riga richiede la libreria WebSocket che abbiamo già installato. Quindi creiamo un server socket sulla porta 9090. Successivamente, ascoltiamo l' evento di connessione . Questo codice verrà eseguito quando un utente effettua una connessione WebSocket al server. Quindi ascoltiamo tutti i messaggi inviati dall'utente. Infine, inviamo una risposta all'utente connesso dicendo "Hello from server".

Ora esegui il server del nodo e il server dovrebbe iniziare ad ascoltare le connessioni socket.

Per testare il nostro server, utilizzeremo l' utility wscat che abbiamo già installato. Questo strumento aiuta a connettersi direttamente al server WebSocket e testare i comandi. Esegui il nostro server in una finestra di terminale, quindi aprine un'altra ed esegui il comando wscat -c ws: // localhost: 9090 . Dovresti vedere quanto segue sul lato client:

Il server dovrebbe anche registrare l'utente connesso -

Registrazione Utente

Nel nostro server di segnalazione, utilizzeremo un nome utente basato su stringa per ogni connessione in modo da sapere dove inviare i messaggi. Cambiamo un po 'il nostro gestore di connessione -

connection.on('message', function(message) { 
   var data; 
	
   //accepting only JSON messages 
   try { 
      data = JSON.parse(message); 
   } catch (e) { 
      console.log("Invalid JSON"); 
      data = {}; 
   } 
	
});

In questo modo accettiamo solo messaggi JSON. Successivamente, dobbiamo archiviare tutti gli utenti connessi da qualche parte. Useremo un semplice oggetto Javascript per questo. Cambia la parte superiore del nostro file -

//require our websocket library 
var WebSocketServer = require('ws').Server;
 
//creating a websocket server at port 9090 
var wss = new WebSocketServer({port: 9090}); 

//all connected to the server users
var users = {};

Aggiungeremo un campo tipo per ogni messaggio proveniente dal client. Ad esempio, se un utente desidera accedere, invia il messaggio del tipo di accesso . Definiamolo -

connection.on('message', function(message){
   var data; 
	
   //accepting only JSON messages 
   try { 
      data = JSON.parse(message); 
   } catch (e) { 
      console.log("Invalid JSON"); 
      data = {}; 
   }
	
   //switching type of the user message 
   switch (data.type) { 
      //when a user tries to login 
      case "login": 
         console.log("User logged:", data.name); 
			
         //if anyone is logged in with this username then refuse 
         if(users[data.name]) { 
            sendTo(connection, { 
               type: "login", 
               success: false 
            }); 
         } else { 
            //save user connection on the server 
            users[data.name] = connection; 
            connection.name = data.name; 
				
            sendTo(connection, { 
               type: "login", 
               success: true 
            });
				
         } 
			
         break;
					 
      default: 
         sendTo(connection, { 
            type: "error", 
            message: "Command no found: " + data.type 
         }); 
			
         break; 
   } 
	
});

Se l'utente invia un messaggio con il tipo di login , noi:

  • Controlla se qualcuno ha già effettuato l'accesso con questo nome utente

  • In tal caso, informa l'utente che non ha effettuato correttamente l'accesso

  • Se nessuno utilizza questo nome utente, aggiungiamo nome utente come chiave all'oggetto connessione.

  • Se un comando non viene riconosciuto inviamo un errore.

Il codice seguente è una funzione di supporto per l'invio di messaggi a una connessione. Aggiungilo al file server.js -

function sendTo(connection, message) { 
   connection.send(JSON.stringify(message)); 
}

La funzione di cui sopra garantisce che tutti i nostri messaggi vengano inviati nel formato JSON.

Quando l'utente si disconnette, dobbiamo pulire la sua connessione. Possiamo eliminare l'utente quando viene attivato l' evento di chiusura . Aggiungere il codice seguente al gestore della connessione :

connection.on("close", function() { 
   if(connection.name) { 
      delete users[connection.name]; 
   } 
});

Ora testiamo il nostro server con il comando login. Tieni presente che tutti i messaggi devono essere codificati nel formato JSON. Esegui il nostro server e prova ad accedere. Dovresti vedere qualcosa del genere -

Effettuare una chiamata

Dopo aver effettuato correttamente l'accesso, l'utente desidera chiamare un altro. Dovrebbe fare un'offerta a un altro utente per ottenerlo. Aggiungi il gestore dell'offerta -

case "offer": 
   //for ex. UserA wants to call UserB 
   console.log("Sending offer to: ", data.name); 
	
   //if UserB exists then send him offer details 
   var conn = users[data.name]; 
	
   if(conn != null){ 
      //setting that UserA connected with UserB 
      connection.otherName = data.name; 
		
      sendTo(conn, { 
         type: "offer", 
         offer: data.offer, 
         name: connection.name 
      }); 
   }
	
   break;

In primo luogo, otteniamo la connessione dell'utente che stiamo cercando di chiamare. Se esiste, gli inviamo i dettagli dell'offerta . Aggiungiamo anche otherName al collegamento all'oggetto. Questo è fatto per la semplicità di trovarlo in seguito.

Rispondendo

La risposta alla risposta ha un modello simile che abbiamo utilizzato nel gestore dell'offerta . Il nostro server passa semplicemente attraverso tutti i messaggi come risposta a un altro utente. Aggiungi il seguente codice dopo la consegna dell'offerta :

case "answer": 
   console.log("Sending answer to: ", data.name); 
	
   //for ex. UserB answers UserA 
   var conn = users[data.name]; 
	
   if(conn != null) { 
      connection.otherName = data.name; 
      sendTo(conn, { 
         type: "answer", 
         answer: data.answer 
      }); 
   }
	
   break;

Puoi vedere come questo è simile al gestore dell'offerta . Si noti il codice segue le createOffer e createAnswer funzioni della RTCPeerConnection oggetto.

Ora possiamo testare il nostro meccanismo di offerta / risposta. Connetti due clienti allo stesso tempo e prova a fare un'offerta e rispondere. Dovresti vedere quanto segue:

In questo esempio, offer e answer sono semplici stringhe, ma in un'applicazione reale verranno compilate con i dati SDP.

Candidati ICE

La parte finale sta gestendo il candidato ICE tra gli utenti. Usiamo la stessa tecnica solo passando i messaggi tra gli utenti. La differenza principale è che i messaggi candidati potrebbero essere visualizzati più volte per utente in qualsiasi ordine. Aggiungi il gestore candidato -

case "candidate": 
   console.log("Sending candidate to:",data.name); 
   var conn = users[data.name]; 
	
   if(conn != null) {
      sendTo(conn, { 
         type: "candidate", 
         candidate: data.candidate 
      }); 
   }
	
   break;

Dovrebbe funzionare in modo simile ai gestori di offerte e risposte .

Lasciando la connessione

Per consentire ai nostri utenti di disconnettersi da un altro utente dovremmo implementare la funzione di riaggancio. Inoltre dirà al server di eliminare tutti i riferimenti utente. Aggiungi illeave gestore -

case "leave": 
   console.log("Disconnecting from", data.name); 
   var conn = users[data.name]; 
   conn.otherName = null; 
	
   //notify the other user so he can disconnect his peer connection 
   if(conn != null) { 
      sendTo(conn, { 
         type: "leave" 
      }); 
   } 
	
   break;

Questo invierà anche all'altro utente l' evento di congedo in modo che possa disconnettere la sua connessione peer di conseguenza. Dovremmo anche gestire il caso in cui un utente interrompe la connessione dal server di segnalazione. Modifichiamo il nostro gestore vicino -

connection.on("close", function() { 

   if(connection.name) { 
      delete users[connection.name]; 
		
      if(connection.otherName) { 
         console.log("Disconnecting from ", connection.otherName); 
         var conn = users[connection.otherName]; 
         conn.otherName = null;
			
         if(conn != null) { 
            sendTo(conn, { 
               type: "leave" 
            }); 
         }  
      } 
   } 
});

Ora se la connessione termina, i nostri utenti verranno disconnessi. L' evento di chiusura verrà attivato quando un utente chiude la finestra del browser mentre siamo ancora nello stato di offerta , risposta o candidato .

Server di segnalazione completo

Ecco l'intero codice del nostro server di segnalazione -

//require our websocket library 
var WebSocketServer = require('ws').Server;
 
//creating a websocket server at port 9090 
var wss = new WebSocketServer({port: 9090}); 

//all connected to the server users 
var users = {};
  
//when a user connects to our sever 
wss.on('connection', function(connection) {
  
   console.log("User connected");
	
   //when server gets a message from a connected user
   connection.on('message', function(message) { 
	
      var data; 
      //accepting only JSON messages 
      try {
         data = JSON.parse(message); 
      } catch (e) { 
         console.log("Invalid JSON"); 
         data = {}; 
      } 
		
      //switching type of the user message 
      switch (data.type) { 
         //when a user tries to login 
			
         case "login": 
            console.log("User logged", data.name); 
				
            //if anyone is logged in with this username then refuse 
            if(users[data.name]) { 
               sendTo(connection, { 
                  type: "login", 
                  success: false 
               }); 
            } else { 
               //save user connection on the server 
               users[data.name] = connection; 
               connection.name = data.name; 
					
               sendTo(connection, { 
                  type: "login", 
                  success: true 
               }); 
            } 
				
            break; 
				
         case "offer": 
            //for ex. UserA wants to call UserB 
            console.log("Sending offer to: ", data.name); 
				
            //if UserB exists then send him offer details 
            var conn = users[data.name];
				
            if(conn != null) { 
               //setting that UserA connected with UserB 
               connection.otherName = data.name; 
					
               sendTo(conn, { 
                  type: "offer", 
                  offer: data.offer, 
                  name: connection.name 
               }); 
            } 
				
            break;  
				
         case "answer": 
            console.log("Sending answer to: ", data.name); 
            //for ex. UserB answers UserA 
            var conn = users[data.name]; 
				
            if(conn != null) { 
               connection.otherName = data.name; 
               sendTo(conn, { 
                  type: "answer", 
                  answer: data.answer 
               }); 
            } 
				
            break;  
				
         case "candidate": 
            console.log("Sending candidate to:",data.name); 
            var conn = users[data.name];  
				
            if(conn != null) { 
               sendTo(conn, { 
                  type: "candidate", 
                  candidate: data.candidate 
               });
            } 
				
            break;  
				
         case "leave": 
            console.log("Disconnecting from", data.name); 
            var conn = users[data.name]; 
            conn.otherName = null; 
				
            //notify the other user so he can disconnect his peer connection 
            if(conn != null) { 
               sendTo(conn, { 
                  type: "leave" 
               }); 
            }  
				
            break;  
				
         default: 
            sendTo(connection, { 
               type: "error", 
               message: "Command not found: " + data.type 
            }); 
				
            break; 
      }  
   });  
	
   //when user exits, for example closes a browser window 
   //this may help if we are still in "offer","answer" or "candidate" state 
   connection.on("close", function() { 
	
      if(connection.name) { 
      delete users[connection.name]; 
		
         if(connection.otherName) { 
            console.log("Disconnecting from ", connection.otherName);
            var conn = users[connection.otherName]; 
            conn.otherName = null;  
				
            if(conn != null) { 
               sendTo(conn, { 
                  type: "leave" 
               });
            }  
         } 
      } 
   });  
	
   connection.send("Hello world"); 
	
});  

function sendTo(connection, message) { 
   connection.send(JSON.stringify(message)); 
}

Quindi il lavoro è finito e il nostro server di segnalazione è pronto. Ricorda che fare le cose fuori ordine quando si effettua una connessione WebRTC può causare problemi.

Sommario

In questo capitolo, abbiamo costruito un server di segnalazione semplice e diretto. Abbiamo esaminato il processo di segnalazione, la registrazione dell'utente e il meccanismo di offerta / risposta. Abbiamo anche implementato l'invio di candidati tra utenti.