Compila servicios de backend de apps de WebRTC

Publicado: 4 de noviembre de 2013

WebRTC habilita la comunicación entre pares. Requiere servidores para que los clientes puedan intercambiar metadatos y coordinar la comunicación a través de un proceso llamado señalización. Los servidores también ayudan a los clientes a lidiar con los traductores de direcciones de red (NAT) y los firewalls.

Aquí, aprenderás a compilar un servicio de señalización y a lidiar con las peculiaridades de la conectividad del mundo real con servidores STUN y TURN. También comprenderás cómo las apps de WebRTC pueden controlar llamadas de varias partes y, además, interactuar con servicios, como VoIP y PSTN (también conocidos como teléfonos).

¿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 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, según los ve el mundo 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. En su lugar, debes compilarlo por tu cuenta.

¿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 y protocolos de señalización. Este enfoque se describe en el Protocolo de establecimiento de sesiones de JavaScript (JSEP):

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 vuelve 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

JSEP requiere el intercambio entre pares de oferta y respuesta, los metadatos de contenido multimedia. Las ofertas y respuestas se comunican en el formato del 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 toda esta jerga 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 la respuesta se puedan ajustar antes de establecerse como la descripción local o remota editando los valores en el texto de 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 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:

  • Determinar las condiciones de los medios locales, como las capacidades de resolución y códec Estos 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. Este es el mecanismo de respuesta completo 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. Alicia llama a setLocalDescription() con su oferta.
  4. Alicia convierte la oferta en una cadena y usa un mecanismo de señalización para enviársela a Eva.
  5. Eva llama a setRemoteDescription() con la oferta de Alicia para que su RTCPeerConnection conozca la configuración de Alicia.
  6. Eve llama a createAnswer() y se pasa una descripción de la sesión local (la respuesta de Eve) a la devolución de llamada de éxito para esto.
  7. Eva establece su respuesta como la descripción local llamando a setLocalDescription().
  8. Luego, Eve usa el mecanismo de señalización para enviar su respuesta en forma de 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 necesitan intercambiar información de red. La expresión "búsqueda de candidatos" se refiere al proceso de encontrar interfaces de red y puertos con el framework de 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 de candidatos en formato de cadena 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 goteo de candidatos ICE, que permite que la persona que llama proporcione candidatos de forma incremental a la persona que recibe la llamada después de la oferta inicial, y que la persona que recibe la llamada 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 del 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. La señalización se analiza 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 y de intercambio de candidatos en acción, visita simpl.info RTCPeerConnection y consulta el registro de la consola para ver un ejemplo de videochat de una sola página. Puedes descargar un volcado completo de las estadísticas y la señalización 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 los mensajes y el videochat 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 una llamada o unirse a una.

WebRTC no define los mecanismos de descubrimiento de pares, y no se detallan 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 otras personas a una llamada compartiendo un vínculo personalizado. El desarrollador Chris Ball creó un interesante experimento de 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 IM, correo electrónico o paloma mensajera.

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

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

Afortunadamente, 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 videochat, el servicio de señalización controló un total de alrededor de 30 a 45 mensajes con un tamaño total para 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, ya que 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 del servidor al cliente

Browser Support

  • Chrome: 6.
  • Edge: 79.
  • Firefox: 6.
  • Safari: 5.

Source

Un servicio de mensajes para la señalización debe ser bidireccional: de cliente a servidor y de servidor a cliente. La comunicación bidireccional va en contra del modelo de solicitud/respuesta del cliente/servidor HTTP, pero se desarrollaron varios trucos, como el sondeo largo, durante 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.

La API de EventSource habilita los eventos enviados por el servidor, que son datos que se envían desde 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 que intercambie 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 receptor.

Browser Support

  • Chrome: 5.
  • Edge: 12.
  • Firefox: 11.
  • Safari: 5.

Source

WebSocket está diseñado para la comunicación dúplex completo entre cliente y servidor, lo que significa que los mensajes pueden fluir en ambas direcciones al mismo tiempo. Una ventaja de un servicio de señalización creado 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 para la mayoría de los paquetes de hosting web para lenguajes como PHP, Python y Ruby.

Todos los navegadores que admiten WebRTC también admiten WebSocket. 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 del proxy. Para obtener más información sobre WebSocket y el recorrido de proxy, lee el capítulo de WebRTC en Herramientas de redes de alto rendimiento para navegadores de Ilya Grigorik.

Puedes controlar la señalización haciendo que los clientes de WebRTC sondeen un servidor de mensajería de forma repetida 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 haya establecido una sesión, los pares deben sondear los mensajes de señalización en caso de que se produzcan cambios o que otros pares finalicen la sesión. La app de ejemplo WebRTC Book usa esta opción con algunas optimizaciones para la frecuencia de sondeo.

Indicador de escalamiento

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 capaces de controlar una carga considerable. No entras en detalles aquí, pero hay varias opciones para la mensajería de alto volumen y alto rendimiento, incluidas las siguientes:

  • El protocolo extensible de mensajería y presencia (XMPP) es un protocolo desarrollado para la mensajería instantánea que se puede usar para la señalización. Las implementaciones de servidor incluyen ejabberd y Openfire. Los clientes de JavaScript, como Strophe.js, usan BOSH para emular la transmisión bidireccional, pero BOSH puede ser ineficiente y escalar de forma deficiente, en comparación con WebSocket.

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

  • Plataformas comerciales de mensajería en la nube que usan WebSocket (aunque pueden recurrir al sondeo largo), como Pusher y Kaazing.

  • Plataformas comerciales de WebRTC, como vLine

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

