This shows you the differences between two versions of the page.
|
isi:laboratoare:07 [2025/11/15 10:43] alexandru.predescu [Breviar teoretic] |
isi:laboratoare:07 [2025/11/21 12:52] (current) stefanel.turcu |
||
|---|---|---|---|
| Line 117: | Line 117: | ||
| == Instalare RabbitMQ == | == Instalare RabbitMQ == | ||
| - | Pentru a putea folosi o instanță de RabbitMQ, aveți două posibilități: să o instalați pe calculatorul personal ([[https://www.rabbitmq.com/docs/download|Installing RabbitMQ | RabbitMQ]]) sau să rulați următoarea comandă Docker Compose: ''docker compose up'' folosind următorul fișier ''docker-compose.yamml'': | + | Pentru a putea folosi o instanță de RabbitMQ, aveți două posibilități: să o instalați pe calculatorul personal ([[https://www.rabbitmq.com/docs/download|Installing RabbitMQ | RabbitMQ]]) sau să rulați următoarea comandă Docker Compose: ''docker compose up'' folosind următorul fișier ''docker-compose.yaml'': |
| <code yaml> | <code yaml> | ||
| Line 167: | Line 167: | ||
| Pași pentru conectarea la RabbitMQ folosind Python | Pași pentru conectarea la RabbitMQ folosind Python | ||
| - | **1. Instalarea Bibliotecii pika** | + | **1. Instalarea bibliotecii pika** |
| Asigură-te că ai instalat biblioteca ''pika''. Poți face acest lucru rulând următoarea comandă: | Asigură-te că ai instalat biblioteca ''pika''. Poți face acest lucru rulând următoarea comandă: | ||
| Line 258: | Line 258: | ||
| == Exemplu de Utilizare a unui Exchange ''fanout'' în RabbitMQ == | == Exemplu de Utilizare a unui Exchange ''fanout'' în RabbitMQ == | ||
| + | |||
| + | <spoiler 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”**. | 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”**. | ||
| Line 265: | Line 268: | ||
| - Conecta ambele queue-uri la exchange fără a folosi ''routing key'' (nefiind necesară pentru exchange-ul ''fanout''). | - Conecta ambele queue-uri la exchange fără a folosi ''routing key'' (nefiind necesară pentru exchange-ul ''fanout''). | ||
| - Trimite un mesaj către exchange, care va fi livrat tuturor queue-urilor conectate. | - Trimite un mesaj către exchange, care va fi livrat tuturor queue-urilor conectate. | ||
| + | |||
| + | |||
| + | **Cod publisher** | ||
| <code python> | <code python> | ||
| Line 273: | Line 279: | ||
| channel = connection.channel() | channel = connection.channel() | ||
| - | # 1. Crearea unui exchange de tip 'fanout' | + | # Crearea unui exchange de tip 'fanout' |
| exchange_name = 'broadcast_exchange' | exchange_name = 'broadcast_exchange' | ||
| channel.exchange_declare(exchange=exchange_name, exchange_type='fanout') | channel.exchange_declare(exchange=exchange_name, exchange_type='fanout') | ||
| print(f"Exchange-ul '{exchange_name}' de tip 'fanout' a fost creat.") | print(f"Exchange-ul '{exchange_name}' de tip 'fanout' a fost creat.") | ||
| - | # 2. Crearea queue-urilor | + | # Trimiterea unui mesaj către exchange-ul 'fanout' |
| - | queue1 = 'queue1' | + | |
| - | queue2 = 'queue2' | + | |
| - | channel.queue_declare(queue=queue1) | + | |
| - | channel.queue_declare(queue=queue2) | + | |
| - | + | ||
| - | # 3. Legarea queue-urilor la exchange fără routing key (nu este necesară pentru fanout) | + | |
| - | channel.queue_bind(exchange=exchange_name, queue=queue1) | + | |
| - | channel.queue_bind(exchange=exchange_name, queue=queue2) | + | |
| - | print(f"Queue-urile '{queue1}' și '{queue2}' au fost conectate la exchange-ul '{exchange_name}'.") | + | |
| - | + | ||
| - | # 4. Trimiterea unui mesaj către exchange-ul 'fanout' | + | |
| message = "Salut tuturor consumatorilor!" | message = "Salut tuturor consumatorilor!" | ||
| channel.basic_publish(exchange=exchange_name, routing_key='', body=message) | channel.basic_publish(exchange=exchange_name, routing_key='', body=message) | ||
| Line 296: | Line 291: | ||
| # Închiderea conexiunii | # Închiderea conexiunii | ||
| connection.close() | connection.close() | ||
| + | </code> | ||
| + | |||
| + | **Cod subscriber** | ||
| + | |||
| + | <code python> | ||
| + | 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.") | ||
| + | |||
| + | queue_name = "queue1" # nume diferite pentru fiecare consumator | ||
| + | channel.queue_declare(queue=queue_name) | ||
| + | |||
| + | # Legare queue la exchange | ||
| + | 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() | ||
| + | |||
| </code> | </code> | ||
| Line 301: | Line 333: | ||
| - Mesajul „Salut tuturor consumatorilor!” va ajunge atât în ''queue1'', cât și în ''queue2''. | - Mesajul „Salut tuturor consumatorilor!” va ajunge atât în ''queue1'', cât și în ''queue2''. | ||
| + | |||
| + | </spoiler> | ||
| + | |||
| <note tip> | <note tip> | ||
| Line 313: | Line 348: | ||
| === Setup MQTT === | === Setup MQTT === | ||
| - | Instalare în Python (biblioteca ''paho-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ă: | ||
| <code> | <code> | ||
| Line 320: | Line 359: | ||
| == Publisher == | == Publisher == | ||
| + | |||
| + | Următoarea secvență de cod implementează un nod de tip publisher care trimite mesaje prin MQTT: | ||
| <code python> | <code python> | ||
| import paho.mqtt.client as mqtt | import paho.mqtt.client as mqtt | ||
| - | BROKER = "isilab.cloud.shiftr.io" | + | BROKER = "localhost" |
| PORT = 1883 | PORT = 1883 | ||
| - | USERNAME = "isilab" | + | USERNAME = "user" |
| - | PASSWORD = "oonhdB7qBZrPK2Lf" | + | PASSWORD = "pass" |
| TOPIC = "chat/general" | TOPIC = "chat/general" | ||
| Line 345: | Line 386: | ||
| == Subscriber == | == Subscriber == | ||
| + | |||
| + | Următoarea secvență de cod implementează un nod de tip subscriber care primește mesaje venite prin MQTT: | ||
| <code python> | <code python> | ||
| import paho.mqtt.client as mqtt | import paho.mqtt.client as mqtt | ||
| - | BROKER = "isilab.cloud.shiftr.io" | + | BROKER = "localhost" |
| PORT = 1883 | PORT = 1883 | ||
| - | USERNAME = "isilab" | + | USERNAME = "user" |
| - | PASSWORD = "oonhdB7qBZrPK2Lf" | + | PASSWORD = "pass" |
| TOPIC = "chat/general" | TOPIC = "chat/general" | ||
| Line 369: | Line 412: | ||
| </code> | </code> | ||
| + | == Interfață grafică == | ||
| + | Putem vizualiza datele la nivelul broker-ului de mesaje folosind un GUI precum MQTTBox (windows: [[https://apps.microsoft.com/detail/9nblggh55jzg|from Microsoft Store]], web: [[https://github.com/workswithweb/MQTTBox|from GitHub]]) | ||
| - | ==== Exerciții ==== | + | {{:isi:laboratoare:mqtt_box_1.png?600|}} |
| - | === RabbitMQ === | + | {{:isi:laboratoare:mqtt_box_2.png?600|}} |
| - | <note> | ||
| - | Descărcați proiectul de pe GitHub: [[https://github.com/ACS-ISI/Messenger/tree/main|Messenger]] | ||
| - | </note> | ||
| - | <note tip> | ||
| - | Pentru a instala toate dependențele necesare pentru laboratorul de astăzi, rulați: ''pip install -r requirements.txt''. Fișierul ''requirements.txt'' poate fi descărcat de aici: [[https://github.com/ACS-ISI/Messenger/blob/main/requirements.txt|requirements.txt]]. | ||
| - | </note> | ||
| - | 0. 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 - [[https://www.rabbitmq.com/docs/management|Plugin]]. | ||
| - | 1. **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''). | ||
| - | <code python> | ||
| - | try: | ||
| - | while True: | ||
| - | message = input("Message: ") | ||
| - | channel.basic_publish(exchange=exchange_name, routing_key='', body=message) | ||
| - | finally: | ||
| - | connection.close() | ||
| - | </code> | ||
| - | 2. **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.** | + | ==== Exerciții ==== |
| - | 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ă. | + | |
| - | <code python> | + | === RabbitMQ === |
| - | channel.queue_bind(exchange=exchange_name, queue=queue_name) | + | |
| - | </code> | + | |
| - | + | ||
| - | 3. **Chat interactiv** | + | |
| - | + | ||
| - | Această aplicație presupune un server Flask care interacționează cu RabbitMQ și expune o interfață web pentru utilizator. | + | |
| - | + | ||
| - | **Implementare:** Pornind de la codul pentru această aplicație de mesagerie, implementează funcționalitățile necesare, marcate cu TODO-uri in fisierul ''message_service.py'', pentru ca aceasta să funcționeze complet: | + | |
| - | + | ||
| - | * TODO a) Trimite la fiecare 30 de secunde un mesaj de tip broadcast pe exchange-ul 'chat_online_user_exchange' și fără nici un routing_key, prin care să informezi ceilalți participanți la chat că ești online și disponibil pentru conversație. Asigură-te că acest mesaj are un timp de viață de maximum 2 minute în queue. | + | |
| - | * TODO b) Consumă mesajele care sosesc pe queue-ul tău. Adaugă un `consumer_tag` cu numele queue-ului, astfel încât consumatorul să nu mai asculte la acest queue atunci când utilizatorul se deconectează (logout). | + | |
| - | * TODO c) Implementează trimiterea unui mesaj către un anumit utilizator pentru a putea iniția o conversație privată cu acesta. | + | |
| <note tip> | <note tip> | ||
| + | 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: | ||
| - | Folosește instanța locală și rulează scriptul ''initialize.py'' din laborator, care configurează 3 utilizatori: **stefan**, **alex** și **maria**. | + | <code> |
| + | Flask==3.1.0 | ||
| + | Flask_SocketIO==5.4.1 | ||
| + | pika==1.3.2 | ||
| + | </code> | ||
| - | **Testare**: Pentru a testa aplicația, puteți rula două instanțe ale aplicației pe porturi diferite, pentru a simula doi utilizatori care folosesc aplicația pe dispozitive diferite. În cadrul testării, punctul a) poate fi verificat individual, iar punctele b) și c) vor fi testate împreună. | + | </note> |
| - | Pentru a rula aplicația, folosește comanda: ''python app.py'' sau ''flask run''. | + | == 1. Setup == |
| - | Aceasta va rula implicit pe portul 5000, iar pentru a vizualiza interfața pusă la dispoziție de aceasta, deschideți [[http://localhost:5000|]]. | + | 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 - [[https://www.rabbitmq.com/docs/management|Plugin]]. |
| - | Pentru a rula aplicația pe un alt port decât cel implicit, rulați ''flask run <nowiki>--</nowiki>port 5001''. | ||
| - | </note> | ||
| - | **Testare:** | + | == 2. Publisher de mesaje == |
| - | * Testează funcționalitatea pentru a verifica dacă poți comunica cu un alt utilizator (folosind două instanțe locale de aplicație). | + | 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''). |
| - | * Schimbă conexiunea la RabbitMQ cu următoarea și testează dacă poți conversa cu alți colegi: | + | |
| - | <code> | + | <code python> |
| - | Host: acc-atomicsoftware.swedencentral.cloudapp.azure.com | + | try: |
| - | User: student | + | while True: |
| - | Parola: întreabă coordonatorul de laborator | + | message = input("Message: ") |
| + | channel.basic_publish(exchange=exchange_name, routing_key='', body=message) | ||
| + | finally: | ||
| + | connection.close() | ||
| </code> | </code> | ||
| - | <note important> | + | == 3. Subscriber pentru mesaje == |
| - | În cadrul acestui exercițiu, nu vei declara queue-uri sau exchange-uri, deoarece acestea sunt deja create pe instanța din cloud. | + | |
| - | **Important**: Când te conectezi la instanța din cloud, utilizatorii vor avea formatul ''nume_prenume_grupa''. Dacă nu ești sigur care este utilizatorul pregătit pentru tine, conectează-te la: | + | 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ă. |
| - | [[http://acc-atomicsoftware.swedencentral.cloudapp.azure.com:15672]] | + | <code python> |
| - | + | channel.queue_bind(exchange=exchange_name, queue=queue_name) | |
| - | cu utilizatorul si parola de mai sus și caută în tab-ul //Queues and Streams// după numele tău. Queue-ul o să se numească //chatUser.username//, iar tu trebuie să folosești doar //username// pentru a te conecta. În situația în care totuși nu te regăsești, poți folosi unul dintre utilizatorii următori: //bob//, //alice//, //stefan//, //alex//.</note> | + | |
| - | + | ||
| - | <note important> | + | |
| - | În situația în care întâmpinați o eroare similară cu "connection lost" sau "unable to pop from an empty queue" atunci când începeți să consumați mesaje, este indicat să adăugați o metodă nouă pentru a crea un channel și să utilizați un channel nou creat atunci când începeți să ascultați. | + | |
| - | + | ||
| - | <code> | + | |
| - | def getChannel(self): | + | |
| - | connection = pika.BlockingConnection(self.connection_params) | + | |
| - | return connection.channel() | + | |
| </code> | </code> | ||
| - | </note> | ||
| - | |||
| - | **Diagrame explicative ale exercițiului 3:** | ||
| - | |||
| - | <spoiler> | ||
| - | {{:isi:laboratoare:general-architecture.jpg?400|}} | ||
| - | |||
| - | {{:isi:laboratoare:rabbitmq-user-alive-notification.jpg?500|}} | ||
| - | |||
| - | {{:isi:laboratoare:rabbitmq-send-message.jpg?500|}} | ||
| - | </spoiler> | ||
| === MQTT === | === MQTT === | ||
| Line 473: | Line 473: | ||
| Creează un script care citește mesaje de la tastatură și le publică pe un topic MQTT. | Creează un script care citește mesaje de la tastatură și le publică pe un topic MQTT. | ||
| - | <code python> | + | <note> |
| - | import paho.mqtt.client as mqtt | + | |
| - | client = mqtt.Client() | + | Date de conectare la broker-ul MQTT în cloud: |
| - | client.connect("localhost", 1883) | + | |
| - | while True: | + | <code python> |
| - | message = input("Message: ") | + | BROKER = "isilab.cloud.shiftr.io" |
| - | if message == "exit": | + | PORT = 1883 |
| - | break | + | USERNAME = "isilab" |
| - | + | PASSWORD = "oonhdB7qBZrPK2Lf" | |
| - | client.publish("chat/general", message) | + | TOPIC = "chat/general" |
| - | client.disconnect() | + | |
| </code> | </code> | ||
| + | |||
| + | Atenție! Daca toți colegii se conecteaza pe același topic, mesajele vor ajunge și la ei (și invers) | ||
| + | |||
| + | </note> | ||
| == 2. Subscriber pentru mesaje == | == 2. Subscriber pentru mesaje == | ||
| Creează un script care ascultă topic-ul și afișează mesajele primite. | Creează un script care ascultă topic-ul și afișează mesajele primite. | ||
| - | |||
| - | <code python> | ||
| - | import paho.mqtt.client as mqtt | ||
| - | |||
| - | def on_message(client, userdata, msg): | ||
| - | print(f"Primit: {msg.payload.decode()}") | ||
| - | |||
| - | client = mqtt.Client() | ||
| - | client.connect("localhost", 1883) | ||
| - | |||
| - | client.subscribe("chat/general") | ||
| - | client.on_message = on_message | ||
| - | |||
| - | print("Ascult mesaje pe chat/general...") | ||
| - | client.loop_forever() | ||
| - | </code> | ||
| == 3. Chat MQTT între 2 clienți == | == 3. Chat MQTT între 2 clienți == | ||
| Line 515: | Line 500: | ||
| Test: | Test: | ||
| - | * Trimite mesaje între instanțe | + | * Trimite mesaje între instanțe (publisher/subscriber) |
| - | * Observă că brokerul rutează mesajele automat | + | * Observă că brokerul rutează mesajele automat pe baza topic-ului, indiferent de câte instanțe de publisher sau subscriber sunt conectate la broker |