
En este artículo, mostraré cómo crear una aplicación web usando Node.js, que le permite rastrear los resultados de las coincidencias de NHL en tiempo real. Los indicadores se actualizan de acuerdo con los cambios en la puntuación durante los juegos.
Realmente disfruté escribiendo este artículo, porque trabajar en él incluía dos cosas que me encantaban: desarrollo de software y deportes.
En el curso del trabajo, utilizaremos las siguientes herramientas:
- Node.js;
- Socket.io;
- MySportsFeed.com.
Si no tiene Node.js instalado, visite la página de descarga e instálelo, y luego continuaremos.
¿Qué es socket.io?
Esta es la tecnología que conecta al cliente con el servidor. En el ejemplo actual, el cliente es un navegador web y el servidor es Node.js. El servidor puede funcionar simultáneamente con varios clientes en cualquier momento.
Una vez establecida la conexión, el servidor puede enviar mensajes a todos los clientes o solo a uno de ellos. Eso, a su vez, puede enviar mensajes al servidor, proporcionando comunicación en dos direcciones.
Antes de Socket.io, las aplicaciones web generalmente se ejecutaban en AJAX. Su uso proporcionó la necesidad de que el cliente sondee el servidor y viceversa en busca de nuevos eventos. Por ejemplo, tales encuestas podrían realizarse cada 10 segundos para verificar si hay nuevos mensajes.
Esto dio una carga adicional, ya que la búsqueda de nuevos mensajes se llevó a cabo incluso cuando no estaban en absoluto.
Cuando se usa Socket.io, los mensajes se reciben en tiempo real sin la necesidad de verificar constantemente su presencia, lo que reduce la carga.
Aplicación de muestra Socket.io
Antes de comenzar a recopilar datos de competencia en tiempo real, creemos una aplicación de ejemplo para demostrar cómo funciona Socket.io.
Primero, voy a crear una aplicación Node.js. En la ventana de la consola, debe ir al directorio C: \ GitHub \ NodeJS, crear una nueva carpeta para la aplicación y en ella una nueva aplicación:
cd \GitHub\NodeJS mkdir SocketExample cd SocketExample npm init
Dejé la configuración predeterminada, puedes hacer lo mismo.
Como estamos creando una aplicación web, utilizaré un paquete NPM llamado Express para simplificar la instalación. En el símbolo del sistema, ejecute los siguientes comandos: npm install express --save.
Por supuesto, también debemos instalar el paquete Socket.io: npm install socket.io --save
Ahora necesita iniciar el servidor web. Para hacer esto, cree un nuevo archivo index.js y coloque la siguiente sección de código:
var app = require('express')(); var http = require('http').Server(app); app.get('/', function(req, res){ res.sendFile(__dirname + '/index.html'); }); http.listen(3000, function(){ console.log('HTTP server started on port 3000'); });
Si Express no le resulta familiar, entonces el ejemplo de código anterior incluye la biblioteca Express, aquí estamos creando un nuevo servidor HTTP. En el ejemplo, el servidor HTTP escucha en el puerto 3000, es decir.
localhost : 3000. La ruta va a la raíz, "/". El resultado se devuelve como un archivo HTML index.html.
Antes de crear este archivo, terminemos el inicio del servidor configurando Socket.io. Para crear un servidor Socket, ejecute los siguientes comandos:
var io = require('socket.io')(http); io.on('connection', function(socket){ console.log('Client connection received'); });
Al igual que con Express, el código comienza importando la biblioteca Socket.io. Esto se indica mediante la variable io. Luego, usando esta variable, cree un controlador de eventos con la función on. Este evento se activa cada vez que un cliente se conecta al servidor.
Ahora creemos un cliente simple. Para hacer esto, cree el archivo index.html y coloque el siguiente código dentro:
<!doctype html> <html> <head> <title>Socket.IO Example</title> </head> <body> <script src="/socket.io/socket.io.js"></script> <script> var socket = io(); </script> </body> </html>
El HTML anterior carga el cliente JavaScript Socket.io e inicializa la conexión al servidor. Para ver un ejemplo, ejecute Nodo: nodo index.js.
Luego, en el navegador, ingrese
localhost : 3000. La página permanecerá en blanco, pero si mira la consola mientras Node se está ejecutando, verá dos mensajes:
El servidor HTTP se inició en el puerto 3000
Conexión del cliente recibidaAhora que nos hemos conectado con éxito, continuemos el trabajo. Por ejemplo, envíe un mensaje al cliente desde el servidor. Luego, cuando el cliente lo reciba, se enviará un mensaje de respuesta:
io.on('connection', function(socket){ console.log('Client connection received'); socket.emit('sendToClient', { hello: 'world' }); socket.on('receivedFromClient', function (data) { console.log(data); }); });
La función io.on anterior se ha actualizado para incluir varias líneas de código nuevas. El primero, socket.emit, envía un mensaje al cliente. sendToClient es el nombre del evento. Al nombrar eventos, puede enviar diferentes tipos de mensajes, de modo que el cliente pueda interpretarlos de manera diferente. Otra actualización es socket.on, que también tiene un nombre de evento: recibidoFromClient. Todo esto crea una función que recibe datos del cliente. En este caso, también se registran en la ventana de la consola.
Los pasos completados completan la preparación del servidor. Ahora puede recibir y enviar datos desde cualquier cliente conectado.
Terminemos este ejemplo actualizando el cliente recibiendo el evento sendToClient. Cuando se recibe un evento, se recibe una respuesta de Clientes.
Esto concluye la parte de JavaScript de HTML. En index.html agregue:
var socket = io(); socket.on('sendToClient', function (data) { console.log(data); socket.emit('receivedFromClient', { my: 'data' }); });
Usando la variable de socket integrada, obtenemos una lógica similar en el servidor con la función socket.on. El cliente escucha el evento sendToClient. Tan pronto como el cliente está conectado, el servidor envía este mensaje. El cliente, al recibirlo, registra el evento en la consola del navegador. Después de eso, el cliente usa el mismo socket.emit que el servidor usado para enviar el evento original. En este caso, el cliente envía el evento FromClient recibido al servidor. Cuando recibe un mensaje, se registra en la ventana de la consola.
Pruébalo tú mismo. Primero, en la consola, inicie su aplicación Node: node index.js. Luego cargue
localhost : 3000 en el navegador.
Verifique la consola de su navegador y verá lo siguiente en los registros JSON: {hola: "mundo"}
Luego, mientras se ejecuta la aplicación Node, verá lo siguiente:
El servidor HTTP se inició en el puerto 3000
Conexión del cliente recibida
{my: 'datos'}Tanto el cliente como el servidor pueden usar datos JSON para realizar tareas específicas. Veamos cómo puede trabajar con datos de competencia en tiempo real.
Información de competencia
Una vez que comprendamos los principios de envío y recepción de datos por parte del cliente y el servidor, vale la pena intentar garantizar que las actualizaciones se realicen en tiempo real. Utilicé los datos de la competencia, aunque lo mismo se puede hacer no solo con información deportiva. Pero dado que estamos trabajando con él, necesitamos encontrar la fuente.
Servirán a
MySportsFeeds . El servicio se paga: desde $ 1 por mes, tenga en cuenta.
Una vez que su cuenta está configurada, puede comenzar con su API. Puede usar el paquete NPM para esto: npm install mysportsfeeds-node --save.
Después de instalar el paquete, conectamos la API:
var MySportsFeeds = require("mysportsfeeds-node"); var msf = new MySportsFeeds("1.2", true); msf.authenticate("********", "*********"); var today = new Date(); msf.getData('nhl', '2017-2018-regular', 'scoreboard', 'json', { fordate: today.getFullYear() + ('0' + parseInt(today.getMonth() + 1)).slice(-2) + ('0' + today.getDate()).slice(-2), force: true });
En el ejemplo anterior, reemplace mis datos con los suyos.
El código hace una llamada API para obtener los resultados de la competencia NHL de hoy. La variable fordate es lo que define la fecha. También utilicé force y true para recuperar datos, incluso si los resultados son los mismos.
Con la configuración actual, los resultados de la llamada API se escriben en un archivo de texto. En el último ejemplo, cambiaremos esto; para fines de demostración, el archivo de resultados se puede ver en un editor de texto para comprender el contenido de la respuesta. En nuestro resultado, vemos el objeto de la tabla de resultados. Este objeto contiene una matriz llamada gameScore. Guarda el resultado de cada juego. Cada objeto a su vez contiene un objeto hijo llamado juego. Este objeto proporciona información sobre quién está jugando.
Fuera del objeto del juego, hay varias variables que muestran el estado actual del juego. Los datos cambian según sus resultados. Cuando el juego aún no ha comenzado, se utilizan variables que proporcionan información sobre cuándo ocurrirá esto. Cuando el juego ha comenzado, se proporcionan datos adicionales sobre los resultados, incluida información sobre qué período es ahora y cuánto tiempo queda. Para comprender mejor lo que está en juego, pasemos a la siguiente sección.
Actualizaciones en tiempo real
Tenemos todas las piezas del rompecabezas, ¡así que vamos a armarlo! Desafortunadamente, MySportsFeeds tiene un soporte limitado para la emisión de datos, por lo que debe solicitar información constantemente. Aquí hay un punto positivo: sabemos que los datos cambian solo una vez cada 10 minutos, por lo que no es necesario sondear el servicio con demasiada frecuencia. Los datos recibidos se pueden enviar desde el servidor a todos los clientes conectados.
Para obtener los datos necesarios, utilizaré la función setInterval JavaScript, que le permite acceder a la API (en mi caso) cada 10 minutos para encontrar actualizaciones. Cuando llegan los datos, el evento se envía a todos los clientes conectados. Los resultados se actualizan a través de JavaScript en un navegador web.
MySportsFeeds también se llama cuando la aplicación Node se inicia primero. Los resultados se usarán para cualquier cliente que se conecte antes del primer intervalo de 10 minutos. La información sobre esto se almacena en una variable global. A su vez, se actualiza como parte de una encuesta de intervalos. Esto asegura que cada uno de los clientes tendrá resultados relevantes.
Para que todo esté bien en el archivo index.js principal, creé un nuevo archivo llamado data.js. Contiene una función exportada desde index.js que realiza una llamada previa a la API MySportsFeeds. Aquí está el contenido completo de este archivo:
var MySportsFeeds = require("mysportsfeeds-node"); var msf = new MySportsFeeds("1.2", true, null); msf.authenticate("*******", "******"); var today = new Date(); exports.getData = function() { return msf.getData('nhl', '2017-2018-regular', 'scoreboard', 'json', { fordate: today.getFullYear() + ('0' + parseInt(today.getMonth() + 1)).slice(-2) + ('0' + today.getDate()).slice(-2), force: true }); };
La función getData se exporta y devuelve los resultados de la llamada. Veamos lo que tenemos en el archivo index.js.
var app = require('express')(); var http = require('http').Server(app); var io = require('socket.io')(http); var data = require('./data.js');
Las primeras siete líneas de código anteriores inicializan las bibliotecas necesarias y llaman a la última variable global de datos. La última lista de bibliotecas utilizadas es: Express, Http Server, creado usando Express, Socket.io más el archivo data.js que acaba de crear.
Teniendo en cuenta las necesidades, la aplicación completa los últimos datos (latestData) para los clientes que se conectan cuando el servidor se inicia por primera vez:
Las siguientes líneas establecen la ruta para la página principal del sitio (en nuestro caso
localhost : 3000 /) e indican al servidor HTTP que escuche en el puerto 3000.
Socket.io se configura para buscar nuevas conexiones. Cuando se detectan, el servidor envía datos de eventos con el contenido de la última variable de datos.
Y finalmente, el último fragmento de código crea el intervalo de sondeo requerido. Cuando se detecta, la variable LatestData se actualiza con los resultados de la llamada API. Estos datos luego pasan el mismo evento a todos los clientes.
Como vemos, cuando un cliente se conecta y se define un evento, se emite con una variable de socket. Esto le permite enviar un evento a un cliente conectado específico. Dentro del intervalo, el io global se usa para enviar el evento. Envía datos a todos los clientes. La configuración del servidor está completa.
Como se vera
Ahora trabajemos en la interfaz del cliente. En un ejemplo anterior, creé el archivo base index.html, que establece una conexión con el cliente para registrar eventos del servidor y enviarlos. Ahora ampliaré las capacidades del archivo.
Como el servidor nos envía un objeto JSON, usaré jQuery y una extensión jQuery llamada JsRender. Esta es una biblioteca de plantillas. Me permitirá crear una plantilla HTML que se utilizará para mostrar el contenido de cada juego NHL de una manera conveniente. Ahora puede ver la amplitud de sus capacidades. El código contiene más de 40 líneas, por lo que lo dividiré en varias secciones más pequeñas y, al final, mostraré todo el contenido HTML.
Esto es lo que se usa para mostrar los datos del juego:
<script id="gameTemplate" type="text/x-jsrender"> <div class="game"> <div> {{:game.awayTeam.City}} {{:game.awayTeam.Name}} at {{:game.homeTeam.City}} {{:game.homeTeam.Name}} </div> <div> {{if isUnplayed == "true" }} Game starts at {{:game.time}} {{else isCompleted == "false"}} <div>Current Score: {{:awayScore}} - {{:homeScore}}</div> <div> {{if currentIntermission}} {{:~ordinal_suffix_of(currentIntermission)}} Intermission {{else currentPeriod}} {{:~ordinal_suffix_of(currentPeriod)}}<br/> {{:~time_left(currentPeriodSecondsRemaining)}} {{else}} 1st {{/if}} </div> {{else}} Final Score: {{:awayScore}} - {{:homeScore}} {{/if}} </div> </div> </script>
La plantilla se define usando la etiqueta de script. Contiene un identificador de plantilla y un tipo de script especial llamado text / x-jsrender. La plantilla define un contenedor div para cada juego que contiene una clase de juego para aplicar un estilo base específico. Dentro de este div está el comienzo de la plantilla.
El siguiente div muestra el equipo invitado y el equipo anfitrión. Esto se implementa combinando el nombre de la ciudad y el nombre del equipo con el objeto del juego de los datos de MySportsFeeds.
{{: game.awayTeam.City}} es cómo defino un objeto que será reemplazado por un valor físico al representar la plantilla. Esta sintaxis está definida por la biblioteca JsRender.
Cuando el juego no se juega, aparece una línea en la que el juego comienza con {{: game.time}}.
Hasta que se complete el juego, se muestra la puntuación actual: {{: awayScore}} - {{: homeScore}}. Y, finalmente, un pequeño truco que determinará cuál es el período ahora y para aclarar si hay un descanso ahora.
Si la variable currentIntermission aparece en los resultados, entonces uso la función I, definida por ordinal_suffix_of, que convierte el número de período al siguiente texto: 1st (2nd, 3rd, etc.) Break.
Cuando no hay descanso, busco el valor de currentPeriod. Ordinal_suffix_of también se usa aquí para mostrar que el juego está en el 1er (2do, 3er, etc.) períodos.
Además, otra función que definí como time_left se usa para convertir el número de segundos restantes hasta el final del período. Por ejemplo: 10:12.
La última parte del código muestra el resultado final cuando se completa el juego.
Aquí hay un ejemplo de cómo se ve cuando hay una lista mixta de juegos completados, juegos que aún no se han completado y juegos que aún no se han iniciado (no soy un muy buen diseñador, por lo que el resultado parece que debería cuando el desarrollador crea una versión personalizada interfaz de aplicación de bricolaje):