A continuación, se muestra el código de una app web 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 especialmente adecuado para la señalización de WebRTC debido a su concepto integrado de salas. Este ejemplo está diseñado para una cantidad relativamente pequeña de usuarios, en lugar de un uso en producción.

Socket.io usa WebSocket con resguardos: sondeo largo de AJAX, transmisión de varias partes de AJAX, Forever Iframe y sondeo de JSONP. Se portó a varios backends, pero quizás sea más conocida por su versión de Node que se usa en este ejemplo.

En este ejemplo, no hay WebRTC. Está diseñada 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 esto 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);

  });

});

Para ejecutar esta app en localhost, debes tener Node.js, Socket.IO y node-static. Para instalar Socket.IO y node-static, ejecuta npm 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).

Independientemente del enfoque que elijas para la señalización, tu backend y tu app cliente deben proporcionar servicios similares a los de este ejemplo.

Problemas potenciales 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() 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 se integran con 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 de empresas como vLine, OpenTok y Asterisk.

Para que conste, 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 Signal

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 proteger 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, como TLS. Esto garantiza 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.

Lidiar con 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 a los 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 par a par
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, un firewall y una NAT se pueden implementar en el mismo dispositivo, como un router Wi-Fi doméstico.

Pares detrás de NAT y firewalls
El mundo real

Las apps de WebRTC pueden usar el framework de ICE para superar las complejidades de las redes del mundo real. Para habilitar esta función, tu app debe pasar las URLs del servidor ICE a RTCPeerConnection.

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. ICE primero intenta establecer una conexión con la dirección de host obtenida 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 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 (de par a par).

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

Las URLs de los servidores STUN y TURN se especifican (de forma opcional) en una app de WebRTC en el objeto de configuración iceServers, que es el primer argumento del constructor RTCPeerConnection. 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 tiene esa información, la magia de ICE ocurre automáticamente. RTCPeerConnection usa el framework de ICE para determinar la mejor ruta entre pares, trabajando 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 la Internet pública y tienen una tarea: verificar la dirección IP:puerto de una solicitud entrante (desde 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.

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 con éxito usando STUN, aunque puede haber menos llamadas entre pares detrás de firewalls y configuraciones de NAT complejas.

Conexión de par a par con un servidor STUN
Uso de servidores STUN para obtener direcciones IP:puerto públicas

TURN

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

Para reiterar, TURN se usa para retransmitir la transmisión de audio, video y datos entre pares, no para los 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: retransmitir un flujo. A diferencia de los servidores STUN, consumen mucho ancho de banda de forma inherente.

Conexión de par a par con un servidor STUN
The full Monty: STUN, TURN, and signaling

En este diagrama, se muestra TURN en acción. La conexión STUN pura no se estableció, por lo que cada par recurre al uso de un servidor TURN.

Implementa servidores STUN y TURN

Para realizar 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 el 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. Puedes configurar un restund en Compute :

  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. Establece la configuración del firewall local (permite CUALQUIER tráfico desde 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 10 correcto. una dirección IP externa.
  12. Ejecuta restund
  13. Prueba con el cliente stund desde la máquina remota: ./client IP:port

WebRTC de varias partes

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

Existen numerosos casos de uso para la transmisión de contenido multimedia que van más allá de una llamada individual. Por ejemplo, una videoconferencia 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. Más allá de eso, el consumo de procesamiento y ancho de banda se vuelve excesivo, en especial para los clientes 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 de 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).

Un MediaStream de un RTCPeerConnection se puede usar 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 Retransmisión de conexión de pares de muestras de WebRTC y Múltiples conexiones de pares de muestras de WebRTC.

Unidad de control multipunto

Una mejor opción para una gran cantidad de extremos es usar una unidad de control multipunto (MCU). Este es un servidor que funciona como un puente para distribuir contenido multimedia entre una gran cantidad de participantes. Las MCU pueden manejar diferentes resoluciones, códecs y velocidades de fotogramas en una videoconferencia, controlar la transcodificación, realizar el reenvío selectivo de transmisiones y mezclar o grabar audio y video. En el caso de las llamadas con varios participantes, hay varios problemas que se deben tener en cuenta, en especial, 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.

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

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

Existen varias opciones de software de MCU de código abierto. 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 la 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 videoconferencia. Para habilitar la comunicación entre una app web de WebRTC y un cliente SIP, como un sistema de videoconferencia, 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 de SRTP (video y audio) puede fluir 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 "antiguos". Para 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 intermediario para comunicarse con los extremos de Jingle, como los clientes de IM. 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 que se desarrolló inicialmente para Talk.

Muchas apps, bibliotecas y plataformas usan la capacidad de WebRTC para comunicarse con el mundo exterior. Por ejemplo:

  • jsSIP: Biblioteca de SIP de JavaScript
  • Phono: API de teléfono de JavaScript de código abierto compilada como complemento
  • Twilio: voz y mensajería

Más información

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