CUSL

  • ¿Por qué una implementación del P2PSP para el Navegador?

    Motivación

    En la actualidad millones de personas se conectan diariamente a Internet para consumir contenido multimedia, y la mayoría de estas personas lo hace mediante dispositivos móviles. Cada vez son más las compañias que ofrecen servicios de acceso a Internet con tarifas reducidas, y todas ellas tienen algo en común: se tarifica por bits descargados sin tener en cuenta la cantidad de información enviada a la red. Esta capacidad está siendo desaprovechada por los actuales sistemas comerciales de streaming de vídeo, porque en estos, toda la carga de la red recae sobre el servidor, de modo que el servidor tiene que servir repetidamente el mismo contenido a todos los clientes que lo están solicitando al mismo tiempo.

    Como mejora al modelo cliente-servidor para streaming tradicional, surge la tecnología P2P (Peer-To-Peer) y el protocolo P2PSP (P2P Straightforward Protocol) es un ejemplo concreto. Gracias al P2PSP, unido a otras tecnologías, podemos confeccionar clientes de streaming P2P que aprovechen ese ancho de banda que es desperdiciado, enviando una copia de su contenido al resto de clientes. Así conseguimos que todos los clientes compartan tanto como reciben a la par que liberamos al servidor de una gran carga computacional y de transmisión, de forma que estamos ante un sistema de streaming escalable capaz de soportar un mayor número de usuarios. Por otra parte, hacer un sistema capaz de ser ejecutado en múltiples plataformas, sobre todo en dispositivos móviles, sería un avance en el campo de la transmisión de vídeo por Internet, ya que permitiría a cualquier usuario independientemente de la plataforma conectarse a un clúster P2PSP para comenzar a recibir y compartir contenido multimedia. 

    P2PSP + WEBRTC

    ¿Por qué el Navegador Web?

    El navegador Web es una herramienta indispensable en cualquier equipo porque es la forma más común de acceder a la información disponible en Internet para un usuario. Esto se debe a que el navegador es fácil de usar, y para acceder a un recurso es suficiente con recordar el nombre de dominio donde se aloja o llegar hasta él mediante motores de búsqueda disponibles en la red.

    Por otra parte, HTML5 es una opción ideal para implementar un cliente P2PSP. Una implementación en HTML5 permitiría a cualquier usuario conectarse a un cluster del P2PSP y comenzar a recibir (y compartir) el contenido multimedia. HTML5 incluye la etiqueta <video> para incrustar el contenido multimedia, pero esto no es suficiente para ejecutar un cliente P2PSP, para ello necesitamos hacer uso de protocolos de transporte ligeros. HTML5 nos brinda la oportunidad de usar sockets TCP para permitir una comunicación bidireccional entre el navegador y el servidor en distintos puertos. Sin embargo, existe un inconveniente en esta solución ya que la transmisión de bloques de vídeo necesitamos realizarla sobre UDP entre los navegadores. Por suerte, este problema podemos  solventarlo haciendo uso de una nueva API que ahora mismo está en proceso de estandarización por la W3C y la IETF. Concretamente, dicha API, que se denomina WebRTC, es ideal para nosotros porque define un recurso llamado interface DataChannel que permite establecer un canal de datos bidireccional entre los navegadores mediante socket sobre UDP. 

  • Chat Multiusuario con WebRTC

    Para entender un poco mejor como vamos a conseguir transmitir vídeo en el navegador mediante P2P sin necesidad de plugins gracias a WebRTC, primero necesitamos entender cómo funcionan los elementos básicos de la API que nos permitirán hacer esto, hablamos de RTCPeerConnection y RTCDataChannel. Para ello, vamos a construir un pequeño ejemplo de un chat multiusuario con el objetivo de entender como trabajar con la API WebRTC.

    El chat estará compuesto por los siguientes elementos:

    • Peer: Es la parte del cliente, cada usuario ejecutará un peer en su navegador. El peer lo vamos a escribir en HTML5+JavaScript+WebRTC
    • Server (SignalingServer): Es la parte servidora, su misión es simple, hacer que los peer se conozcan entre sí. Sólo eso, los mensajes nunca pasarán por el servidor, estos serán intercambiados directamente entre los peers. El Server lo vamos a escribir en Python.

    Nota: Si todo esto te suena a chino te recomiendo que eches un vistazo a WebRTC Comunicación en tiempo real sin plugins antes de seguir leyendo.

    ¡Manos a la obra!

    El Server (SignalingServer)

    Para que los clientes (peer) se conozca entre si es necesario hacer uso de un servidor de señalización que nos de la información necesaria de cada uno para poder establecer una comunicación directa entre ellos. Necesitamos una forma de comunicarnos con los peer que se están ejecutando en el navegador de cada usuario, algo que nos permita intercambiar la información entre el servidor y cada uno de los clientes. La forma más sencilla y directa es usar WebSocket, para ello haremos uso de la siguiente librería SimpleWebSocketServer by opiate, es software libre y nos facilita su implementación en Python.

    Partiendo del módulo sólo tenemos que reescribir los meéodos handleMessage, handleConnected y handleClose para que hagan lo que nosotros búscamos, en este caso es muy sencillo, intercambiar la información de cada peer con el resto de peers.

    #hanleMessage se ejecuta cuando un mensaje es recibido
    def handleMessage(self):
          #recibimos un mensaje (json)
        datos=str(self.data) 
     
            #decodificamos el mensaje (json)
            try:
            decoded = json.loads(datos) 
        except (ValueError, KeyError, TypeError):
            print "JSON format error"
     
            #Reenviamos el mensaje a resto de clientes
        for client in self.server.connections.itervalues():
                if client != self:
                  try:
                    client.sendMessage(str(self.data))
                  except Exception as n:
                      print n
     
    #handleConnected se ejecuta cuando un nuevo cliente se conecta
    def handleConnected(self):
        global nextid
     
        try:
              #enviamos al cliente su id de peer
          self.sendMessage(str('{"numpeer":"'+str(nextid)+'"}'))
                #enviamos al cliente la lista de peer actual
          self.sendMessage(str('{"peerlist":"'+str(peerlist)+'"}'))
                #agregamos el nuevo peer a la lista
          peerlist.append(nextid)
          peeridlist[self]=nextid
          nextid=nextid+1
        except Exception as n:
          print n
     
    #handleClose se ejecuta cuando un cliente se desconecta
    def handleClose(self):
          #eliminamos el peer de la lista
        peerlist.remove(peeridlist[self]);

    Nota: El código completo del SignalingServer está actualizado y disponible en Launchpad -> P2PSP > Experimentos > ChatMultiusuario > Server.py

    El Peer

    Como ya hemos dicho, la parte del cliente se ejecuta en el navegador y toda la funcionalidad vamos a escribirla en JavaScript. A continuación sólo vamos a comentar algunas partes del código que son interesantes. Recuerda que puedes acceder al código fuente completo de este experimento en Launchpad -> WebRTCMultiPeerChat

    Primero definimos el SignalingServer que será la URL donde se está ejecutando Server.py y configuration para RTCPeerConnection que será el servidor STUN que se encarga de proporcionarnos la información de red externa (IP, Puerto, etc).

    var signalingChannel = new WebSocket("ws://127.0.0.1:9876/");
    var configuration = {iceServers: [{ url: 'stun:stun.l.google.com:19302' }]}

    A continuación se muestra el resto del código necesario para el peer, está comentado para una comprensión más sencilla:

    // Inicializamos las conexión
    // isInitiator = true o false
    // i= id del peer
    function start(isInitiator,i) {
         //Inicializamos RTCPeerConnection para el peer i.
      pcs[i] = new webkitRTCPeerConnection(configuration, {optional: []});
     
     
      // Enviar cualquier ICE candidate a los otros peer.
      pcs[i].onicecandidate = function (evt) {
        if (evt.candidate){
          signalingChannel.send(JSON.stringify({ "candidate": evt.candidate , "idtransmitter":'"'+idpeer+'"', "idreceiver":'"'+i+'"'}));
        }
      };
     
     
      // dejar a "negotiationneeded" generar ofertas (Offer)
      pcs[i].onnegotiationneeded = function () {
        pcs[i].createOffer(function(desc){localDescCreated(desc,pcs[i],i);});
        console.log("Create and send OFFER");
      }
     
      if (isInitiator) {
        // Crear el datachannel para ese peer
        channel[i] = pcs[i].createDataChannel("chat"+i);
        setupChat(i);
      } else {
          // Establecer el datachannel para ese peer
        pcs[i].ondatachannel = function (evt) {
          channel[i] = evt.channel;
          setupChat(i);
        };
      }  
      console.log("Saved in slot: "+i+" PeerConection: "+pcs[i]);
    }
     
    //Establecer localDescriction y y enviarla a los otros peer (ellos la estableceran como remoteDescription)
    function localDescCreated(desc,pc,i) {
        pc.setLocalDescription(desc, function () {
      console.log("localDescription is Set");
            signalingChannel.send(JSON.stringify({ "sdp": pc.localDescription , "idtransmitter":'"'+idpeer+'"', "idreceiver":'"'+i+'"'}));
        }, logError);
    }
     
    //Se ejecuta cuando se recibe un mensaje desde el signalingChannel (WebSocket)
    signalingChannel.onmessage = function (evt) {
      handleMessage(evt);
    }
     
    //Manipular el mensaje
    function handleMessage(evt){
      var message = JSON.parse(evt.data);
     
        //Si es el ide del peer se almacena
        if (message.numpeer){    
        idpeer=message.numpeer;
        console.log('Peer ID: '+idpeer);
        return;    
        }  
     
        //Si es la lista de peer se almacena
      if (message.peerlist){    
        console.log('Peer List '+message.peerlist);
        peerlist=JSON.parse(message.peerlist);
        for (i in peerlist){
          console.log("Peer: "+peerlist[i]);
        }
        return;    
        }  
     
        //guardamos el id del que envia el mensaje y el id del que debe recibirlo
        var id=(message.idtransmitter).split('"').join(''); 
      var idreceiver=(message.idreceiver).split('"').join(''); 
        console.log("Received from: "+id+" and send to: "+idreceiver);
     
        //Si es nuevo para este peer se configura la RTCPeerConection y se añade a la lista de peer.
        if (!pcs[id]) { 
        console.log('%cCreate a new PeerConection','background: #222; color: #bada55');
        peerlist.push(id);
        console.log("PEER LIST UPDATE: "+peerlist);
        start(false,id);
        }   
     
        //Si el mensaje va dirigido a mi y es SDP (informacion de la sesion)
        if (message.sdp &amp;&amp; idreceiver==idpeer){
            //Estableco la información de la conexión remota (remoteDescription)
        pcs[id].setRemoteDescription(new RTCSessionDescription(message.sdp), function () {
          console.log("remoteDescription is Set");
            // Si recibimos una oferta enviamos una respuesta.
          if (pcs[id].remoteDescription.type == "offer"){
            console.log("Create and send ANSWER");
                pcs[id].createAnswer(function(desc){localDescCreated(desc,pcs[id],id);});
            }
            });
        }
     
        //Si el mensaje va dirigido a mi y es un ICE candidate
        if (message.candidate &amp;&amp; idreceiver==idpeer){
            //agrego el candidato a la lista de candidatos de ese peer.
        console.log("Received ice candidate: "+ message.candidate.candidate); 
        pcs[id].addIceCandidate(new RTCIceCandidate(message.candidate));
        }
     
    }
     
    //Configuracion del chat
    function setupChat(i) {
        //Cuando se abra el dataChannel con ese peer se habilita el boton enviar.
        channel[i].onopen = function () {
            btnSend.disabled=false;
      document.getElementById("chatcontrols").style.display="inline";
        };
     
        //Cuando se reciba un mensaje por DataChannel (WebRTC) se muestra el mensaje.
        channel[i].onmessage = function (evt) {
           document.getElementById("receive").innerHTML+="<br />"+evt.data;
        };
    }
     
    //Enviar un mensaje por DataChannel (al resto de peer)
    function sendChatMessage() {
        document.getElementById("receive").innerHTML+="<br />"+document.getElementById("login").value+ ": "+msg.value;
      //Para cada peer de la lista...
        for (i in peerlist){  
        if (peerlist[i]!=idpeer){
          console.log("send to "+peerlist[i]);  
                //Se envial el mensaje con el nombre de usuario y el texto a enviar.
          try{
            channel[peerlist[i]].send(document.getElementById("login").value+ ": "+msg.value);
          }catch(e){
            console.log(i+" said bye!");
          }
     
        }
      }
    }

    Nota: El código completo del Peer está actualizado y disponible en Launchpad -> P2PSP > Experimentos > ChatMultiusuario > Peer.js

    Una versión de este experimento está disponible para probarlo en http://www.p2psp.org/chat. Si al introducir un nickname aparece el mensaje "Conecting..."  pero no aparece el cuadro de texto con el botón "send" es posible que no haya ningún otro peer (o esté detrás de un NAT simétrico), para comprobar que funciona correctamente puedes abrir otra vez la misma URL en otra pestaña de tu navegador o incluso en otro equipo de tu red.

    Si tienes cualquier duda o sugerencia, usa los comentarios :-)

    Este experimento, por el momento, sólo funciona en Google Chrome (incluido Chrome for Android)

  • Entendiendo el protocolo P2PSP

    Llevo unas semanas desconectado del proyecto por los exámenes, aunque he dedicado algún tiempo a probar WebRTC y buscar la forma de adaptar el P2PSP para conseguir implementarlo con éxito en el navegador. Lo primero, entender como funciona el protocolo.

    El Protocolo P2PSP es un conjunto de reglas de transmisión y comportamiento que ayuda a aumentar la calidad de servicio en los sistemas de transmisión de streaming en tiempo real. El protocolo está diseñado por módulos, cada uno de ellos está definido por un conjunto de reglas que provee diferentes funcionalidades. Los módulos que forman el P2PSP son: Data Broadcasting Set (DBS) of rules, Lost chunks Recovery Set (LRS) of rules, Adaptive Chunk-rate Set (ACS) of rules, End-point Masquerading Set (EMS) of rules, NAT Traversal Set (NTS) of rules, Multi-Channel Set (MCS) of rules, Data Integrity Set (DIS) of rules, Data Privacy Set (DPS) of rules. 

    El módulo principal e indispensable para el funcionamiento básico del protocolo es el DBS, el resto son opcionales, por lo tanto, nos centraremos en implementar este módulo en WebRTC para conseguir streaming de vídeo en tiempo real en el navegador.

    Las entidades que serán necesarias para el funcionamiento del módulo DBS son:

    • Source: Es el que produce el stream que será transmitido a la red P2PSP. Normalmente es un servidor de video HTTP como Icecast
    • Player: Es el encargado de decodificar y reproducir el stream.
    • Splitter: Esta entidad recibe el stream desde el Source, lo divide en trozos del mismo tamaño y envía los trozos a los peers.
    • Peer: Recibe los trozos desde el splitter y desde otros peers, ensambla el stream y lo envia al Player

    Sin embargo, para la versión en WebRTC debemos tener en cuenta que el Player y el Peer estarán incrustados en el navegador Web y el Splitter además de su función en la red P2PSP hará también la función de Signaling Server de WebRTC

    Un posible escenario del P2PSP implementado con WebRTC se muestra a continuación:

    Escenario WebRTC+P2PSP

    Nótese que en la imagen anterior el player forma parte del Client+Peer y el Source se ha divido en Streaming Server, que hace la función del Source original descrito en el White Paper del protocolo P2PSP y en el Video Source, que es un dispositivo encargado de capturar el vídeo y enviarlo al Streaming Server.

    El conjunto de reglas necesario para implementar el módulo DBS está descrito en la Web oficial del protocolo en la sección The Data Broadcasting Set of rules

  • Fase Final del 8 CUSL

    Los días 15 y 16 de Mayo visité Sevilla,  allí se celebraba la fase final del Concurso Universitario de Software Libre y el proyecto "implementación del protocolo P2PSP usando WebRTC" era uno de los 6 proyectos finalistas. Fueron dos días fantásticos, conocí gente nueva que compartía conmigo la pasión por la programación y por el software libre. Además de la exposición de los proyectos de cada participante hubo otras charlas. La organización del concurso llevó a grandes profesionales para presentarnos otros proyectos de la comunidad y contarnos cosas muy interesantes relacionadas con el Open Source. Hasta se organizó un editatón de Wikipedia en el que todos los participantes ayudamos a mejorarla. Realmente fueron dos días bastante intensos.

    La sorpresa llegó el último día en la entrega de premios, la implementación del P2PSP para el navegador fue galardonada con el premio principal, le otorgaron el Premio especial de la comunidad del 8º Concurso Universitario de Software Libre. Para mi es un orgullo este reconocimiento y desde aquí quiero agradecer a todos los que de una forma u otra están implicados en este proyecto, entre ellos, mi tutor del proyecto el profesor Vicente González, la comunidad del P2PSP, el grupo SAL de la Universidad de Almería y la gente de Luxunda. Gracias también al concurso por este reconocimiento.

    Entrega Premio especia de la comunidad octavo concurso universitario de software libre.

    (En la foto: Jose María Ortíz Silva de Technosite-Fundación ONCE entregándome el premio)

  • Streaming de vídeo entre navegadores vía WebRTC Datachannel y Media Source Extensions

    WebRTC Streaming: Chrome to ChromeHoy vamos a llevar a cabo un experimento que nos permitirá enviar un vídeo en streaming entre uno o varios navegadores Web. En este ejemplo con WebRTC, la aplicación Web nos permitirá cargar un fichero de vídeo en el navegador, dividirlo en bloques, enviar los bloques a otros navegadores y reproducir el vídeo en todos ellos. ¡Todo en tiempo real!

     

  • Streaming tradicional vs Streaming P2P

    Cuando accedemos al contenido multimedia en Internet, la mayoría de las veces lo hacemos mediante Streaming, esto nos permite visualizar el contenido a medida que lo estamos descargando. Portales tan famosos como YouTube o Vimeo nos ofrecen contenido en Streaming -algunos incluso en directo- usando el modelo cliente-servidor, en este tipo de servicios el servidor envía una copia del vídeo por cada cliente que está consumiendo el contenido en ese momento, esto provoca una sobrecarga en el servidor que lo hace muy poco escalable necesitando de mucha potencia de procesamiento y un gran ancho de banda en el lado servidor. El concepto IPTV es muy similar a esto, la diferencia radica en que usan redes privadas con un gran ancho de banda reservado para este fin.

    Streaming Cliente Servidor [IPTV]

  • WebRTC, P2PSP y el CUSL

    Concurso Universitario de Software LibreFue en mayo de 2012 cuando empecé a aportar mi granito de arena a la comunidad de software libre.  Por aquel entonces, el profesor de redes de computadores de la Universidad, Vicente González, decidió que la mejor forma de entender los socket era entrando de lleno con ellos. Nos animó a colaborar en un proyecto de software libre que aún se estaba gestando, el protocolo P2PSP. Para mí aquello fue una gran oportunidad, podría colaborar en un proyecto de software libre, poner en práctica lo aprendido en clase, aprender y divertirme al mismo tiempo. Por supuesto no lo dudé un instante y solicité formar parte del P2PSP Team en launchpad. Después de 18 meses, 58 revisiones, cambios de nombre, varias correcciones de bug y algunos cambios significativos en el protocolo, el P2PSP es una realidad, incluso tiene su propia web: www.p2psp.org. Como en cualquier proyecto de software libre que se precie, seguimos trabajando para mejorarlo entre todos y nos encantaría que te unieras al proyecto. Aún queda mucho por hacer.

    Alguno estará pensando, ¿A qué viene esto ahora?, la respuesta es sencilla: Me presento al concurso universitario de software libre (CUSL). Para los que aún no lo conozcan, es un concurso de desarrollo de software, hardware y documentación técnica libre en el que puede participar cualquier estudiante, el objetivo es contribuir a la consolidación de la comunidad de software libre en la universidad. Para el concurso he decidido implementar el protocolo P2PSP con WebRTC, de forma que se ejecute íntegramente en el navegador Web sin necesidad de plugins.

    A partir de ahora escribiré una serie de post dedicados a informar sobre todo lo que rodea al proyecto. Estarán accesibles desde el Blog o directamente en Mi participación en el CUSL, todos los post se podrán encontrar por la etiqueta CUSL. Ya sólo queda desear suerte a todos los participantes. ¡A disfrutar!