Empleando las Rooms o habitaciones en Flask SocketIO

Flask-SocketIO es una extensión de Flask que permite la integración de WebSockets en una aplicación Flask, lo que permite la comunicación bidireccional en tiempo real entre el servidor y el cliente sin necesidad de actualizar constantemente la página; por ejemplo, en una app tipo chat, tu envias el mensaje al servidor, a otra persona o grupo de personas; pero, tambien puede recibir mensajes, por lo tanto, en el esquemal clasico de cliente servidor no es posible obtener el mensaje de la misma manera que los envias, por lo tanto tendrías que edtar recargando o enviando peticiones al servidor cada ciertos cambios para consultar si tienes posibles mensajes, pero, en la comunicación full duplex existe un canal entre el servidor al cliente (la inversa) y no hay necesidad de enviar peticiones al servidor cada cierto tiempo (que es un enfoque ineficiente). 

Flask-SocketIO utiliza una implementación de WebSocket de la biblioteca Socket.IO de JavaScript, que es compatible con múltiples navegadores y admite la creación de salas para manejar múltiples conexiones simultáneas. Además, proporciona funciones para enviar mensajes al cliente y recibir mensajes del cliente en tiempo real.

La comunicación entre clientes y servidores es parte de cualquier aplicación disponible hoy en día que se encuentre en Internet. Los sockets web son un protocolo desarrollado para facilitar este proceso. Nos permiten establecer un contacto bidireccional entre un cliente (o varios clientes) y un servidor. Puede pensar en su navegador web como un cliente, por ejemplo. El servidor puede ser un programa de fondo que se ejecuta en su sistema.

WebSocket es un protocolo de comunicación para el modelo cliente-servidor; para comprender WebSocket, es mejor comparar la especialidad de WebSocket sobre HTTPS.

http vs websocket
http vs websocket


Hay algunas ventajas de WebSocket sobre HTTPS, pero pasan sobre todo por la posibilidad de crear un canal fullduplex entre el cliente y servidor, y la posibilidad de crear habitaciones para alojar clientes, crear broadcast entre otros.

El uso de los Websoket es muy facil en Flask, al igual que ocurre con otras posibles dependencias, podemos instalar un plugin con el cual tener el completo control de los webSokect en Flask y la habilitación de un servidor especial para poder utilizar los websokect.

En esta entrada, veremos como podemos crear habitaciones para comunicar varios clientes entre si, con mensajes al servidor y viseversa.

Crear nuestro Room

El uso de los rooms es un mecanismo que nos permite tener mayor control sobre los usuarios que se puedan conectar a nuestros canales dúplex desde el cliente y el servidor; a diferencia de la comunicación que tenemos en Flask SocketIO en la cual no tenemos este control, ya que cualquier usuario que entra al apartado de nuestra web en la que habilitamos los socket, como mostramos en:

Podrá tener acceso total a los mismos sin control algunos; pero con los rooms ahora podemos indicar que usuario va a unirse o abandonar una sala; esto nos da mucho más control sobre qué usuario puede o no acceder a ver los mensajes que compartimos por dicha habitación; para esto, colocamos un parámetro más a comunicación tanto en el servidor como en el cliente al emitir los eventos:

emit('chat', message['message'], broadcast=True, to=message['room'])
socket.emit('chat', { message: message.value.trim(), room:"room{{current_user.id}}"})

Unirnos y abandonar rooms

Por lo tanto, ahora vamos a usar dos funciones más para poder unirnos a una habitación o abandonar la misma.

Para unirnos a una habitación:

join_room 

Y para salirnos de una habitación:

leave_room

Servidor

La función de chat, que empleamos para enviar mensajes desde el servidor; es decir, Flask:

@socketio.on('chat')
def chat(message):    
    print("chat "+str(message))
    emit('chat', message['message'], broadcast=True, to=message['room'])

En esta funcion emitimos un evento, pasado desde el cliente y con el broadcast indicamos que todos los clientes que se encuentren escuchando la habitación de message['room'] recibirán este mensaje; esta función es usada desde el cliente como veremos en unos momentos.

Cliente

Y desde el cliente, es decir, JavaScript:

 function sendMsj() {
    message = document.querySelector("#message")
    if (message.value.trim() == "")
       return alert("No hay mensaje que enviar")
    socket.emit('chat', { message: message.value.trim(), room:"room"})
    message.value = ""
}

En esta función hacemos las verificaciones típicas para saber si tenemos datos, luego de esto, mediante la función de emit, emitimos el evento a la función de chat alojada en el servidor, la que vimos anteriormente.

