Compila los servicios de backend necesarios para una app de WebRTC

¿Qué es la señalización?

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

  • Mensajes de control de sesiones utilizados 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 usados para establecer conexiones seguras
  • Datos de red, como la dirección IP y el puerto de un host tal como los ve el mundo exterior

Este proceso de señalización necesita un método para que los clientes transmitan y reciban mensajes. Las APIs de WebRTC no implementan ese mecanismo. Debes construirlo tú mismo. Más adelante en este artículo, aprenderás a crear un servicio de señalización. Primero, sin embargo, 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 protocolos de señalización. El protocolo de establecimiento de sesiones de JavaScript (JSEP) describe este enfoque:

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

Diagrama de la arquitectura de JSEP
Arquitectura de JSEP

JSEP requiere el intercambio entre pares de offer y answer, los metadatos de contenido multimedia mencionados anteriormente. Las ofertas y respuestas se comunican en el formato de protocolo de descripción de sesión (SDP), que es similar a lo siguiente:

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 trago de SDP? Consulta los ejemplos del Grupo de trabajo de ingeniería de Internet (IETF).

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

Indicadores y API de RTCPeerConnection: 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:

  • Determinación de las condiciones de los medios locales, como la resolución y las capacidades de códecs. Estos son los metadatos que se usan para el mecanismo de oferta y respuesta.
  • Obtén posibles direcciones de red para el host de la app, conocidas como candidatos.

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

Imagina que Alicia intenta llamar a Eva. Este es el mecanismo completo de oferta y respuesta en todos sus detalles sangrientos:

  1. Alicia crea un objeto RTCPeerConnection.
  2. Alicia crea una oferta (una descripción de la sesión de SDP) con el método RTCPeerConnection createOffer().
  3. Alicia llama a setLocalDescription() con su oferta.
  4. Alicia encadena la oferta y usa un mecanismo de señalización para enviarla a Eva.
  5. Eva llama a setRemoteDescription() con la oferta de Alicia para que su RTCPeerConnection esté al tanto de la configuración de Alicia.
  6. Eva llama a createAnswer(), y la devolución de llamada de éxito correspondiente a esto recibe una descripción de la sesión local (respuesta de Eva).
  7. Eva establece su respuesta como descripción local llamando a setLocalDescription().
  8. Luego, Eva usa el mecanismo de señalización para enviar su respuesta en cadena a Alicia.
  9. Alicia configura la respuesta de Eva como la descripción de la sesión remota mediante setRemoteDescription().

Alicia y Eva también deben intercambiar información de red. La expresión “encontrar candidatos” hace referencia al proceso de encontrar interfaces de red y puertos con el framework ICE.

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

JSEP admite el truco de candidatos de ICE, que permite que el emisor proporcione de forma incremental candidatos al destinatario después de la oferta inicial para que este comience a actuar en la llamada y establezca una conexión sin esperar a que lleguen todos los candidatos.

Código WebRTC para señalización

El siguiente fragmento de código es un ejemplo de código W3C que resume todo el proceso de señalización. El código asume la existencia de algún mecanismo de señalización, SignalingChannel. Analizaremos las señales con más detalle más adelante.

// 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 e intercambio de candidatos en acción, consulta simpl.info RTCPeerConnection y revisa el registro de la consola para ver un ejemplo de videochat 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 en la página opera://webrtc-internals en Opera.

Descubrimiento de pares

Es una forma sofisticada de preguntar: "¿Cómo puedo encontrar a alguien con quien hablar?".

Para las llamadas telefónicas, tienes números de teléfono y directorios. Para el videochat y la mensajería 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 un método para que los clientes se indiquen entre sí que quieren iniciar una llamada o unirse a ella.

WebRTC no define los mecanismos de detección de pares, y no se mostrarán las opciones aquí. El proceso puede ser tan sencillo como enviar un correo electrónico o enviar un mensaje a una URL. En el caso de las apps de videochat, como Talky, tawk.to y Reunión del navegador, puedes compartir un vínculo personalizado para invitar a otras 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 les guste, como mensajería instantánea, correo electrónico o palomas homónimas.

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

Para reiterar, los estándares de WebRTC no definen los protocolos y mecanismos de señalización. Independientemente de la opción que elijas, necesitas un servidor intermediario para intercambiar mensajes de señalización y datos de app entre los clientes. Lamentablemente, una aplicación web no puede simplemente gritarle a Internet: “¡Conéctame a mi amigo!”.

