Compila los servicios de backend necesarios para una app de WebRTC

¿Qué es el envío de indicadores?

La señalización es el proceso de coordinar la comunicación. Para que una app de WebRTC configure una llamada, sus clientes deben intercambiar la siguiente información:

  • Mensajes de control de sesión que se usan para abrir o cerrar la comunicación
  • Mensajes de error
  • Metadatos de contenido multimedia, como códecs, configuración de códecs, ancho de banda y tipos de contenido multimedia
  • Datos clave que se usan para establecer conexiones seguras
  • Datos de red, como la dirección IP y el puerto de un host tal como se ven desde el exterior

Este proceso de señalización necesita una forma para que los clientes pasen mensajes de un lado a otro. Las APIs de WebRTC no implementan ese mecanismo. Debes construirlo tú mismo. Más adelante en este artículo, aprenderás a compilar un servicio de señalización. Sin embargo, primero necesitas un poco de contexto.

¿Por qué WebRTC no define la señalización?

Para evitar la redundancia y maximizar la compatibilidad con las tecnologías establecidas, los estándares de WebRTC no especifican los métodos ni los protocolos de señalización. El Protocolo de establecimiento de sesión de JavaScript (JSEP) describe este enfoque de la siguiente manera:

La arquitectura de JSEP también evita que un navegador tenga que guardar el estado, es decir, que funcione como una máquina de estados de señalización. Esto sería problemático si, por ejemplo, se perdieran los datos de señalización cada vez que se volviera a cargar una página. En su lugar, el estado de la señalización se puede guardar en un servidor.

Diagrama de arquitectura de JSEP
Arquitectura de JSEP

JSEP requiere el intercambio entre pares de oferta y respuesta, los metadatos de medios mencionados anteriormente. Las ofertas y las respuestas se comunican en formato de Session Description Protocol (SDP), que se ve de la siguiente manera:

v=0
o=- 7614219274584779017 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE audio video
a=msid-semantic: WMS
m=audio 1 RTP/SAVPF 111 103 104 0 8 107 106 105 13 126
c=IN IP4 0.0.0.0
a=rtcp:1 IN IP4 0.0.0.0
a=ice-ufrag:W2TGCZw2NZHuwlnf
a=ice-pwd:xdQEccP40E+P0L5qTyzDgfmW
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=mid:audio
a=rtcp-mux
a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:9c1AHz27dZ9xPI91YNfSlI67/EMkjHHIHORiClQe
a=rtpmap:111 opus/48000/2

¿Quieres saber qué significa todo este galimatías del SDP? Consulta los ejemplos del Grupo de trabajo de ingeniería de Internet (IETF).

Ten en cuenta que WebRTC está diseñado de modo que la oferta o la respuesta se puedan ajustar antes de establecerse como la descripción local o remota editando los valores en el texto del SDP. Por ejemplo, la función preferAudioCodec() en appr.tc se puede usar para establecer el códec y la tasa de bits predeterminados. El SDP es algo difícil de manipular con JavaScript, y se debate si las versiones futuras de WebRTC deberían usar JSON en su lugar, pero hay algunas ventajas en seguir usando SDP.

API de RTCPeerConnection y señalización: Oferta, respuesta y candidato

RTCPeerConnection es la API que usan las apps de WebRTC para crear una conexión entre pares y comunicar audio y video.

Para inicializar este proceso, RTCPeerConnection tiene dos tareas:

  • Determina las condiciones de los medios locales, como las capacidades de resolución y códec. Son los metadatos que se usan para el mecanismo de oferta y respuesta.
  • Obtener posibles direcciones de red para el host de la app, conocidas como candidatas

Una vez que se determinan estos datos locales, se deben intercambiar a través de un mecanismo de señalización con el par remoto.

Imagina que Alice intenta llamar a Eve. A continuación, se muestra el mecanismo completo de oferta y respuesta con todos sus detalles:

  1. Alice crea un objeto RTCPeerConnection.
  2. Alice crea una oferta (una descripción de sesión de SDP) con el método RTCPeerConnection createOffer().
  3. Alice llama a setLocalDescription() con su oferta.
  4. Alice convierte la oferta en una cadena y usa un mecanismo de señalización para enviársela a Eve.
  5. Eve llama a setRemoteDescription() con la oferta de Alice para que su RTCPeerConnection conozca la configuración de Alice.
  6. Eve llama a createAnswer(), y la devolución de llamada de éxito para esto recibe una descripción de sesión local: la respuesta de Eve.
  7. Eve establece su respuesta como la descripción local llamando a setLocalDescription().
  8. Luego, Eve usa el mecanismo de señalización para enviarle su respuesta convertida en cadena a Alice.
  9. Alice establece la respuesta de Eve como la descripción de la sesión remota con setRemoteDescription().

