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 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 multimedia, como códecs, configuración de códecs, ancho de banda y tipos de contenido multimedia
  • Datos de clave que se usan 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 una forma para que los clientes pasen mensajes de ida y vuelta. Las APIs de WebRTC no implementan ese mecanismo. Debes crearlo por tu cuenta. Más adelante en este artículo, aprenderás a crear 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:

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 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 contenido multimedia mencionados anteriormente. Las ofertas y respuestas se comunican en formato de protocolo de descripción de sesión (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 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 se puedan ajustar la oferta o la respuesta antes de establecerlas 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. SDP es un poco 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 para seguir con SDP.

API y señalización 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:

  • Determina las condiciones de los medios locales, como la resolución y las capacidades del códec. 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 candidatas.

Una vez que se hayan verificado 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 Eva. Este es el mecanismo completo de oferta y respuesta en todos sus detalles:

  1. Alice crea un objeto RTCPeerConnection.
  2. Alice crea una oferta (una descripción de la sesión de SDP) con el método createOffer() de RTCPeerConnection.
  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 enviarla a Eva.
  5. Eva llama a setRemoteDescription() con la oferta de Alice para que su RTCPeerConnection sepa sobre la configuración de Alice.
  6. Eva llama a createAnswer() y a la devolución de llamada de éxito para esto se le pasa una descripción de sesión local: la respuesta de Eva.
  7. Eva llama a setLocalDescription() para establecer su respuesta como la descripción local.
  8. Luego, Eva usa el mecanismo de señalización para enviar su respuesta con formato de cadena a Alice.
  9. Alice establece la respuesta de Eva como la descripción de la sesión remota con setRemoteDescription().

Alice y Eve 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. Alice 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 de candidatos con formato de cadena a Eve a través de su canal de señalización.
  4. Cuando Eva recibe un mensaje de candidato de Alice, llama a addIceCandidate() para agregarlo a la descripción del par remoto.

JSEP admite el trickling de candidatos de ICE, que permite que el llamador 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.

Cómo codificar WebRTC para la señalización

El siguiente fragmento de código es un ejemplo de código del W3C que resume todo el proceso de señalización. El código supone la existencia de algún mecanismo de señalización, SignalingChannel. Más adelante, se analizará el uso de indicadores con más 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 videochat de una sola página. Si quieres obtener más información, descarga un volcado completo de los indicadores 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 puedo encontrar a alguien con quien hablar?".

Para las llamadas telefónicas, tienes números de teléfono y directorios. Para el videochat y los mensajes 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 descubrimiento de pares, y no debes analizar las opciones aquí. El proceso puede ser tan simple como enviar una URL por correo electrónico o mensaje. En el caso de las apps de videochat, como Talky, tawk.to y Browser Meeting, puedes invitar a las personas a una llamada compartiendo un vínculo personalizado. El desarrollador Chris Ball creó un intrigante experimento de serverless-webrtc que permite que los participantes de las llamadas de WebRTC intercambien metadatos a través de cualquier servicio de mensajería que deseen, como IM, correo electrónico o paloma mensajera.

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

Repito, los estándares de WebRTC no definen los protocolos y mecanismos de señalización. Independientemente de lo que elijas, necesitas un servidor intermediario para intercambiar mensajes de señalización y datos de apps entre los clientes. Lamentablemente, una app web no puede simplemente gritar a Internet: "¡Conéctame con mi amigo!".

Por suerte, los mensajes de señalización son pequeños y se intercambian principalmente 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 manejó un total de alrededor de 30 a 45 mensajes con un tamaño total de todos los mensajes de alrededor de 10 KB.

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 porque solo necesitan retransmitir mensajes y retener una pequeña cantidad de datos de estado de la sesión, como qué clientes están conectados.

Envía mensajes push desde el servidor al cliente

Un servicio de mensajes 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 del cliente-servidor HTTP, pero se han desarrollado varios hacks, como la solicitud de larga duración, a lo largo de muchos años para enviar datos de 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, que son datos enviados de un servidor web a un cliente de 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 compilar un servicio para intercambiar mensajes de señalización. Un servicio de señalización pasa un mensaje de un llamador, que se entrega mediante una solicitud XHR, enviándolo a través de EventSource al llamado.

WebSocket es una solución más natural, diseñada para la comunicación dúplex completa entre cliente y 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 hosting web para lenguajes como PHP, Python y Ruby.

Todos los navegadores modernos, excepto Opera Mini, son compatibles con 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 para garantizar que los mensajes no se puedan interceptar sin encriptar y también para reducir los problemas con el recorrido de 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 de forma reiterada a través de Ajax, pero eso 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 que otros pares realicen cambios o cierren la sesión. El ejemplo de la app de WebRTC Book usa esta opción con algunas optimizaciones para la frecuencia de sondeo.

Señalización de escala

Aunque 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 muchos mensajes de diferentes ubicaciones con altos niveles de simultaneidad. Las apps de WebRTC que reciben mucho tráfico necesitan servidores de señalización que puedan controlar una carga considerable. No entras en detalles, pero hay varias opciones para enviar mensajes de alto volumen y alto rendimiento, incluidas las siguientes:

  • Protocolo extensible de mensajería y presencia (XMPP), conocido originalmente como Jabber, un protocolo desarrollado para la mensajería instantánea que se puede usar 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 varios motivos, es posible que BOSH no sea tan eficiente como WebSocket y, por los mismos motivos, es posible que no se ajuste bien. (A modo de ejemplo, Jingle es una extensión de XMPP para habilitar la voz y el video. El proyecto 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 usa TokBox 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 solicitud de larga duración), 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

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 simplifica la compilación de un servicio para intercambiar mensajes, y es particularmente 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 resguardos: sondeo largo AJAX, transmisión de varias partes AJAX, iframe eterno y sondeo JSONP. Se portó a varios backends, pero quizás sea más conocido por su versión de Node que se usa en este ejemplo.

No hay WebRTC en este ejemplo. Su diseño solo tiene como objetivo mostrar cómo compilar 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 intercambian mensajes. En este codelab de WebRTC, se proporcionan instrucciones paso a paso para integrarlo en una app de videochat de WebRTC completa.

Este es el index.html del cliente:

<!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 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 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, 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).

