This is an old revision of the document!


Laboratorul 07. Arhitecturi pe bază de mesaje. Chat interactiv

Obiective

În cadrul acestui laborator, veți parcurge mai întâi un breviar teoretic pentru a înțelege cum funcționează RabbitMQ, care sunt principalele concepte ale acestei tehnologii, cum să rulați o instanță de RabbitMQ local și cum să o utilizați cu Python. În această primă parte nu este necesar să rulați nimic; materialul oferit are rol de suport pentru partea a doua.

Partea a doua constă într-o serie de exerciții care vor aplica tot ceea ce s-a explicat în breviarul teoretic și vă vor ajuta să aprofundați cunoștințele prin exemple practice.

Breviar teoretic

Ce este RabbitMQ

RabbitMQ este un broker de mesaje open-source bazat pe protocolul AMQP (Advanced Message Queuing Protocol) utilizat pentru a permite aplicațiilor să comunice eficient și asincron prin trimiterea de mesaje între ele. Acesta este folosit pentru a decupla componentele aplicațiilor, facilitând astfel comunicarea, scalabilitatea și reziliența în arhitecturi distribuite și microservicii.

Ce este MQTT

MQTT (Message Queuing Telemetry Transport) este un protocol de comunicație optimizat pentru dispozitive IoT (Internet of Things), senzori, rețele cu lățime de bandă redusă și comunicații instabile. MQTT a fost proiectat pentru latență mică și consum minim de energie.

Spre deosebire de RabbitMQ care folosește AMQP, MQTT utilizează un model publish/subscribe, bazat pe un server central numit broker.

Aplicații tipice MQTT:

  • Smart Home
  • Telemetrie industrială
  • Automotive
  • Dispozitive medicale
  • Rețele GSM/LoRa/Satelit

Comparație

Caracteristică RabbitMQ / AMQP MQTT
Domeniul principal Sisteme enterprise, microservicii IoT, dispozitive embedded
Complexitate Ridicată Redusă
Transport TCP TCP / WebSockets
Schemă mesaje Structurată, headere multe Minimul posibil (~2 bytes)
Persistență Da Opțional (QoS)

Caracteristici RabbitMQ

  • Asincron și fiabil: RabbitMQ permite transmiterea de mesaje între componente, eliminând nevoia procesării imediate. Mesajele sunt stocate în queue-uri, iar consumatorii le pot prelua și procesa în ritmul lor.
  • Queue-uri persistente: Mesajele pot fi salvate în queue-uri persistente, garantând livrarea chiar și în caz de restart sau întreruperi.
  • Scalabilitate: RabbitMQ poate fi scalat orizontal prin configurarea sa într-un cluster distribuit, ceea ce permite gestionarea unor volume mari de mesaje și distribuirea sarcinii între mai multe servere.
Componentele RabbitMQ
  1. Producer (Producător): Trimite mesaje către RabbitMQ.
  2. Exchange: Primește mesajele de la producători și decide cum să le ruteze către queue-uri pe baza regulilor de rutare.
  3. Queue (Coadă): Stochează mesajele până când acestea sunt preluate de consumatori.
  4. Consumer (Consumator): Preia mesajele din queue și le procesează.
Tipuri de Exchange
  • Direct Exchange: Rutează mesajele către un queue specific folosind un routing key exact.
  • Fanout Exchange: Trimite toate mesajele către toate queue-urile asociate, ignorând routing key.
  • Topic Exchange: Rutează mesajele pe baza unui pattern de routing key, permițând un nivel ridicat de flexibilitate. https://www.rabbitmq.com/tutorials/tutorial-five-python
  • Headers Exchange: Utilizează headerele mesajelor pentru rutarea acestora către queue-uri, fiind potrivit pentru rutarea pe criterii complexe.
Cum funcționează RabbitMQ
  1. Producerea mesajelor: Producătorii creează mesaje și le trimit către un exchange.
  2. Rutarea mesajelor: Exchange-ul distribuie mesajele către queue-uri în funcție de regulile de rutare configurate.
  3. Stocarea mesajelor: Queue-urile stochează mesajele temporar până când sunt preluate de consumatori.
  4. Consumarea mesajelor: Consumatorii se conectează la queue-uri și preiau mesajele pentru a le procesa.

Pentru a conecta o aplicație Python la RabbitMQ, vei folosi biblioteca pika, care este un client AMQP popular pentru Python. Aceasta permite crearea conexiunilor, configurarea exchange-urilor și queue-urilor, și trimiterea sau recepționarea mesajelor prin RabbitMQ.

Caracteristici MQTT

