ProyectoPython_2/README.md

11 KiB
Raw Blame History

🖥️ Proyecto PSP — Programación de Servicios y Procesos

Aplicación de escritorio desarrollada en Python con Tkinter que agrupa en una sola ventana demostraciones prácticas de los tres grandes temas de la asignatura: procesos, hilos y comunicaciones en red.


📋 Índice


Descripción general

La aplicación se organiza en tres pestañas principales (T1, T2, T3) accesibles desde un ttk.Notebook. Cada pestaña contiene a su vez un notebook interno con varias sub-pestañas. Una barra de estado en la parte inferior muestra información dinámica de cada módulo activo.


Estructura de ficheros

ProyectoPython_AdrianHustea/
│
├── Proyecto.py          # Fichero principal — toda la lógica de la aplicación
├── chat_client.py       # Script independiente para las ventanas de chat (subproceso)
├── alarm.mp3            # Sonido de la alarma (T2)
├── music.mp3            # Música de fondo (T2)
├── backup_script.sh     # Script de backup del editor de texto (T1)
└── README.md

chat_client.py debe estar en la misma carpeta que Proyecto.py. Es lanzado como subproceso independiente al abrir el chat.


Requisitos e instalación

Python 3.8 o superior

Instalar dependencias:

pip install psutil matplotlib pygame
Librería Uso
tkinter Interfaz gráfica (incluida en Python estándar)
psutil Monitorización de CPU, memoria, disco y red (T1)
matplotlib Gráficas en tiempo real de recursos (T1)
pygame Reproducción de música y alarma (T2)
imaplib Lectura de correo via IMAP (T3 — incluida en Python estándar)
smtplib Envío de correo via SMTP (T3 — incluida en Python estándar)
subprocess Lanzamiento de subprocesos para el chat (T3 — incluida en Python estándar)

Cómo ejecutar

python3 Proyecto.py

Para probar el chat de forma independiente:

touch /tmp/cola_a.txt /tmp/cola_b.txt

python3 chat_client.py "Cliente A" "Cliente B" /tmp/cola_a.txt /tmp/cola_b.txt 100 150
python3 chat_client.py "Cliente B" "Cliente A" /tmp/cola_b.txt /tmp/cola_a.txt 560 150

Pestaña T1 — Procesos

Sub-pestañas:

Estado del sistema Monitorización en tiempo real del sistema usando hilos de fondo (threading.Thread) que actualizan gráficas de matplotlib embebidas en Tkinter. Métricas disponibles:

  • Uso de CPU (%)
  • Memoria RAM usada/libre
  • Espacio en disco
  • Tráfico de red (bytes enviados/recibidos)

Editor de texto Editor con las operaciones básicas (abrir, guardar, nuevo) y un botón de backup que invoca backup_script.sh como subproceso mediante subprocess.Popen.

Hilo Demostración de un hilo de red que realiza peticiones HTTP y muestra el resultado en la interfaz sin bloquear la GUI.


Pestaña T2 — Hilos

Sub-pestañas:

Reloj / Alarma Reloj digital que se actualiza cada segundo mediante un hilo dedicado (threading.Thread con stop_event). Permite programar una alarma con cuenta atrás, reproduciendo alarm.mp3 a través de pygame.mixer cuando se alcanza el tiempo establecido.

Coches Simulación de una carrera entre varios coches, donde cada coche es un hilo independiente. Se usa un threading.Lock para proteger el acceso a la variable winner (sección crítica) y un threading.Event para detener la carrera limpiamente.

Scraping Hilo de fondo que realiza scraping de una URL usando urllib.request y expresiones regulares, mostrando los resultados en la interfaz sin bloquear la GUI.


Pestaña T3 — Comunicaciones

Contiene dos sub-pestañas: Chat y Correo.


Chat IPC

Comunicación entre procesos (IPC) mediante ficheros de cola compartidos.

¿Por qué procesos y no hilos? Tkinter no es thread-safe. No es posible tener dos ventanas tk.Tk() en hilos distintos del mismo proceso sin que colisionen sobre el display X11. La solución es lanzar dos procesos completamente independientes, uno por cada ventana, usando subprocess.Popen.

Arquitectura:

Proyecto.py
    │
    ├── subprocess.Popen ──→ chat_client.py "Cliente A"  (proceso 1)
    └── subprocess.Popen ──→ chat_client.py "Cliente B"  (proceso 2)

Ficheros de cola (en /tmp/chat_ipc_xxxx/):
    cola_a.txt  ←  Cliente A escribe  /  Cliente B lee
    cola_b.txt  ←  Cliente B escribe  /  Cliente A lee

Protocolo de mensajes: Cada mensaje se almacena como una línea de texto con el formato:

