Implementación de una aplicación de mensajería utilizando socket.io
por Cristian Camilo Pérez, el 24 de febrero de 2020
¿Qué es un socket?
Podría definirse como un tipo de 'puerto' en el cual se establece una comunicación abierta donde los datos fluyen bidireccionalmente entre cliente y servidor.
¿Por qué usar sockets?
Los sockets no necesitan que se envíe una petición para poder responder. Ellos permiten un flujo de datos bidireccional por lo tanto solo es necesario escuchar el servidor y éste enviará un mensaje cuando esté disponible.
¿Para qué tipo de aplicaciones es apropiado utilizarlos?
- Aplicaciones de mensajería (Chats)
- Estadísticas en tiempo real.
- Colaboración en documentos.
- Streaming binario.
Algunas diferencias entre Sockets y REST
- Los Sockets requieren el uso de la dirección IP y el puerto, mientras que la aplicación RESTful necesita diseñar operaciones basadas en los verbos HTTP.
- Los Sockets son bidireccionales, es decir, es posible realizar operaciones en ambos sentidos de cliente al servidor y viceversa, mientras que REST sigue un enfoque unidireccional.
- Los sockets son un protocolo stateful mientras que REST está basado en un protocolo stateless (Todo el estado es manejado en el lado del cliente).
- El enfoque de WebSocket es ideal para aplicaciones escalables en tiempo real, mientras que REST es más adecuado para el escenario con muchas solicitudes.
Para implementar la aplicación vamos a utilizar una librería Javascript basada en eventos llamada socket.io.
¿Por qué utilizar socket.io?
- Las conexiones se establecen incluso en presencia de: proxies, balanceadores de carga, firewalls y software antivirus.
- Un cliente desconectado intentará volver a conectarse por siempre, hasta que el servidor vuelva a estar disponible.
- Se implementa un mecanismo de latido en el nivel Engine.IO, lo que permite que tanto el servidor como el cliente sepan cuándo el otro ya no responde.
Ahora sí, empecemos a construir nuestra aplicación de mensajería en node.js con el objetivo de poner en práctica lo definido anteriormente.
Diagrama de funcionamiento
Configuración del entorno de desarrollo
Lo primero que debemos realizar es la creación de nuestro proyecto node, para esto corremos el siguiente comando.
Se instalan las dependencias.
Implementación del servidor
Creamos un nuevo archivo llamado server.js
Ahora copiamos el siguiente código, para configurar nuestro servidor:
const http = require('http');
const express = require('express');
const socketIo = require('socket.io');
const app = express();
// Set up server
const server = http.createServer(app);
const io = socketIo(server);
io.on('connection', socket => {
console.log('User connected!');
socket.on('join', user => {
socket.user = { ...user, id: socket.client.id };
const activeUsers = socket.client.conn.server.clientsCount;
socket.emit('login', { activeUsers, user: socket.user });
socket.broadcast.emit('new user', { activeUsers, user: socket.user });
});
socket.on('message', msg => {
socket.broadcast.emit('message', msg);
});
socket.on('disconnect', () => {
console.log('User disconnected!');
socket.broadcast.emit('left', {
activeUsers: socket.client.conn.server.clientsCount,
user: socket.user
});
});
});
const port = process.env.PORT || 9000;
server.listen(port, () => console.log(`App running on port ${port}`));
Se definieron tres eventos:
- join: Notifica cuando un usuario ha entrado a la aplicación y lo emite a todos los clientes.
- message: Notifica cuando un mensaje ha llegado y lo emite a todos los clientes.
- disconnect: Evento reservado por socket.io el cual notifica cuando un cliente ha salido
La estructura de nuestro proyecto queda de la siguiente manera:
Ahora ejecutamos el servidor. Deberíamos ver el siguiente mensaje de salida:
Nota: Eventos importantes de socket.io.
// En escucha de evento
socket.on('escuchando', ...data)
// Enviar al emisor
socket.emit('enviar a emisor', ...data)
// Enviar a todos los clientes excepto al emisor
socket.broadcast.emit('enviar a todos excepto emisor', ...data)
// Enviar a todos los clientes conectados
io.emit('enviar a todos', ...data)
Implementación del cliente
Socket.io nos brinda una librería que funciona tanto en cliente como servidor precisamente para conseguir la conexión bidireccional. En este caso se desarrolló un cliente utilizando React. Como el objetivo de la lección no es aprender react, entonces vamos a ejecutar los siguientes comandos para clonar el repositorio que contiene el código:
git clone git@github.com:crperz/chat-app.git
cd chat-app/front
Ahora ejecutemos el código en localhost:
npm install
npm start
Deberíamos de ver el siguiente mensaje de salida:
Esto significa que la aplicación ahora se está ejecutando. Para probar su funcionamiento se pueden abrir dos pestañas del navegador y chatear consigo mismo en localhost, pero esto no es muy interesante, porque la idea es poder chatear con más personas fuera de la máquina local.
Para resolver este problema, vamos a empaquetar la aplicación en un contenedor de docker y luego se propone su ejecución en AWS.
Dockerización
El primer paso para empaquetar la aplicación en un contenedor Docker es agregar un Dockerfile al proyecto. Este archivo es una serie de instrucciones que le indican a Docker cómo ensamblar una imagen (instalación de dependencias, pasos de compilación y ejecución de la aplicación). Veamos el Dockerfile.
# Descarga de la imagen base
FROM node:12.14.1-alpine
# Crear el directorio
WORKDIR /usr/src/app
# [<ubicación de="" los="" archivos="" a="" copiar="">, ]
COPY package*.json ./
# Instalación de dependencias
RUN npm install
# Copiar los demás archivos de la node app (docker cachea lo que ya tiene por lo tanto mejora el rendimiento al hacer las builds)
COPY . .
# El puerto por el cual va a exponer el contenedor, es el mismo puerto por donde está escuchando node
EXPOSE 9000
# Run app
CMD ["node", "server.js"]
</ubicación>
Para construir la imagen y luego ejecutarla se usan los siguientes comandos:
docker build -t chat .
docker run -d --name chat-app -p 9000:9000 chat
Arquitectura de despliegue AWS Fargate
El resultado es un proceso Node.js en un contenedor que ejecuta un servidor socket.io. Este contenedor se ejecuta en AWS Fargate y se expone a través de una ELB.
Código fuente
https://github.com/crperz/chat-app