Afortunadamente, los mensajes de señalización son pequeños y se intercambian principalmente al inicio de una llamada. En las pruebas con appr.tc para una sesión de videochat, el servicio de señalización manejó 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 o memoria porque solo necesitan retransmitir mensajes y retener una pequeña cantidad de datos de estado de sesión, como qué clientes están conectados.

Envía mensajes del servidor al cliente

Un servicio de mensajes para señalización debe ser bidireccional: cliente a servidor y servidor a cliente. La comunicación bidireccional va en contra del modelo de solicitud/respuesta de cliente/servidor HTTP, pero a lo largo de los años se han desarrollado varios hackeos, como los sondeos largos, para enviar datos de un servicio que se ejecuta en un servidor web a una app web que se ejecuta en un navegador.

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

WebSocket es una solución más natural, diseñada para una comunicación dúplex completa entre cliente y servidor; mensajes que pueden fluir en ambas direcciones al mismo tiempo. Una de las ventajas 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 hosting 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 de escritorio como en dispositivos móviles. Se debe usar TLS para todas las conexiones a fin de garantizar que los mensajes no se intercepten sin encriptar y también para reducir los problemas con el cruce de proxies. Para obtener más información sobre WebSocket y el recorrido del 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 WebRTC consulten un servidor de mensajería repetidamente a través de Ajax, pero eso genera muchas solicitudes de red redundantes, lo que es particularmente problemático para los dispositivos móviles. Incluso después de que se haya establecido una sesión, los pares deben sondear en busca de mensajes indicadores en caso de cambios o finalización de la sesión por parte de otros pares. En el ejemplo de la app de WebRTC Book, se usa esta opción con algunas optimizaciones para la frecuencia de sondeo.

Señalización de báscula

Aunque un servicio de señalización consume relativamente poco ancho de banda y CPU por cliente, los servidores de señalización para una app popular pueden tener que manejar muchos mensajes desde diferentes ubicaciones con altos niveles de simultaneidad. Las aplicaciones de WebRTC que reciben mucho tráfico necesitan servidores de señalización que puedan manejar una carga considerable. Aquí no entras en detalle, pero hay una serie de opciones para mensajería de alto volumen y rendimiento, incluidas las siguientes:

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

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

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

  • Plataformas comerciales de WebRTC, como vLine

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

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

El siguiente es el código de una app web simple que usa un servicio de señalización compilado con Socket.io en Node. El diseño de Socket.io facilita la compilación de un servicio para intercambiar mensajes, y Socket.io es particularmente adecuado para la señalización de WebRTC debido a su concepto integrado de habitaciones. 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 un número relativamente pequeño de usuarios.

Socket.io usa WebSocket con resguardos: sondeos largos AJAX, transmisión de varias partes de AJAX, Iframe de Forever y sondeo de JSONP. Se transfirió a varios backends, pero es quizás más conocido por la versión de nodo que se usa en este ejemplo.

No hay WebRTC en este ejemplo. Solo está diseñado para mostrar cómo integrar indicadores en una app web. Consulta el registro de la consola para ver qué sucede cuando los clientes se unen a una sala e intercambian mensajes. En este codelab de WebRTC, se brindan instrucciones paso a paso para integrar esta función en una app de videochat 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 necesitas aprender sobre el nodo estático para esto. Simplemente se usa en este ejemplo).

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

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 pestaña o ventana nueva en cualquier navegador y vuelve a abrir localhost:2013. Para ver qué sucede, consulta la consola. En Chrome y Opera, puede 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).

Sin importar el enfoque que elijas para la señalización, tu backend y tu app cliente, al menos, deben proporcionar servicios similares a los de este ejemplo.

Problemas de los indicadores

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

Servidores de señalización listos para usar

Si no quieres implementar el tuyo, hay varios servidores de señalización de WebRTC disponibles, que utilizan Socket.IO como el ejemplo anterior y están integrados en las bibliotecas cliente de JavaScript 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 disponibles plataformas comerciales WebRTC completas de empresas, como vLine, OpenTok y Asterisk.

