WebSocket es un protocolo de comunicación sobre una conexión TCP que establece un canal bidireccional o full-duplex con mínima latencia entre el servidor y el navegador. El navegador utiliza javascript y la API de Websocket soportada por todos los navegadores y del lado del servidor, vamos a implementar el esquema de Websockets con Node.js
El protocolo Websocket fue estandarizado por la IETF e 2011 como RFC 6455 y la API de Websocket por la W3C.
Este protocolo es diferente al protocolo HTTP aunque ambos son de la capa 7 en el modelo OSI y dependen de la capa 4 TCP. Websocket esta diseñado para operar sobre HTTP en los puertos 443 y 80 al mismo tiempo de soportar proxies e intermediarios.
Funcionalidad
El punto interesante es que el protocolo Websocket habilita la interacción entre un navegador web o cualquier otra aplicación cliente y un servidor web con baja sobrecarga facilitando la transferencia de datos en tiempo real desde y hacia el servidor. Esto se hace posible con una forma estandarizada de enviar contenido del servidor al cliente sin que el cliente inicie la petición y permite enviar mensajes de regreso y hacia adelante manteniendo la conexión abierta. Por lo que se puede establecer una comunicación bidireccional entre el cliente y el servidor.
En consecuencia, la mayoría de los navegadores soportan el protocolo Websocket a través de la directiva ws:// y para el protocolo seguro la directiva es wss:// como esquemas de URI (Uniform Resource Identifier).
Para su implementación se utiliza Javascript del lado del cliente en una página web que invoca funciones a través de los eventos de la página y del lado del servidor se pueden utilizar diversos lenguajes de programación entre los cuales podemos mencionar C#, Java, PHP, Python y por supuesto Javascript con Node y en nuestro caso utilizaremos el paquete de websockets.
Para el manejo de los Sockets, del lado del cliente existen dos grupos de funciones: 1) las requeridas para administrar el ciclo de vida del objeto y 2) los callbacks, que son las funciones que el navegador va a invocar cuando detecte un evento relacionado con el Websocket.
Ciclo de vida
En primer lugar se crea el WebSocket, lo más recomendable es que sea al terminar de cargarse la página, dentro de la función onload() del objeto window.
var socket = new WebSocket("ws://servidor.com/socketserver");
El objeto Websocket recibe en el constructor únicamente la URL del servidor al que nos vamos a conectar. La directiva ws:// reemplaza a http:// y si requerimos un canal seguro, entonces usamos wss://
Para enviar mensajes al servidor utilizamos el método send:
socket.send("Mensaje para el servidor");
El método send recibe una cadena con el mensaje que se enviará al servidor.
Si deseamos enviar un objeto JSON podemos hacerlo de la siguiente forma:
var mensaje = {
nombre: "Enrique Martínez",
correo: "enrique@gmail.com",
edad: 30
};
socket.send(JSON.stringify(mensaje));
Con la función JSON.stringify convertimos el objeto JSON en una cadena, que es lo que recibe el método send. Cuando sea necesario cerrar la conexión de manera explicita invocamos:
socket.close();
Eventos
Debido a que los Websockets funcionan de manera asíncrona, es decir, que la aplicación no se queda esperando en una línea a que llegue el mensaje del servidor, sino que en el momento en que llega un mensaje del servidor, el navegador lo notifica invocando un función de callback. De esta manera la aplicación no se queda bloqueada y sigue respondiendo a las interacciones del usuario mientras se interactúa con el servidor.
Los eventos del Websocket son los siguientes:
- onmessage
- onopen
- onclose
- onerror
Las funciones de callback que se implementan para cada uno de los eventos reciben el objeto event, con el cual obtenemos la información correspondiente al evento. El mensaje recibido desde el servidor lo obtenemos con event.data
socket.onopen = function(evt){ alert("Conexión establecida"); ...};
socket.onmessage = function(evt){ alert("Mensaje redibido: " + evt.data); ...};
socket.onclose = function(evt){ alert("Conexión cerrada"); ...};
Ejercicio Práctico Websockets con Node.js
En primer lugar, empezaremos con la página html que realiza la conexión al servidor de websockets. El archivo tiene el nombre: index.html y se puede colocas en la carpeta public dentro del proyecto de node o puede ser un archivo de una aplicación o sitio web distinto.
Cliente WebSockets
<meta charset="utf-8">
<title>Mensajes con WebSockets</title>
<script src="js/funciones.js"></script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-eOJMYsd53ii+scO/bJGFsiCZc+5NDVN2yr8+0RDqr0Ql0h+rP48ckxlpbzKgwra6" crossorigin="anonymous">
<div class="container">
<h2>Implementando Websockets con Node.js</h2>
<div class="row mt-4">
<div class="col-sm-6">
<label class="form-label">Mensajes del servidor</label>
<textarea id="mensajes" rows="10" cols="10" class="form-control"></textarea>
</div>
<div class="col-sm-5">
<form onsubmit="enviarTexto(event)">
<label class="form-label">Texto a enviar</label>
<input type="text" id="texto" name="texto" class="form-control">
<button type="submit" id="enviar" class="btn btn-primary mt-2">Enviar</button>
</form>
</div>
</div>
</div>
En la sección del head se esta incorporando un archivo de javascript ubicado en la carpeta js con el nombre funciones.js, posteriormente se incorpora una etiqueta link para agregar la hoja de estilos de Bootstrap directamente del cdn de bootstrap para darle formato a la página sin agregar tanto código.
El cuerpo de la página creamos dos columnas con el modelo de cajas de Bootstrap. En la columna de la izquierda colocamos un Textarea para ir agregando y mostrando los mensajes que se reciben del servidor, el ID del textarea es mensajes.
En la columna del lado derecho colocamos un formulario con un campo de texto y un botón. El ID del campo de texto es texto y en el se escribirán los mensajes que se envían al servidor. Para ello el formulario invocará la función enviarTexto() al momento de realizar la acción submit dando click en el botón, el cual de inicio se encuentra deshabilitado.
La siguiente imagen muestra el despliegue del archivo index.html
El siguiente paso ahora es crear el archivo de Javascript, que en este caso se llama funciones.js y se encuentra dentro de la carpeta js.
En el archivo funciones.js se crean las funciones enviarTexto(), init(), wsConnect(), onOpen(), onClose(), onMessage(), onError() y doSend()
Al terminar de cargar la ventana se ejecuta la función init() la cual invoca la conexión con servidor, si en algún momento se desconecta, la función onClose realiza el reintento de conexión cada 2 segundos. Si la conexión tiene éxito se habilita el botón.
archivo: funciones.js
// Se invoca cuando se oprime el botón Enviar
function enviarTexto(event){
event.preventDefault();
var campo = event.target.texto;
// Enviamos el valor del campo al servidor
doSend(campo.value);
// Vaciamos el campo
campo.value="";
}
// La función init se ejecuta cuando termina de cargarse la página
function init() {
// Conexión con el servidor de websocket
wsConnect();
}
// Invoca esta función para conectar con el servidor de WebSocket
function wsConnect() {
// Connect to WebSocket server
websocket = new WebSocket("ws://localhost:3000");
// Asignación de callbacks
websocket.onopen = function (evt) {
onOpen(evt)
};
websocket.onclose = function (evt) {
onClose(evt)
};
websocket.onmessage = function (evt) {
onMessage(evt)
};
websocket.onerror = function (evt) {
onError(evt)
};
}
// Se ejecuta cuando se establece la conexión Websocket con el servidor
function onOpen(evt) {
// Habilitamos el botón Enviar
document.getElementById("enviar").disabled = false;
// Enviamos el saludo inicial al servidor
doSend("Hola");
}
// Se ejecuta cuando la conexión con el servidor se cierra
function onClose(evt) {
// Deshabilitamos el boton
document.getElementById("enviar").disabled = true;
// Intenta reconectarse cada 2 segundos
setTimeout(function () {
wsConnect()
}, 2000);
}
// Se invoca cuando se recibe un mensaje del servidor
function onMessage(evt) {
// Agregamos al textarea el mensaje recibido
var area = document.getElementById("mensajes")
area.innerHTML += evt.data + "\n";
}
// Se invoca cuando se presenta un error en el WebSocket
function onError(evt) {
console.log("ERROR: " + evt.data);
}
// Envía un mensaje al servidor (y se imprime en la consola)
function doSend(message) {
console.log("Enviando: " + message);
websocket.send(message);
}
// Se invoca la función init cuando la página termina de cargarse
window.addEventListener("load", init, false);
Servidor Websockets
La implementación del servidor la hacemos con Node.js para lo cual primero creamos el proyecto y la estructura del mismo, posteriormente instalamos las librerías requeridas.
Creamos la carpeta del proyecto y en la ventana de línea de comandos, dentro de la carpeta del proyecto, ejecutamos el comando para la creación de la aplicación con Node:
npm init -y
Una vez terminada la ejecución del init, creamos la carpeta src y dentro de ella la carpeta public y el archivo index.js
Posteriormente, instalamos las librerías que vamos a requerir:
npm install express cors websockets
Para el proyecto, instalamos express y cors para crear el servidor http y para el servidor de Websockets, instalamos la librería websockets. Puedes consultar la documentación adicional de la librería websockets en este enlace: https://github.com/theturtle32/WebSocket-Node/blob/HEAD/docs/index.md
Una vez completada la instalación de las librerías, colocamos en la carpeta public el archivo index.html y la carpeta js con su correspondiente archivo funciones.js
El archivo index.js que se encuentra dentro de la carpeta src tendrá lo siguiente:
// importamos las librerías requeridas
const path = require("path");
const express = require('express');
const cors = require('cors');
const app = express();
const server = require('http').Server(app);
const WebSocketServer = require("websocket").server;
// Creamos el servidor de sockets y lo incorporamos al servidor de la aplicación
const wsServer = new WebSocketServer({
httpServer: server,
autoAcceptConnections: false
});
// Especificamos el puerto en una varibale port, incorporamos cors, express
// y la ruta a los archivo estáticos (la carpeta public)
app.set("port", 3000);
app.use(cors());
app.use(express.json());
app.use(express.static(path.join(__dirname, "./public")));
function originIsAllowed(origin) {
// Para evitar cualquier conexión no permitida, validamos que
// provenga de el cliente adecuado, en este caso del mismo servidor.
if(origin === "http://localhost:3000"){
return true;
}
return false;
}
// Cuando llega un request por sockets validamos el origen
// En caso de origen permitido, recibimos el mensaje y lo mandamos
// de regreso al cliente
wsServer.on("request", (request) =>{
if (!originIsAllowed(request.origin)) {
// Sólo se aceptan request de origenes permitidos
request.reject();
console.log((new Date()) + ' Conexión del origen ' + request.origin + ' rechazada.');
return;
}
const connection = request.accept(null, request.origin);
connection.on("message", (message) => {
console.log("Mensaje recibido: " + message.utf8Data);
connection.sendUTF("Recibido: " + message.utf8Data);
});
connection.on("close", (reasonCode, description) => {
console.log("El cliente se desconecto");
});
});
// Iniciamos el servidor en el puerto establecido por la variable port (3000)
server.listen(app.get('port'), () =>{
console.log('Servidor iniciado en el puerto: ' + app.get('port'));
})
Los mensajes por Websockets que recibe el servidor, los imprime en consola y los manda de regreso al cliente.
Puedes ver la implementación de este ejemplo en el siguiente video
Suscríbete también al canal de Youtube
Me parece que la linea “npm install express cors websockets ” tiene un typo, viendo el video se instalo “websocket” en lugar de “websockets”
Hola,
SI, tienes razón, el componente es websocket sin la s, gracias por la observación
como hacer esto sin el boton.O sea escribir algo en un texarea y se refleje instantaneamente en el otro texarea.
Puedes colocar la misma variable en los dos textarea
o agregar un evento en el primer textarea que sea onchange, este invoca una función de javascript la cual recibe los caracteres que se van capturando y los envías al segundo textarea
me aparece este erro cada que intento ingresar con localhost:3000
Ese mensaje es normal mientras no tengas el redireccionamiento a la carpeta public