Independientemente del enfoque que elijas para la señalización, tu backend y tu app cliente deben proporcionar, como mínimo, 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 es obligatorio en el borrador de IETF de JSEP.
  • 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, hay varios servidores de señalización de WebRTC disponibles, que usan Socket.IO como en el ejemplo anterior y están integrados con las bibliotecas de JavaScript 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, existen plataformas comerciales completas de WebRTC disponibles en empresas como vLine, OpenTok y Asterisk.

A modo de referencia, Ericsson creó un servidor de señalización con PHP en Apache en los primeros días de WebRTC. Esto ahora es un poco obsoleto, pero vale la pena mirar el código si estás considerando algo similar.

Seguridad de los indicadores

"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 que la señalización sea segura. Si un atacante logra apropiarse de 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, como 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 manera 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 controlar los NAT y los firewalls

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

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 punto a punto simple
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 se encuentran detrás de proxies y firewalls corporativos. De hecho, el mismo dispositivo puede implementar un firewall y NAT, como un router Wi-Fi doméstico.

Nodos 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 redes del mundo real. Para que esto suceda, tu app debe pasar las URLs del servidor de ICE a RTCPeerConnection, como se describe en este artículo.

ICE intenta encontrar la mejor ruta para conectar 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 obtenida del sistema operativo y la tarjeta de red de un dispositivo. Si eso falla (lo que sucederá en el caso de 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 los servidores TURN se usan para retransmitir el tráfico si falla la conexión directa (entre pares).

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 las configuraciones de NAT. En realidad, el perforado de NAT puede requerir más que solo una dirección IP pública:puerto.

Las URLs de los servidores STUN o TURN se especifican (de manera opcional) en el objeto de configuración iceServers que es el primer argumento del constructor RTCPeerConnection por una app de WebRTC. Para 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, la magia de ICE se producirá automáticamente. RTCPeerConnection usa el framework de ICE para determinar la mejor ruta entre pares y trabajar 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, los pares de WebRTC no pueden comunicarse. Para solucionar este problema, WebRTC usa STUN.

Los servidores STUN se encuentran en la 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, 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, las 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 con 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 esto puede ser menor para las llamadas entre pares detrás de firewalls y configuraciones de NAT complejas.

Conexión punto a punto con un servidor STUN
Cómo usar servidores STUN para obtener direcciones IP públicas:puertos

TURN

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

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

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 una transmisión. Sin embargo, a diferencia de los servidores STUN, consumen mucho ancho de banda de forma inherente. 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 lo 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.

Un servidor TURN alternativo es restund, disponible como código fuente y también para AWS. Estas son las 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, con la imagen estándar de Ubuntu 12.06.
  3. Configura el firewall local (permite CUALQUIER COSA desde CUALQUIER LUGAR).
  4. Instala herramientas:shell sudo apt-get install make sudo apt-get install gcc
  5. Instala libre desde creytiv.com/re.html.
  6. Obtén el reembolso 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 y 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 10 correcto. dirección IP.
  12. Ejecuta restund
  13. Prueba con el cliente de stund desde la máquina remota: ./client IP:port

Más allá de uno a uno: WebRTC multipartido

También te recomendamos que consultes 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, videoconferencias entre un grupo de colegas o un evento público con un orador y cientos o millones de usuarios.

Una app de WebRTC puede usar varias RTCPeerConnections para que cada extremo se conecte a 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 un pequeño grupo 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 vías
Topología de malla completa: Todos conectados a todos

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

A partir de Chrome 31 y Opera 18, se puede usar un MediaStream de un RTCPeerConnection como entrada para otro. Esto puede permitir arquitecturas más flexibles, ya que permite que una app web controle el enrutamiento de llamadas eligiendo con qué otro par conectarse. Para ver esto en acción, consulta WebRTC samples Peer connection relay y WebRTC samples Multiple peer connections.

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 admitir diferentes resoluciones, códecs y velocidades de fotogramas en una videoconferencia, controlar la transcodificación, realizar el reenvío de transmisiones selectivo y mezclar o grabar audio y video. En el caso de las llamadas con varias partes, hay varios problemas que se deben tener en cuenta, en particular, cómo mostrar varias entradas de video y mezclar 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 del Cisco MCU5300
La parte posterior de una MCU de Cisco

Hay varias opciones de software de MCU de código abierto disponibles. Por ejemplo, Licode (antes conocido 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 una 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 videoconferencia.

SIP es un protocolo de señalización que usan los sistemas de VoIP y 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 la señalización. La señalización debe fluir a través de la puerta de enlace, pero, una vez establecida la comunicación, el tráfico SRTP (video y audio) puede fluir directamente de igual a igual.

La red telefónica conmutada (RTC) es la red conmutada por circuitos de todos los teléfonos analógicos “convencionales”. En el caso de las llamadas entre apps web de WebRTC y teléfonos, 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.

Varias apps, bibliotecas y plataformas usan la capacidad de WebRTC para comunicarse con el mundo exterior:

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

Los desarrolladores de sipML5 también crearon la puerta de enlace webrtc2sip. Tethr y Tropo demostraron un framework para comunicaciones ante desastres "en un maletín" con una celda OpenBTS para habilitar las comunicaciones entre teléfonos básicos y computadoras a través de WebRTC. Esa es la 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 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 técnico de WebRTC

Presentación de Chris Wilson en SFHTML5: 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 los datos y las rutas de señalización, y también incluye varios diagramas detallados de topología de red.

WebRTC y señalización: lo que nos enseñaron dos años: 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.

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