Alice y Eve también deben intercambiar información de la red. La expresión "búsqueda de candidatos" se refiere al proceso de búsqueda de interfaces y puertos de red con el marco de trabajo de ICE.

  1. Alice crea un objeto RTCPeerConnection con un controlador onicecandidate.
  2. Se llama al controlador cuando hay candidatos de red disponibles.
  3. En el controlador, Alice envía datos de candidatos convertidos en cadenas a Eve a través de su canal de señalización.
  4. Cuando Eve recibe un mensaje de candidato de Alice, llama a addIceCandidate() para agregar el candidato a la descripción del par remoto.

JSEP admite el envío incremental de candidatos ICE, que permite que el llamante proporcione candidatos de forma incremental al llamado después de la oferta inicial, y que el llamado comience a actuar en la llamada y establezca una conexión sin esperar a que lleguen todos los candidatos.

Codifica WebRTC para la señalización

El siguiente fragmento de código es un ejemplo de código de W3C que resume el proceso de señalización completo. El código supone la existencia de algún mecanismo de señalización, SignalingChannel. Más adelante, se analizará el concepto de señalización con mayor detalle.

// handles JSON.stringify/parse
const signaling = new SignalingChannel();
const constraints = {audio: true, video: true};
const configuration = {iceServers: [{urls: 'stun:stun.example.org'}]};
const pc = new RTCPeerConnection(configuration);

// Send any ice candidates to the other peer.
pc.onicecandidate = ({candidate}) => signaling.send({candidate});

// Let the "negotiationneeded" event trigger offer generation.
pc.onnegotiationneeded = async () => {
  try {
    await pc.setLocalDescription(await pc.createOffer());
    // send the offer to the other peer
    signaling.send({desc: pc.localDescription});
  } catch (err) {
    console.error(err);
  }
};

// After remote track media arrives, show it in remote video element.
pc.ontrack = (event) => {
  // Don't set srcObject again if it is already set.
  if (remoteView.srcObject) return;
  remoteView.srcObject = event.streams[0];
};

// Call start() to initiate.
async function start() {
  try {
    // Get local stream, show it in self-view, and add it to be sent.
    const stream =
      await navigator.mediaDevices.getUserMedia(constraints);
    stream.getTracks().forEach((track) =>
      pc.addTrack(track, stream));
    selfView.srcObject = stream;
  } catch (err) {
    console.error(err);
  }
}

signaling.onmessage = async ({desc, candidate}) => {
  try {
    if (desc) {
      // If you get an offer, you need to reply with an answer.
      if (desc.type === 'offer') {
        await pc.setRemoteDescription(desc);
        const stream =
          await navigator.mediaDevices.getUserMedia(constraints);
        stream.getTracks().forEach((track) =>
          pc.addTrack(track, stream));
        await pc.setLocalDescription(await pc.createAnswer());
        signaling.send({desc: pc.localDescription});
      } else if (desc.type === 'answer') {
        await pc.setRemoteDescription(desc);
      } else {
        console.log('Unsupported SDP type.');
      }
    } else if (candidate) {
      await pc.addIceCandidate(candidate);
    }
  } catch (err) {
    console.error(err);
  }
};

Para ver los procesos de oferta/respuesta y de intercambio de candidatos en acción, consulta simpl.info RTCPeerConnection y observa el registro de la consola para ver un ejemplo de chat de video de una sola página. Si quieres obtener más información, descarga un volcado completo de la señalización y las estadísticas de WebRTC desde la página about://webrtc-internals en Google Chrome o la página opera://webrtc-internals en Opera.

Descubrimiento de pares

Esta es una forma elegante de preguntar: "¿Cómo encuentro a alguien con quien hablar?".