Arhitectură de tip Publish / Subscribe

  • Mesajele NU sunt transmise direct între clienți:
    • Publisher → Broker → Subscriber
    • Brokerul trimite mesajele doar clienților abonați la topic-ul corespunzător.

QoS (Quality of Service)

Nivel QoS Garanție livrare Explicație
0 At most once Fără garanție (ca UDP)
1 At least once Posibile duplicate
2 Exactly once Cel mai sigur, dar mai lent
Componentele MQTT
  • Broker – serverul central prin care trec mesajele (ex.: Mosquitto, EMQX, HiveMQ)
  • Client MQTT:
    • Publisher – publică mesaje
    • Subscriber – recepționează mesaje
  • Topic – identificator ierarhic pentru rutarea mesajelor

Exemplu de topic ierarhic:

casa/living/temperatura

Wildcard-uri în topic:

  • + – un singur nivel
  • # – toate nivelurile următoare
casa/+/temperatura -> casa/living/temperatura
casa/# -> toate subtopic-urile 

Setup RabbitMQ

Instalare RabbitMQ

Pentru a putea folosi o instanță de RabbitMQ, aveți două posibilități: să o instalați pe calculatorul personal (Installing RabbitMQ | RabbitMQ) sau să rulați următoarea comandă Docker Compose: docker compose up folosind următorul fișier docker-compose.yaml:

version: '3.8'

services:
  rabbitmq:
    image: rabbitmq:3-management
    container_name: rabbitmq
    environment:
      RABBITMQ_DEFAULT_USER: guest
      RABBITMQ_DEFAULT_PASS: guest
    ports:
      - "5672:5672"  # Port for AMQP protocol
      - "15672:15672"  # Port for RabbitMQ management UI
    volumes:
      - rabbitmq_data:/var/lib/rabbitmq
    networks:
      - rabbitmq_network

volumes:
  rabbitmq_data:

networks:
  rabbitmq_network:
    driver: bridge

Comandă rulare pentru docker-compose.yaml prezentat anterior:

docker-compose up -d

Pentru o introducere în Docker, consultați: Quickstart | Docker Docs

Interfață grafică