HH:MM|texto del mensaje

Polling: Cada cliente comprueba su fichero de entrada cada 300ms usando root.after(300, poll_incoming). Se usa root.after() en lugar de un hilo adicional porque programa la llamada dentro del propio hilo del mainloop de Tkinter, haciendo seguro actualizar los widgets directamente.

Paso de configuración: Toda la configuración de cada cliente (nombre, rutas de ficheros, posición de ventana) se pasa por argumentos de línea de comandos (sys.argv) al lanzar el subproceso.


Gestor de Correo

Cliente de correo electrónico completo integrado en la aplicación. Configurado para el servidor de clase en 10.10.0.101.

Protocolos utilizados:

Protocolo Puerto Uso
IMAP 143 Consulta y lectura de correos (correos permanecen en el servidor)
SMTP 25 Envío de correos (sin autenticación — relay interno)
POP3 110 Definido en configuración (disponible para uso futuro)

Flujo de navegación:

Login
  └─→ Bandeja de entrada (IMAP)
        └─→ Leer correo
              └─→ Responder  ──→  Formulario de redacción (SMTP)
  └─→ Nuevo correo            ──→  Formulario de redacción (SMTP)

Cada pantalla destruye los widgets de la anterior y construye los suyos propios dentro del mismo frame — navegación sin ventanas adicionales.

Carga en hilo separado: La descarga de correos de la bandeja se realiza en un threading.Thread(daemon=True) para evitar que la interfaz se congele durante la conexión al servidor.

Cierre blindado de conexiones IMAP: Todas las conexiones IMAP usan un bloque finally con doble red de seguridad para garantizar que la conexión se cierra siempre, previniendo la acumulación de conexiones zombi que podrían provocar el bloqueo temporal de la IP por parte del servidor:

finally:
    if conn is not None:
        try:
            conn.logout()      # Cierre limpio
        except Exception:
            try:
                conn.shutdown()  # Cierre forzado del socket
            except Exception:
                pass             # El GC cerrará el socket

Decodificación de cabeceras: La función decode_str() decodifica cabeceras de email en base64 o quoted-printable (asunto, remitente) para mostrarlas correctamente con tildes y caracteres especiales.


Arquitectura técnica

┌─────────────────────────────────────────────────────┐
│                    Proyecto.py                       │
│                                                      │
│  ┌─────────┐  ┌─────────┐  ┌──────────────────────┐ │
│  │   T1    │  │   T2    │  │         T3           │ │
│  │Procesos │  │ Hilos   │  │  ┌──────┬──────────┐ │ │
│  │         │  │         │  │  │ Chat │  Correo  │ │ │
│  │Estado   │  │Reloj    │  │  └──────┴──────────┘ │ │
│  │Editor   │  │Alarma   │  │         │            │ │
│  │Hilo red │  │Coches   │  │  subprocess.Popen    │ │
│  └─────────┘  │Scraping │  └──────────────────────┘ │
│               └─────────┘           │               │
└─────────────────────────────────────│───────────────┘
                                       │
                    ┌──────────────────┴──────────────────┐
                    │           chat_client.py             │
                    │  (proceso independiente × 2)         │
                    │  Comunicación: ficheros de cola       │
                    └──────────────────────────────────────┘

Gestión de hilos: Cada módulo tiene su propio stop_event (threading.Event) para detener los hilos de forma limpia al cambiar de pestaña. La función navigate_to_tab() se encarga de llamar a todas las funciones de parada antes de inicializar la nueva pestaña.


Decisiones de diseño destacadas

Procesos en lugar de hilos para el chat Tkinter no es thread-safe para múltiples ventanas. Usar subprocess.Popen con chat_client.py como script independiente evita completamente el problema de compartir el estado de X11.

IPC por ficheros en lugar de sockets o pipes Los ficheros de texto plano como canal de comunicación son simples, depurables (se puede leer el fichero directamente) y no requieren gestión de conexiones ni sincronización con semáforos.

root.after() para el polling Usar el scheduler interno de Tkinter en lugar de un hilo de polling mantiene todas las actualizaciones de widgets en el hilo principal del mainloop, evitando condiciones de carrera.

finally blindado en IMAP Lección aprendida durante el desarrollo: sin cerrar explícitamente las conexiones IMAP, las conexiones zombi se acumulan hasta que el servidor bloquea la IP. El bloque finally con doble red de seguridad (logoutshutdownpass) garantiza la liberación del recurso en cualquier escenario.

Navegación por destrucción de widgets En lugar de mantener múltiples frames ocultos/visibles, cada función de pantalla destruye el contenido anterior del frame padre con winfo_children() + destroy(). Esto simplifica la gestión de estado y evita widgets huérfanos consumiendo memoria.