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.