RabbitMQ oferă și o interfață grafică de management (Management UI) pentru o monitorizare și administrare mai ușoară a mesajelor, queue-urilor și exchange-urilor, fiind accesibilă în mod implicit pe portul 15672 (http://localhost:15672). User-ul și parola implicite sunt guest/guest, însă aceste credențiale funcționează doar pentru accesul de pe localhost; pentru accesul extern este necesară configurarea unor utilizatori noi.

Programarea în Python

Pentru a conecta o aplicație Python la RabbitMQ, vei folosi biblioteca pika, un client AMQP popular pentru Python. Aceasta permite crearea conexiunilor, configurarea exchange-urilor și queue-urilor, și trimiterea sau recepționarea mesajelor prin RabbitMQ.

Pași pentru conectarea la RabbitMQ folosind Python

1. Instalarea bibliotecii pika

Asigură-te că ai instalat biblioteca pika. Poți face acest lucru rulând următoarea comandă:

pip install pika

2. Configurarea unei Conexiuni la RabbitMQ

Pentru a te conecta la RabbitMQ, va trebui să definești parametrii de conectare. În mod implicit, RabbitMQ rulează local pe portul 5672, iar contul implicit este guest cu parola guest.

import pika
 
# Configurarea conexiunii la RabbitMQ
credentials = pika.PlainCredentials("guest", "guest")
connection_params = pika.ConnectionParameters(host='localhost', port=5672, credentials=credentials)
connection = pika.BlockingConnection(connection_params)  
channel = connection.channel()
 
print("Conexiunea la RabbitMQ a fost stabilită.")

Acest cod:

  • Creează o conexiune la serverul RabbitMQ de pe localhost.
  • Creează un channel, care este un canal de comunicare utilizat pentru a interacționa cu RabbitMQ.
Notă: Dacă RabbitMQ rulează pe un alt server sau port, schimbă localhost cu adresa IP și/sau specifică portul prin ConnectionParameters.

3. Crearea unui Queue

Pentru a trimite și primi mesaje, ai nevoie de un queue (coadă) în care să fie stocate mesajele. Poți crea un queue folosind queue_declare:

queue_name = 'my_queue'
channel.queue_declare(queue=queue_name)
 
print(f"Queue-ul '{queue_name}' a fost creat.")

Acest cod va crea un queue numit my_queue (sau se va conecta la el dacă deja există). Queue-ul este o structură FIFO (First In, First Out) care stochează mesajele până când sunt preluate de consumatori.

4. Trimiterea unui Mesaj în Queue

Pentru a trimite un mesaj, vei folosi metoda basic_publish:

message = "Salut din RabbitMQ!"
channel.basic_publish(exchange="", routing_key=queue_name, body=message)
 
print(f"Mesaj trimis: {message}")
  • exchange=”” specifică că mesajul este trimis direct către queue-ul specificat de routing_key.
  • routing_key=queue_name indică queue-ul în care trebuie să ajungă mesajul.
  • body=message este conținutul mesajului trimis.

5. Primirea unui Mesaj din Queue

Pentru a primi mesaje, definești un consumator. Acesta va utiliza un callback pentru a procesa fiecare mesaj primit:

def callback(ch, method, properties, body):
    print(f"Mesaj primit: {body.decode()}")
 
channel.basic_consume(queue=queue_name, on_message_callback=callback, auto_ack=True)
 
print("Aștept mesaje...")
channel.start_consuming()
  • callback este o funcție care primește și afișează mesajele.
  • basic_consume configurează consumatorul să asculte queue-ul specificat (queue_name) și să apeleze callback pentru fiecare mesaj care este primit (“să consume mesjul”).
  • auto_ack=True confirmă automat mesajele ca fiind procesate imediat ce sunt primite. După ce sunt procesate, acestea vor fi șterse de pe queue.

6. Închiderea Conexiunii

După ce ai terminat de trimis și primit mesaje, închide conexiunea la RabbitMQ pentru a elibera resursele:

connection.close()
print("Conexiunea la RabbitMQ a fost închisă.")
Extra

Pentru a vedea un exemplu complet despre cum două aplicații, un sender și un receiver, comunică folosind RabbitMQ, precum și pentru a citi explicații mai detaliate, vizitați acest tutorial: Hello World.

În cadrul acestuia veți găsi exemplificat întregul proces de conectare la RabbitMQ: crearea unei cozi (queue), trimiterea și recepția mesajelor, precum și închiderea conexiunii.

Exemplu de Utilizare a unui Exchange ''fanout'' în RabbitMQ

click here

click here

Până acum am văzut cum putem crea o coadă (queue), să trimitem un mesaj pe aceasta și să consumăm acel mesaj. În cadrul exemplului anterior, am presupus că un mesaj este trimis către o singură coadă. În ceea ce urmează, vom face ceva complet diferit – vom livra un mesaj către mai mulți consumatori (îl vom publica pe mai multe queue-uri). Acest pattern este cunoscut sub denumirea de „publish/subscribe”.

În acest exemplu, vom:

  1. Crea un exchange de tip fanout numit broadcast_exchange.
  2. Crea două queue-uri (queue1 și queue2).
  3. Conecta ambele queue-uri la exchange fără a folosi routing key (nefiind necesară pentru exchange-ul fanout).
  4. Trimite un mesaj către exchange, care va fi livrat tuturor queue-urilor conectate.

Cod publisher

import pika
 
# Conectarea la RabbitMQ
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
 
# Crearea unui exchange de tip 'fanout'
exchange_name = 'broadcast_exchange'
channel.exchange_declare(exchange=exchange_name, exchange_type='fanout')
print(f"Exchange-ul '{exchange_name}' de tip 'fanout' a fost creat.")
 
# Trimiterea unui mesaj către exchange-ul 'fanout'
message = "Salut tuturor consumatorilor!"
channel.basic_publish(exchange=exchange_name, routing_key='', body=message)
print(f"Mesaj trimis către exchange-ul '{exchange_name}': {message}")
 
# Închiderea conexiunii
connection.close()

Cod subscriber

import pika
 
# Conectarea la RabbitMQ
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
 
# Crearea unui exchange de tip 'fanout'
exchange_name = 'broadcast_exchange'
 
channel.exchange_declare(exchange=exchange_name, exchange_type='fanout')
print(f"Exchange-ul '{exchange_name}' de tip 'fanout' a fost creat.")
 
hannel.queue_declare(queue=queue_name)
 
# Legare queue la exchange
queue_name = "queue1" # nume diferite pentru fiecare consumator
 
channel.queue_bind(exchange=exchange_name, queue=queue_name)
 
print(f"Ascult mesaje pe '{queue_name}'...")
 
def callback(ch, method, properties, body):
    print(f"[Primit] {body.decode()}")
 
channel.basic_consume(queue=queue_name, on_message_callback=callback, auto_ack=True)
 
# Închiderea conexiunii
try:
    channel.start_consuming()
except KeyboardInterrupt:
    print("Receiver oprit.")
    connection.close()

Rezultatul Așteptat

- Mesajul „Salut tuturor consumatorilor!” va ajunge atât în queue1, cât și în queue2.

Utilizare

Exchange-urile fanout sunt utile pentru aplicații de tip difuzare (broadcast), unde același mesaj trebuie să fie recepționat de toate queue-urile conectate. Acest tip de exchange este ideal pentru scenarii precum notificările de sistem, unde toate instanțele trebuie să fie informate simultan.

Setup MQTT

Pentru a conecta o aplicație Python la MQTT, vei folosi biblioteca paho-mqtt. Aceasta permite crearea și configurarea conexiunilor, și trimiterea sau recepționarea mesajelor prin MQTT.

1. Instalarea bibliotecii ''paho-mqtt''

Asigură-te că ai instalat biblioteca paho-mqtt. Poți face acest lucru rulând următoarea comandă:

pip install paho-mqtt
Publisher

Următoarea secvență de cod implementează un nod de tip publisher care trimite mesaje prin MQTT:

import paho.mqtt.client as mqtt
 
BROKER = "localhost"
PORT = 1883
USERNAME = "user"
PASSWORD = "pass"
TOPIC = "chat/general"
 
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
client.username_pw_set(USERNAME, PASSWORD)
 
client.connect(BROKER, PORT)
 
while True:
    message = input("Message: ")
    client.publish("chat/general", message)
    if message == "exit":
        break
 
client.disconnect()
Subscriber

Următoarea secvență de cod implementează un nod de tip subscriber care primește mesaje venite prin MQTT:

import paho.mqtt.client as mqtt
 
BROKER = "localhost"
PORT = 1883
USERNAME = "user"
PASSWORD = "pass"
TOPIC = "chat/general"
 
def on_message(client, userdata, msg):
    print(f"Mesaj primit pe topicul {msg.topic}: {msg.payload.decode()}")
 
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
client.username_pw_set(USERNAME, PASSWORD)
client.connect(BROKER, PORT)
 
client.subscribe("chat/general")
client.on_message = on_message
 
print("Ascult mesaje MQTT...")
client.loop_forever()
Interfață grafică

Putem vizualiza datele la nivelul broker-ului de mesaje folosind un GUI precum MQTTBox (windows: from Microsoft Store, web: from GitHub)

Exerciții

RabbitMQ

Pentru a instala toate dependențele necesare pentru laboratorul de astăzi, rulați: pip install -r requirements.txt folosind fișierul requirements.txt cu următorul conținut:

Flask==3.1.0
Flask_SocketIO==5.4.1
pika==1.3.2

1. Setup

Rulați local o instanță de RabbitMQ, fie folosind docker-compose-ul pus la dispoziție mai sus, fie instalând RabbitMQ direct pe mașina voastră. Atenție, dacă optați pentru instalarea directă pe mașina voastră, va fi necesar să instalați separat plugin-ul care oferă acces la interfața web - Plugin.

2. Publisher de mesaje

Creează un script Python care să permită citirea textelor de la tastatură și publicarea acestora într-un exchange nou. (care este creat programatic după realizarea cu succes a conexiunii la RabbitMQ). Scriptul va citi textul de la utilizator și va publica fiecare mesaj în exchange-ul RabbitMQ (in mod fanout).

try:
   while True:
      message = input("Message: ")
      channel.basic_publish(exchange=exchange_name, routing_key='', body=message)
finally:
    connection.close()
3. Subscriber pentru mesaje

Creează un script Python care să creeze un queue, să-l lege la exchange-ul din exercițiul 1 și să afișeze fiecare mesaj primit. Scriptul va crea un queue nou și îl va asocia (binding) cu exchange-ul creat în exercițiul anterior. Pentru fiecare mesaj primit în queue, scriptul va afișa conținutul acestuia în consolă.

channel.queue_bind(exchange=exchange_name, queue=queue_name)

MQTT

1. Publisher de mesaje

Creează un script care citește mesaje de la tastatură și le publică pe un topic MQTT.

Date de conectare la broker-ul MQTT în cloud:

BROKER = "isilab.cloud.shiftr.io"
PORT = 1883
USERNAME = "isilab"
PASSWORD = "oonhdB7qBZrPK2Lf"
TOPIC = "chat/general"

2. Subscriber pentru mesaje

Creează un script care ascultă topic-ul și afișează mesajele primite.

3. Chat MQTT între 2 clienți

Rulați două terminale:

  • Terminal 1 → scriptul subscriber
  • Terminal 2 → scriptul publisher

Test:

  • Trimite mesaje între instanțe (publisher/subscriber)
  • Observă că brokerul rutează mesajele automat pe baza topic-ului, indiferent de câte instanțe de publisher sau subscriber sunt conectate
isi/laboratoare/07.1763236675.txt.gz · Last modified: 2025/11/15 21:57 by alexandru.predescu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0