Para las llamadas telefónicas, tienes números de teléfono y directorios. Para el chat y la mensajería de video en línea, necesitas sistemas de administración de identidad y presencia, y un medio para que los usuarios inicien sesiones. Las apps de WebRTC necesitan una forma para que los clientes se indiquen entre sí que quieren iniciar o unirse a una llamada.

WebRTC no define los mecanismos de detección de pares, por lo que no se abordan las opciones aquí. El proceso puede ser tan simple como enviar un correo electrónico o un mensaje con una URL. En el caso de las apps de chat de video, como Talky, tawk.to y Browser Meeting, debes compartir un vínculo personalizado para invitar a las personas a una llamada. El desarrollador Chris Ball creó un intrigante experimento serverless-webrtc que permite a los participantes de llamadas de WebRTC intercambiar metadatos a través de cualquier servicio de mensajería que deseen, como mensajería instantánea, correo electrónico o palomas mensajeras.

¿Cómo puedes crear un servicio de señalización?

Para reiterar, los estándares de WebRTC no definen los protocolos ni los mecanismos de señalización. Cualquiera que sea tu elección, necesitas un servidor intermedio para intercambiar mensajes de señalización y datos de la app entre los clientes. Lamentablemente, una app web no puede simplemente gritar en Internet: "¡Conéctame con mi amigo!".

Afortunadamente, los mensajes de señalización son pequeños y, en su mayoría, se intercambian al comienzo de una llamada. En las pruebas con appr.tc para una sesión de chat de video, el servicio de señalización controló un total de entre 30 y 45 mensajes, con un tamaño total de alrededor de 10 KB para todos los mensajes.

Además de ser relativamente poco exigentes en términos de ancho de banda, los servicios de señalización de WebRTC no consumen mucho procesamiento ni memoria, ya que solo necesitan retransmitir mensajes y conservar una pequeña cantidad de datos de estado de la sesión, como qué clientes están conectados.

Envía mensajes push del servidor al cliente

Un servicio de mensajería para la señalización debe ser bidireccional: del cliente al servidor y del servidor al cliente. La comunicación bidireccional va en contra del modelo de solicitud/respuesta cliente/servidor HTTP, pero se han desarrollado varios trucos, como el sondeo largo, a lo largo de muchos años para enviar datos desde un servicio que se ejecuta en un servidor web a una app web que se ejecuta en un navegador.

Más recientemente, la API de EventSource se implementó ampliamente. Esto habilita los eventos enviados por el servidor, es decir, los datos que se envían desde un servidor web a un cliente del navegador a través de HTTP. EventSource está diseñado para la mensajería unidireccional, pero se puede usar en combinación con XHR para crear un servicio de intercambio de mensajes de señalización. Un servicio de señalización pasa un mensaje de un llamador, que se entrega a través de una solicitud de XHR, enviándolo a través de EventSource al destinatario de la llamada.

WebSocket es una solución más natural, diseñada para la comunicación dúplex completa entre el cliente y el servidor: mensajes que pueden fluir en ambas direcciones al mismo tiempo. Una ventaja de un servicio de señalización compilado con WebSocket puro o eventos enviados por el servidor (EventSource) es que el backend de estas APIs se puede implementar en una variedad de frameworks web comunes a la mayoría de los paquetes de alojamiento web para lenguajes como PHP, Python y Ruby.

Todos los navegadores modernos, excepto Opera Mini, admiten WebSocket y, lo que es más importante, todos los navegadores que admiten WebRTC también admiten WebSocket, tanto en computadoras como en dispositivos móviles. Se debe usar TLS para todas las conexiones y garantizar que los mensajes no se puedan interceptar sin encriptar, además de reducir los problemas con el recorrido del proxy. (Para obtener más información sobre WebSocket y el recorrido de proxy, consulta el capítulo de WebRTC en High Performance Browser Networking de Ilya Grigorik).

También es posible controlar la señalización haciendo que los clientes de WebRTC sondeen un servidor de mensajería repetidamente a través de Ajax, pero esto genera muchas solicitudes de red redundantes, lo que es especialmente problemático para los dispositivos móviles. Incluso después de que se establece una sesión, los pares deben sondear los mensajes de señalización en caso de cambios o finalización de la sesión por parte de otros pares. El ejemplo de la app WebRTC Book toma esta opción con algunas optimizaciones para la frecuencia de sondeo.

Indicadores de escala