Servidor

Volviendo al servidor, es importante notar que, pasa usar un socket, necesitamos unirnos y tambien poder dejar ese grupo; como comentamos antes, para eso existen las funciones de join_room y leave_room, en las cuales, con la sesión habilitada, nos unimos o dejamos el grupo respectivamente.

Finalmente, las funciones para unirnos o salirnos del grupo que usamos:

@socketio.on('join')
def join(room):
    username=current_user.name   
    print("Join")
    join_room(room['room'])
    emit('join', username+" se unio a la habitacion", to=room['room'])
@socketio.on('leave')
def leave(room):
    username=current_user.name   
    print("leave")
    leave_room(room['room'])
    emit('leave', username+" abandono la habitacion", to=room['room'])

Como puedes ver, para hacerlo un poco más interesante, empleamos Flask Login para tener datos diferentes para cada usuario que realice una conexión.

El parámetro room lo puedes tomar de cualquier parte, una referencia que venga desde la vista, base de datos, etc; en este ejemplo lo tenemos establecido por defecto desde la función chat del cliente.

Ya con esto, podemos enviar mensajes que solamente van a escuchar los usuarios que se encuentren asignada tras previo acceso a la habitación llamada "room"; para eso tenemos un par de botones:

<button onclick="join()">Unir</button>
<button onclick="leave()">Abandonar</button>

Y sus correspondientes funciones:

function join(){
     socket.emit('join', { room:"room{{current_user.id}}"})
}
function leave(){
  socket.emit('leave', { room:"room{{current_user.id}}"})
}

Finalmente, te dejo el código completo que forma parte de mi curso completo de Flask con Python:

from flask import Blueprint,render_template
from flask_socketio import emit, join_room, leave_room
from flask_login import login_required, current_user
from app import db, socketio
from app.user.models import User
from app.chat.models import MessageRoom
from datetime import datetime
roomBp = Blueprint('room',__name__)
@roomBp.route("/room")
@login_required
def index():
    return render_template("room/index.html")
@socketio.on('chat')
@login_required
def chat(message):    
    print("Estamos en evento "+str(message))
    emit('chat', message['message'], broadcast=True, to=message['room'])
@socketio.on('join')
@login_required
def join(room):
    username=current_user.name   
    print("Join")
    join_room(room['room'])
    emit('join', username+" se unio a la habitacion", to=room['room'])
@socketio.on('leave')
@login_required
def leave(room):
    username=current_user.name   
    print("leave")
    leave_room(room['room'])
    emit('leave', username+" se unio a la habitacion", to=room['room'])

En la vista de chats:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Sokect en Flask</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"
        integrity="sha512-q/dWJ3kcmjBLU4Qc47E4A9kTB4m3wuTY7vkFJDTZKjTs8jhyGQnaUrxa0Ytd0ssMZhbNua9hE+E7Qv1j+DyZwA=="
        crossorigin="anonymous"></script>
</head>
<body>
    <textarea id="message"></textarea>
    <button onclick="sendMsj()">Enviar</button>
    <button onclick="join()">Unir</button>
    <button onclick="leave()">Abandonar</button>
    <script>
        var socket = io()
        function join(){
            socket.emit('join', { room:"room{{current_user.id}}"})
        }
        function leave(){
            socket.emit('leave', { room:"room{{current_user.id}}"})
        }
        function sendMsj() {
            message = document.querySelector("#message")
            if (message.value.trim() == "")
                return alert("No hay mensaje que enviar")
            socket.emit('chat', { message: message.value.trim(), room:"room{{current_user.id}}"})
            message.value = ""
        }
        socket.on('connect', function () {
            console.log("Conectados!")
        })
        socket.on('disconnect', function () {
            console.log("Desconectados!")
        })
        socket.on('chat', function (message) {
            console.log("chat "+message)
        })
        socket.on('leave', function (message) {
            console.log("leave "+message)
        })
        socket.on('join', function (message) {
            console.log("join "+message)
        })
    </script>
    <h1>Hola Mundo</h1>
</body>
</html>

Extra: Detectar conexiones o desconexiones

En Flask Socket IO, podemos detectar fácilmente cuando nos conectamos y desconectamos del servidor de socket; para eso:

@socketio.on('connect')
def connect():
    print("Conectado!!!!!!!")
@socketio.on('disconnect')
def disconnect():

Recuerda que este material forma parte de mi curso completo sobre Flask.

- Andrés Cruz

In english
Andrés Cruz

Desarrollo con Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter

Andrés Cruz En Udemy

Acepto recibir anuncios de interes sobre este Blog.