Hay constancia de que Ericsson compiló un servidor de señalización mediante PHP en Apache en los primeros días de WebRTC. Esto es un poco obsoleto, pero vale la pena mirar el código si consideras 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 hacer que la señalización sea segura. Si un atacante logra apropiarse de los indicadores, puede detener sesiones, redireccionar conexiones y grabar, alterar o inyectar contenido.

El factor más importante para asegurar la señalización es usar protocolos seguros, HTTPS y WSS (por ejemplo, TLS), que garantizan que los mensajes no se puedan interceptar sin encriptar. Además, ten cuidado de no transmitir mensajes de señalización de modo que otros emisores puedan acceder a ellos con el mismo servidor de señalización.

Después de la señalización: Usa ICE para lidiar con las NAT y los firewalls

Para la indicación de metadatos, las apps de WebRTC usan un servidor intermedio, pero, una vez que se establece una sesión y la transmisión de datos y contenido multimedia real, RTCPeerConnection intenta conectar los clientes de forma directa o entre pares.

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

Conexión sencilla entre pares
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, por ejemplo, un router Wi-Fi doméstico, puede implementar un firewall y NAT.

Pares detrás de NAT y firewalls
El mundo real

Las apps de WebRTC pueden usar el framework ICE para superar las complejidades de las herramientas de redes del mundo real. Para permitir 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 a los pares. Prueba 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 que se obtuvo del sistema operativo y la tarjeta de red de un dispositivo. Si eso falla (lo que sucederá con los dispositivos detrás de NAT), ICE obtiene una dirección externa mediante un servidor STUN y, si 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 los servidores TURN se usan para retransmitir el tráfico si falla la conexión directa (entre pares).

Todos los servidores TURN son compatibles con STUN. Un servidor TURN es un servidor STUN con funcionalidad de retransmisión incorporada adicional. ICE también aborda las complejidades de las configuraciones de NAT. En realidad, la perforación de agujeros de NAT puede requerir más que solo una dirección IP:puerto pública.

Una app de WebRTC especifica las URLs para los servidores STUN o TURN (opcional) 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 tenga esa información, el comando mágico de ICE ocurrirá automáticamente. RTCPeerConnection usa el framework de ICE para determinar la mejor ruta entre pares y trabaja con los servidores STUN y TURN según sea necesario.

IMPACTO

Las NAT proporcionan una dirección IP a un dispositivo para su uso dentro de una red local privada, pero esta no se puede usar externamente. Sin una dirección pública, los pares de WebRTC no pueden comunicarse. Para solucionar este problema, WebRTC usa STUN.

Los servidores STUN viven en la Internet pública y tienen una tarea simple: verificar la dirección IP:port 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í misma y, luego, la pase a otro par mediante un mecanismo de señalización para configurar un vínculo directo. (En la práctica, las distintas 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 especificación relativamente baja pueden manejar una gran cantidad de solicitudes.

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

Conexión entre pares mediante un servidor STUN
Usa servidores STUN para obtener direcciones IP:puertos públicas

TURN

RTCPeerConnection intenta configurar la comunicación directa entre intercambios de tráfico a través de UDP. Si eso falla, RTCPeerConnection recurre a TCP. Si eso falla, los servidores TURN se pueden utilizar como resguardo, retransmitiendo datos entre endpoints.

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

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

Conexión entre pares mediante un servidor STUN
El Monty 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 a un servidor TURN.

Implementa servidores STUN y TURN

Para realizar pruebas, Google ejecuta un servidor STUN público, stun.l.google.com:19302, que usa appr.tc. Para un servicio STUN/TURN de producción, usa rfc5766-turn-server. El código fuente para 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 está disponible una imagen de VM para Amazon Web Services.

Un servidor TURN alternativo es RESTund, disponible como código fuente, y también para AWS. Aquí encontrarás instrucciones para configurar RESTund en Compute Engine.

  1. Abre el firewall según sea necesario para tcp=443, udp/tcp=3478.
  2. Crea cuatro instancias, una para cada IP pública, imagen de Ubuntu 12.06 estándar.
  3. Establece la configuración del firewall local (permite CUALQUIERA desde CUALQUIERA).
  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 desde creytiv.com/restund.html y desempaqueta./
  7. wget hancke.name/restund-auth.patch y aplica 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 el resto:
    1. Establece LD_LIBRARY_PATH.
    2. Copia restund.conf en /etc/restund.conf.
    3. Configura restund.conf para usar la 10 correcta. dirección IP.
  12. Ejecutar RESTund
  13. Probar con un cliente Stund desde una máquina remota: ./client IP:port

Más allá de uno a uno: WebRTC de varias partes

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

Son fáciles de imaginar casos de uso para la transmisión de contenido multimedia que trascienden una simple llamada uno a uno. Por ejemplo, videoconferencias entre un grupo de colegas o un evento público con un orador y cientos o millones de usuarios.

Una aplicación de WebRTC puede usar varias RTCPeerConnections para que cada extremo se conecte al resto de los endpoints de una configuración de malla. Este es el enfoque que adoptan las apps, como talky.io, y funciona muy bien para unos pocos pares. Más allá de eso, el procesamiento y el consumo de ancho de banda se vuelven excesivos, en especial para los clientes de dispositivos móviles.

Malla: llamada N-vía pequeña
Topología de malla completa: Todos se conectan a todos

De forma alternativa, una aplicación WebRTC podría elegir un extremo para distribuir las 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 aplicación cliente de muestra).