Si bien un servicio de señalización consume relativamente poco ancho de banda y CPU por cliente, es posible que los servidores de señalización de una app popular deban controlar una gran cantidad de mensajes de diferentes ubicaciones con altos niveles de simultaneidad. Las apps de WebRTC que reciben mucho tráfico necesitan servidores de señalización capaces de controlar una carga considerable. Aquí no se profundiza en el tema, pero existen varias opciones para enviar mensajes de alto rendimiento y gran volumen, incluidas las siguientes:

  • Protocolo extensible de mensajería y presencia (XMPP), originalmente conocido como Jabber, un protocolo desarrollado para la mensajería instantánea que se puede usar para la señalización (las implementaciones del servidor incluyen ejabberd y Openfire. Los clientes de JavaScript, como Strophe.js, usan BOSH para emular la transmisión bidireccional, pero, por varios motivos, BOSH puede no ser tan eficiente como WebSocket y, por los mismos motivos, puede no escalar bien). (Como nota aparte, Jingle es una extensión de XMPP para habilitar la voz y el video. El proyecto de WebRTC usa componentes de red y transporte de la biblioteca libjingle, una implementación de Jingle en C++.

  • Bibliotecas de código abierto, como ZeroMQ (que TokBox usa para su servicio Rumour) y OpenMQ (NullMQ aplica conceptos de ZeroMQ a plataformas web con el protocolo STOMP a través de WebSocket).

  • Plataformas comerciales de mensajería en la nube que usan WebSocket (aunque pueden recurrir a la votación larga), como Pusher, Kaazing y PubNub (PubNub también tiene una API para WebRTC).

  • Plataformas comerciales de WebRTC, como vLine

(La Guía de tecnologías web en tiempo real del desarrollador Phil Leggetter proporciona una lista completa de servicios y bibliotecas de mensajería).

Compila un servicio de señalización con Socket.io en Node

A continuación, se muestra el código de una aplicación web simple que usa un servicio de señalización compilado con Socket.io en Node. El diseño de Socket.IO facilita la creación de un servicio para intercambiar mensajes, y Socket.IO es especialmente adecuado para la señalización de WebRTC debido a su concepto integrado de salas. Este ejemplo no está diseñado para escalar como un servicio de señalización de nivel de producción, pero es fácil de entender para una cantidad relativamente pequeña de usuarios.

Socket.io usa WebSocket con alternativas: sondeo largo de AJAX, transmisión multipartita de AJAX, Forever Iframe y sondeo JSONP. Se ha portado a varios back-ends, pero quizás sea más conocida por su versión de Node que se usa en este ejemplo.

En este ejemplo, no se usa WebRTC. Está diseñada solo para mostrar cómo incorporar la señalización en una app web. Consulta el registro de la consola para ver qué sucede cuando los clientes se unen a una sala y se intercambian mensajes. En este codelab de WebRTC, se proporcionan instrucciones paso a paso para integrar esta función en una app de chat de video de WebRTC completa.

Este es el cliente index.html:

<!DOCTYPE html>
<html>
  <head>
    <title>WebRTC client</title>
  </head>
  <body>
    <script src='/socket.io/socket.io.js'></script>
    <script src='js/main.js'></script>
  </body>
</html>

Este es el archivo JavaScript main.js al que se hace referencia en el cliente:

const isInitiator;

room = prompt('Enter room name:');

const socket = io.connect();

if (room !== '') {
  console.log('Joining room ' + room);
  socket.emit('create or join', room);
}

socket.on('full', (room) => {
  console.log('Room ' + room + ' is full');
});

socket.on('empty', (room) => {
  isInitiator = true;
  console.log('Room ' + room + ' is empty');
});

socket.on('join', (room) => {
  console.log('Making request to join room ' + room);
  console.log('You are the initiator!');
});

socket.on('log', (array) => {
  console.log.apply(console, array);
});

Esta es la app de servidor completa:

const static = require('node-static');
const http = require('http');
const file = new(static.Server)();
const app = http.createServer(function (req, res) {
  file.serve(req, res);
}).listen(2013);

const io = require('socket.io').listen(app);

io.sockets.on('connection', (socket) => {

  // Convenience function to log server messages to the client
  function log(){
    const array = ['>>> Message from server: '];
    for (const i = 0; i < arguments.length; i++) {
      array.push(arguments[i]);
    }
      socket.emit('log', array);
  }

  socket.on('message', (message) => {
    log('Got message:', message);
    // For a real app, would be room only (not broadcast)
    socket.broadcast.emit('message', message);
  });

  socket.on('create or join', (room) => {
    const numClients = io.sockets.clients(room).length;

    log('Room ' + room + ' has ' + numClients + ' client(s)');
    log('Request to create or join room ' + room);

    if (numClients === 0){
      socket.join(room);
      socket.emit('created', room);
    } else if (numClients === 1) {
      io.sockets.in(room).emit('join', room);
      socket.join(room);
      socket.emit('joined', room);
    } else { // max two clients
      socket.emit('full', room);
    }
    socket.emit('emit(): client ' + socket.id +
      ' joined room ' + room);
    socket.broadcast.emit('broadcast(): client ' + socket.id +
      ' joined room ' + room);

  });

});

(No es necesario que aprendas sobre node-static para esto. Solo se usa en este ejemplo.

Para ejecutar esta app en localhost, debes tener instalados Node, Socket.IO y node-static. Puedes descargar Node desde Node.js (la instalación es sencilla y rápida). Para instalar Socket.IO y node-static, ejecuta Node Package Manager desde una terminal en el directorio de tu app:

npm install socket.io
npm install node-static

Para iniciar el servidor, ejecuta el siguiente comando desde una terminal en el directorio de tu app:

node server.js

En el navegador, abre localhost:2013. Abre una nueva pestaña o ventana en cualquier navegador y vuelve a abrir localhost:2013. Para ver qué sucede, consulta la consola. En Chrome y Opera, puedes acceder a la consola a través de las Herramientas para desarrolladores de Google Chrome con Ctrl+Shift+J (o Command+Option+J en Mac).

Cualquiera sea el enfoque que elijas para la señalización, tu backend y tu app cliente, como mínimo, deben proporcionar servicios similares a los de este ejemplo.

Problemas potenciales de señalización

  • RTCPeerConnection no comenzará a recopilar candidatos hasta que se llame a setLocalDescription(). Esto se exige en el borrador de JSEP del IETF.
  • Aprovecha Trickle ICE. Llama a addIceCandidate() en cuanto lleguen los candidatos.

Servidores de señalización listos para usar

Si no quieres crear tu propio servidor de señalización, hay varios servidores de señalización de WebRTC disponibles que usan Socket.IO, como en el ejemplo anterior, y que están integrados en las bibliotecas de JavaScript del cliente de WebRTC:

  • webRTC.io es una de las primeras bibliotecas de abstracción para WebRTC.
  • Signalmaster es un servidor de señalización creado para usarse con la biblioteca cliente de JavaScript SimpleWebRTC.

Si no quieres escribir ningún código, hay plataformas comerciales completas de WebRTC disponibles en empresas como vLine, OpenTok y Asterisk.

Para que quede registrado, Ericsson creó un servidor de señalización con PHP en Apache en los primeros días de WebRTC. Ahora está algo obsoleto, pero vale la pena consultar el código si estás considerando algo similar.

Seguridad de la señalización

"La seguridad es el arte de hacer que no suceda nada".

Salman Rushdie

La encriptación es obligatoria para todos los componentes de WebRTC.

Sin embargo, los estándares de WebRTC no definen los mecanismos de señalización, por lo que depende de ti garantizar la seguridad de la señalización. Si un atacante logra secuestrar la señalización, puede detener sesiones, redireccionar conexiones y grabar, alterar o insertar contenido.

El factor más importante para proteger la señalización es usar protocolos seguros (HTTPS y WSS, por ejemplo, TLS) que garanticen que los mensajes no se puedan interceptar sin encriptar. Además, ten cuidado de no transmitir mensajes de señalización de manera que otros llamantes puedan acceder a ellos a través del mismo servidor de señalización.

Después de la señalización: Usa ICE para hacer frente a NAT y firewalls

Para la señalización de metadatos, las apps de WebRTC usan un servidor intermediario, pero para la transmisión real de contenido multimedia y datos una vez que se establece una sesión, RTCPeerConnection intenta conectar clientes directamente o de par a par.

En un mundo más simple, cada extremo de WebRTC tendría una dirección única que podría intercambiar con otros pares para comunicarse directamente.

Conexión simple de punto a punto
Un mundo sin NAT ni firewalls

En realidad, la mayoría de los dispositivos se encuentran detrás de una o más capas de NAT, algunos tienen software antivirus que bloquea ciertos puertos y protocolos, y muchos están detrás de proxies y firewalls corporativos. De hecho, el mismo dispositivo, como un router Wi-Fi doméstico, puede implementar un firewall y una NAT.

Peers detrás de NAT y firewalls
El mundo real

Las apps de WebRTC pueden usar el marco de trabajo ICE para superar las complejidades de las redes del mundo real. Para que esto suceda, tu app debe pasar las URLs del servidor ICE a RTCPeerConnection, como se describe en este artículo.

ICE intenta encontrar la mejor ruta para conectar pares. Intenta todas las posibilidades en paralelo y elige la opción más eficiente que funcione. Primero, ICE intenta establecer una conexión con la dirección del host obtenida del sistema operativo y la tarjeta de red de un dispositivo. Si eso falla (lo que sucederá para los dispositivos detrás de NAT), ICE obtiene una dirección externa con un servidor STUN y, si eso falla, el tráfico se enruta a través de un servidor de retransmisión TURN.

En otras palabras, se usa un servidor STUN para obtener una dirección de red externa y se usan servidores TURN para retransmitir el tráfico si falla la conexión directa (peer-to-peer).

Todos los servidores TURN admiten STUN. Un servidor TURN es un servidor STUN con funcionalidad de retransmisión integrada adicional. ICE también se encarga de las complejidades de la configuración de NAT. En realidad, la perforación de NAT puede requerir más que solo una dirección IP:puerto pública.

Una app de WebRTC especifica (de forma opcional) las URLs de los servidores STUN o TURN en el objeto de configuración iceServers, que es el primer argumento del constructor RTCPeerConnection. En el caso de appr.tc, ese valor se ve de la siguiente manera:

{
  'iceServers': [
    {
      'urls': 'stun:stun.l.google.com:19302'
    },
    {
      'urls': 'turn:192.158.29.39:3478?transport=udp',
      'credential': 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',
      'username': '28224511:1379330808'
    },
    {
      'urls': 'turn:192.158.29.39:3478?transport=tcp',
      'credential': 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',
      'username': '28224511:1379330808'
    }
  ]
}

Una vez que RTCPeerConnection tiene esa información, la magia de ICE sucede automáticamente. RTCPeerConnection usa el framework de ICE para determinar la mejor ruta entre pares, y trabaja con servidores STUN y TURN según sea necesario.

STUN

Las NAT proporcionan a un dispositivo una dirección IP para usar dentro de una red local privada, pero esta dirección no se puede usar de forma externa. Sin una dirección pública, no hay forma de que los pares de WebRTC se comuniquen. Para solucionar este problema, WebRTC usa STUN.

Los servidores STUN se encuentran en Internet pública y tienen una tarea simple: verificar la dirección IP:puerto de una solicitud entrante (de una app que se ejecuta detrás de una NAT) y enviar esa dirección como respuesta. En otras palabras, la app usa un servidor STUN para descubrir su IP:puerto desde una perspectiva pública. Este proceso permite que un par de WebRTC obtenga una dirección de acceso público para sí mismo y, luego, se la pase a otro par a través de un mecanismo de señalización para configurar un vínculo directo. (En la práctica, los diferentes NAT funcionan de diferentes maneras y puede haber varias capas de NAT, pero el principio sigue siendo el mismo).

Los servidores STUN no tienen que hacer mucho ni recordar mucho, por lo que los servidores STUN de especificaciones relativamente bajas pueden controlar una gran cantidad de solicitudes.

La mayoría de las llamadas de WebRTC establecen una conexión correctamente con STUN (el 86% según Webrtcstats.com), aunque este porcentaje puede ser menor en el caso de las llamadas entre pares detrás de firewalls y configuraciones NAT complejas.

Conexión punto a punto con un servidor STUN
Uso de servidores STUN para obtener direcciones IP públicas y puertos

TURN

RTCPeerConnection intenta establecer una comunicación directa entre pares a través de UDP. Si eso falla, RTCPeerConnection recurre a TCP. Si eso falla, se pueden usar los servidores TURN como alternativa para retransmitir datos entre los extremos.

Solo para reiterar, TURN se usa para retransmitir audio, video y transmisión de datos entre pares, no para señalizar datos.

Los servidores TURN tienen direcciones públicas, por lo que los pares pueden comunicarse con ellos incluso si están detrás de firewalls o proxies. Los servidores TURN tienen una tarea conceptualmente simple: retransmitir un flujo. Sin embargo, a diferencia de los servidores STUN, consumen mucho ancho de banda. En otras palabras, los servidores TURN deben ser más potentes.

Conexión punto a punto con un servidor STUN
El paquete completo: STUN, TURN y señalización

En este diagrama, se muestra TURN en acción. STUN puro no tuvo éxito, por lo que cada par recurre al uso de un servidor TURN.

Implementa servidores STUN y TURN

Para las pruebas, Google ejecuta un servidor STUN público, stun.l.google.com:19302, como el que usa appr.tc. Para un servicio STUN/TURN de producción, usa rfc5766-turn-server. El código fuente de los servidores STUN y TURN está disponible en GitHub, donde también puedes encontrar vínculos a varias fuentes de información sobre la instalación de servidores. También hay disponible una imagen de VM para Amazon Web Services.

Como alternativa, puedes usar el servidor TURN restund, que está disponible como código fuente y también para AWS. A continuación, se incluyen instrucciones para configurar restund en Compute Engine.

  1. Abre el firewall según sea necesario para tcp=443 y udp/tcp=3478.
  2. Crea cuatro instancias, una para cada IP pública, con la imagen estándar de Ubuntu 12.06.
  3. Configura el firewall local (permite CUALQUIER elemento de CUALQUIER origen).
  4. Instala las herramientas: shell sudo apt-get install make sudo apt-get install gcc
  5. Instala libre desde creytiv.com/re.html.
  6. Recupera restund de creytiv.com/restund.html y descomprímelo.
  7. wget hancke.name/restund-auth.patch y aplícalo con patch -p1 < restund-auth.patch.
  8. Ejecuta make, sudo make install para libre y restund.
  9. Adapta restund.conf a tus necesidades (reemplaza las direcciones IP y asegúrate de que contenga el mismo secreto compartido) y cópialo en /etc.
  10. Copia restund/etc/restund en /etc/init.d/.
  11. Configura restund:
    1. Establece LD_LIBRARY_PATH.
    2. Copia restund.conf en /etc/restund.conf.
    3. Establece restund.conf para usar el valor correcto de 10. dirección IP.
  12. Ejecuta restund
  13. Prueba con el cliente de stund desde una máquina remota: ./client IP:port

Más allá de la comunicación uno a uno: WebRTC para varias partes

También puedes consultar el estándar IETF propuesto por Justin Uberti para una API de REST para acceder a los servicios de TURN.

Es fácil imaginar casos de uso para la transmisión de contenido multimedia que van más allá de una simple llamada uno a uno. Por ejemplo, una videoconferencia entre un grupo de colegas o un evento público con un orador y cientos o millones de espectadores.

Una app de WebRTC puede usar varias RTCPeerConnections para que cada extremo se conecte con todos los demás extremos en una configuración de malla. Este es el enfoque que adoptan apps como talky.io, y funciona muy bien para una pequeña cantidad de pares. Además, el procesamiento y el consumo de ancho de banda se vuelven excesivos, en especial para los clientes de dispositivos móviles.

Malla: Llamada pequeña de N participantes
Topología de malla completa: Todos conectados con todos

Como alternativa, una app de WebRTC podría elegir un extremo para distribuir transmisiones a todos los demás en una configuración en estrella. También sería posible ejecutar un extremo de WebRTC en un servidor y construir tu propio mecanismo de redistribución (webrtc.org proporciona una app cliente de ejemplo).

Desde Chrome 31 y Opera 18, se puede usar un MediaStream de un RTCPeerConnection como entrada para otro. Esto puede habilitar arquitecturas más flexibles, ya que permite que una app web controle el enrutamiento de llamadas eligiendo a qué otro par conectarse. Para ver esto en acción, consulta Ejemplos de WebRTC: retransmisión de conexión entre pares y Ejemplos de WebRTC: varias conexiones entre pares.

Unidad de control multipunto

Una mejor opción para una gran cantidad de extremos es usar una unidad de control multipunto (MCU). Es un servidor que funciona como puente para distribuir contenido multimedia entre una gran cantidad de participantes. Las MCU pueden procesar diferentes resoluciones, códecs y velocidades de fotogramas en una videoconferencia, controlar la transcodificación, reenviar transmisiones de forma selectiva y mezclar o grabar audio y video. En el caso de las llamadas multipartidarias, hay varios problemas que se deben tener en cuenta, en especial cómo mostrar varias entradas de video y mezclar el audio de varias fuentes. Las plataformas en la nube, como vLine, también intentan optimizar el enrutamiento del tráfico.

Puedes comprar un paquete de hardware de MCU completo o crear el tuyo.

Vista posterior de la MCU5300 de Cisco
La parte posterior de una MCU de Cisco

Existen varias opciones de software de MCU de código abierto. Por ejemplo, Licode (antes conocida como Lynckia) produce una MCU de código abierto para WebRTC. OpenTok tiene Mantis.

Más allá de los navegadores: VoIP, teléfonos y mensajería

La naturaleza estandarizada de WebRTC permite establecer comunicación entre una app de WebRTC que se ejecuta en un navegador y un dispositivo o una plataforma que se ejecuta en otra plataforma de comunicación, como un teléfono o un sistema de videoconferencias.

SIP es un protocolo de señalización que utilizan los sistemas de VoIP y de videoconferencias. Para habilitar la comunicación entre una app web de WebRTC y un cliente SIP, como un sistema de videoconferencias, WebRTC necesita un servidor proxy para mediar en la señalización. La señalización debe fluir a través de la puerta de enlace, pero, una vez que se establece la comunicación, el tráfico de SRTP (video y audio) puede fluir directamente de par a par.

La red telefónica conmutada (RTC) es la red de conmutación de circuitos de todos los teléfonos analógicos "comunes". En el caso de las llamadas entre teléfonos y apps web de WebRTC, el tráfico debe pasar por una puerta de enlace de PSTN. Del mismo modo, las apps web de WebRTC necesitan un servidor XMPP intermedio para comunicarse con los extremos de Jingle, como los clientes de MI. Google desarrolló Jingle como una extensión de XMPP para habilitar la voz y el video en los servicios de mensajería. Las implementaciones actuales de WebRTC se basan en la biblioteca libjingle de C++, una implementación de Jingle desarrollada inicialmente para Talk.

Una serie de apps, bibliotecas y plataformas aprovechan la capacidad de WebRTC para comunicarse con el mundo exterior:

  • sipML5: Un cliente SIP de JavaScript de código abierto
  • jsSIP: Biblioteca de SIP en JavaScript
  • Phono: API de teléfono de JavaScript de código abierto compilada como un complemento
  • Zingaya: Un widget de teléfono incorporable
  • Twilio: voz y mensajería
  • Uberconference: Conferencias

Los desarrolladores de sipML5 también crearon la puerta de enlace webrtc2sip. Tethr y Tropo demostraron un marco de trabajo para las comunicaciones en caso de desastre "en un maletín" con una celda OpenBTS para habilitar las comunicaciones entre teléfonos básicos y computadoras a través de WebRTC. Es comunicación telefónica sin operador.

Más información

El codelab de WebRTC proporciona instrucciones paso a paso para compilar una app de chat de texto y video con un servicio de señalización de Socket.io que se ejecuta en Node.

Presentación de Google I/O sobre WebRTC de 2013 con el líder técnico de WebRTC, Justin Uberti

Presentación de Chris Wilson en SFHTML5: Introduction to WebRTC Apps (Introducción a las apps de WebRTC)

El libro de 350 páginas WebRTC: APIs and RTCWEB Protocols of the HTML5 Real-Time Web proporciona muchos detalles sobre las rutas de datos y señalización, y también incluye varios diagramas detallados de la topología de red.

WebRTC and Signaling: What Two Years Has Taught Us: Entrada de blog de TokBox sobre por qué fue una buena idea dejar la señalización fuera de la especificación

A Practical Guide to Building WebRTC Apps de Ben Strong proporciona mucha información sobre las topologías y la infraestructura de WebRTC.

El capítulo sobre WebRTC en High Performance Browser Networking de Ilya Grigorik profundiza en la arquitectura, los casos de uso y el rendimiento de WebRTC.