El siguiente es un fragmento de JavaScript que crea un socket, funciones auxiliares ordinal_suffix_of y time_left, y una variable que se refiere a la plantilla jQuery generada.
<script> var socket = io(); var tmpl = $.templates("#gameTemplate"); var helpers = { ordinal_suffix_of: function(i) { var j = i % 10, k = i % 100; if (j == 1 && k != 11) { return i + "st"; } if (j == 2 && k != 12) { return i + "nd"; } if (j == 3 && k != 13) { return i + "rd"; } return i + "th"; }, time_left: function(time) { var minutes = Math.floor(time / 60); var seconds = time - minutes * 60; return minutes + ':' + ('0' + seconds).slice(-2); } }; </script>
El último fragmento es el código para recibir el evento de socket y representar la plantilla:
socket.on('data', function (data) { console.log(data); $('#data').html(tmpl.render(data.scoreboard.gameScore, helpers)); });
Tengo un delimitador con una identificación de datos. El resultado del renderizado de plantilla (tmpl.render) escribe HTML en este contenedor. Lo que es realmente genial: la biblioteca JsRender puede tomar una matriz de datos, en este caso data.scoreboard.gameScore, que recorre cada elemento de la matriz y crea un juego por elemento.
Aquí está la versión final prometida anteriormente, donde se combinan HTML y JavaScript:
<!doctype html> <html> <head> <title>Socket.IO Example</title> </head> <body> <div id="data"> </div> <script id="gameTemplate" type="text/x-jsrender"> <div class="game"> <div> {{:game.awayTeam.City}} {{:game.awayTeam.Name}} at {{:game.homeTeam.City}} {{:game.homeTeam.Name}} </div> <div> {{if isUnplayed == "true" }} Game starts at {{:game.time}} {{else isCompleted == "false"}} <div>Current Score: {{:awayScore}} - {{:homeScore}}</div> <div> {{if currentIntermission}} {{:~ordinal_suffix_of(currentIntermission)}} Intermission {{else currentPeriod}} {{:~ordinal_suffix_of(currentPeriod)}}<br/> {{:~time_left(currentPeriodSecondsRemaining)}} {{else}} 1st {{/if}} </div> {{else}} Final Score: {{:awayScore}} - {{:homeScore}} {{/if}} </div> </div> </script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jsrender/0.9.90/jsrender.min.js"></script> <script src="/socket.io/socket.io.js"></script> <script> var socket = io(); var helpers = { ordinal_suffix_of: function(i) { var j = i % 10, k = i % 100; if (j == 1 && k != 11) { return i + "st"; } if (j == 2 && k != 12) { return i + "nd"; } if (j == 3 && k != 13) { return i + "rd"; } return i + "th"; }, time_left: function(time) { var minutes = Math.floor(time / 60); var seconds = time - minutes * 60; return minutes + ':' + ('0' + seconds).slice(-2); } }; var tmpl = $.templates("#gameTemplate"); socket.on('data', function (data) { console.log(data); $('#data').html(tmpl.render(data.scoreboard.gameScore, helpers)); }); </script> <style> .game { border: 1px solid #000; float: left; margin: 1%; padding: 1em; width: 25%; } </style> </body> </html>
¡Ahora es el momento de iniciar la aplicación Node y abrir
localhost : 3000 para ver el resultado!
Cada X minutos, el servidor envía un evento al cliente. El cliente, a su vez, volverá a dibujar los elementos del juego con datos actualizados. Por lo tanto, cuando deje el sitio abierto, los resultados de los juegos se actualizarán constantemente.
Conclusión
El producto final usa Socket.io para crear un servidor al que se conectan los clientes. El servidor recupera los datos y los envía al cliente. Cuando un cliente recibe datos, puede actualizar los resultados gradualmente. Esto reduce la carga en el servidor porque el cliente solo actúa cuando recibe un evento del servidor.
El servidor puede enviar mensajes al cliente, y el cliente, a su vez, al servidor. Cuando el servidor recibe el mensaje, realiza el procesamiento de datos.
Las aplicaciones de chat funcionan de la misma manera. El servidor recibirá un mensaje del cliente y luego transmitirá todos los datos a los clientes conectados para indicar que alguien ha enviado un nuevo mensaje.
Espero que hayan disfrutado este artículo, porque cuando desarrollé esta aplicación deportiva en tiempo real, obtuve una montaña de placer que quería compartir con mi conocimiento. ¡Después de todo, el hockey es uno de mis deportes favoritos!