A partir de Chrome 31 y Opera 18, se puede utilizar un MediaStream de un RTCPeerConnection como entrada para otro. De esta manera, se pueden habilitar arquitecturas más flexibles, ya que permite que una app web maneje el enrutamiento de llamadas eligiendo a qué otro intercambio de tráfico conectarse. Para ver esto en acción, consulta Retransmisión de conexiones de intercambio de tráfico de muestras de WebRTC y Muestras de WebRTC de varias conexiones de intercambio de tráfico.

Unidad de control multipunto

Una mejor opción para una gran cantidad de extremos es usar una Unidad de control de varios puntos (MCU). Se trata de un servidor que funciona como puente para distribuir contenido multimedia entre una gran cantidad de participantes. Las MCU pueden lidiar con diferentes resoluciones, códecs y velocidades de fotogramas en una videoconferencia, controlar la transcodificación, reenviar transmisiones selectivas y mezclar o grabar audio y video. En el caso de las llamadas entre varias partes, debes considerar una serie de cuestiones, en particular, cómo mostrar varias entradas de video y mezclar audio de varias fuentes. Las plataformas de nube, como vLine, también intentan optimizar el enrutamiento del tráfico.

Es posible comprar un paquete de hardware de MCU completo o crear el tuyo propio.

Vista posterior del Cisco MCU5300
La parte posterior de un MCU de Cisco

Hay varias opciones de software de MCU de código abierto disponibles. Por ejemplo, Licode (antes conocido como Lynckia) produce un 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 la comunicación entre una aplicación 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 usan los sistemas de videoconferencia y VoIP. Para habilitar la comunicación entre una app web WebRTC y un cliente SIP, como un sistema de videoconferencias, WebRTC necesita un servidor proxy para mediar 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 SRTP (video y audio) puede fluir directamente entre pares.

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

Varias 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 SIP de JavaScript
  • Phono: API de JavaScript de código abierto compilada como complemento
  • Zingaya: Un widget de teléfono incorporable
  • Twilio: Voz y mensajería
  • Uberconference: Conferencia

Los desarrolladores de sipML5 también compilaron la puerta de enlace webrtc2sip. Tethr y Tropo demostraron un framework para comunicaciones ante desastres "en un maletín" con una celda de OpenBTS para permitir la comunicación entre teléfonos de gama media y computadoras mediante WebRTC. Eso sería comunicación telefónica sin un operador.

Más información

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

Presentación de WebRTC de Google I/O de 2013 con Justin Uberti, líder de tecnología de WebRTC

Presentación de SFHTML5 de Chris Wilson: Introducción a las apps de WebRTC

En el libro de 350 páginas WebRTC: APIs and RTCWEB Protocols of HTML5 Real-Time Web (API en tiempo real de HTML5), encontrarás una gran cantidad de detalles sobre los datos y las rutas de señalización, además de una serie de diagramas detallados de la topología de red.

WebRTC and Signaling: What Two Years Has Taught Us: Entrada de blog de TokBox en la que se explica por qué no se recomienda dejar las señales fuera de la especificación

La guía práctica para compilar apps de WebRTC de Ben Strong proporciona mucha información sobre la infraestructura y las topologías de WebRTC.

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