Final sin video

This commit is contained in:
Kevin William Olarte Braun 2025-02-20 22:54:53 +01:00
parent 9cd3eb56a7
commit b9aef3fee3
8 changed files with 210 additions and 49 deletions

View File

@ -5,29 +5,47 @@ from server import start_server
class ControladorChat: class ControladorChat:
def __init__(self, vista): def __init__(self, vista):
self.vista = vista self.vista = vista
self.modelo = ModeloCliente() self.modelos = {}
def iniciar_servidor(self): def iniciar_servidor(self):
"""Inicia el servidor en un hilo separado.""" """Inicia el servidor en un hilo separado."""
threading.Thread(target=start_server, daemon=True).start() threading.Thread(target=start_server, daemon=True).start()
self.vista.mostrar_mensaje("[SERVIDOR] Servidor iniciado en segundo plano...\n") #vista.mostrar_mensaje("[SERVIDOR] Servidor iniciado en segundo plano...\n")
def enviar_mensaje(self, host):
"""Envía un mensaje al servidor y lo muestra en el chat."""
if host in self.modelos:
mensaje = self.vista.chats_frames[host]["entry"].get()
if mensaje:
self.vista.mostrar_mensaje(host, f"[TÚ] {mensaje}")
self.modelos[host].enviar_mensaje(mensaje)
self.vista.chats_frames[host]["entry"].delete(0, 'end')
def actualizar_host_port(self):
"""Obtiene valores de host y port desde la interfaz y los actualiza en el modelo."""
host = self.vista.host_entry.get()
port = int(self.vista.port_entry.get())
self.modelo.set_host_port(host, port)
self.vista.mostrar_mensaje(f"[CONFIG] Host y puerto actualizados: {host}:{port}")
def conectar_cliente(self): def conectar_cliente(self):
"""Intenta conectar el cliente al servidor.""" """Conecta un nuevo cliente y agrega su botón en el menú."""
mensaje = self.modelo.conectar( host = self.vista.host_entry.get()
on_message_received=self.vista.mostrar_mensaje, port = int(self.vista.port_entry.get())
on_error=self.vista.mostrar_mensaje
)
self.vista.mostrar_mensaje(mensaje)
if self.modelo.connected:
self.vista.habilitar_envio()
def enviar_mensaje(self): if host not in self.modelos:
"""Obtiene el mensaje de la vista y lo envía al servidor, además lo imprime en la interfaz.""" modelo = ModeloCliente()
mensaje = self.vista.message_entry.get() modelo.set_host_port(host, port)
if mensaje: mensaje = modelo.conectar(
self.vista.mostrar_mensaje(f"[TÚ] {mensaje}") # Agregar el mensaje a la vista on_message_received=lambda msg: self.vista.mostrar_mensaje(host, msg),
error = self.modelo.enviar_mensaje(mensaje) on_error=lambda err: self.vista.mostrar_mensaje(host, err)
if error: )
self.vista.mostrar_mensaje(error)
self.vista.message_entry.delete(0, 'end') # Limpiar el campo de entrada if modelo.connected:
self.modelos[host] = modelo
self.vista.agregar_boton_chat(host)
self.vista.habilitar_envio(host)
self.vista.mostrar_mensaje(host, mensaje)

View File

@ -1,26 +1,30 @@
import socket import socket
import threading import threading
SERVER_HOST = '127.0.0.1'
SERVER_PORT = 3333
class ModeloCliente: class ModeloCliente:
def __init__(self): def __init__(self):
self.client_socket = None self.client_socket = None
self.connected = False self.connected = False
self.host = '127.0.0.1' # Valores por defecto
self.port = 3333
def set_host_port(self, host, port):
"""Actualiza los valores de host y port."""
self.host = host
self.port = port
def conectar(self, on_message_received, on_error): def conectar(self, on_message_received, on_error):
"""Conecta el cliente al servidor.""" """Conecta el cliente al servidor con el host y puerto definidos."""
if self.connected: if self.connected:
on_error("[CLIENTE] Ya estás conectado al servidor.\n") on_error("[CLIENTE] Ya estás conectado al servidor.\n")
return return
self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try: try:
self.client_socket.connect((SERVER_HOST, SERVER_PORT)) self.client_socket.connect((self.host, self.port))
self.connected = True self.connected = True
threading.Thread(target=self.recibir_mensajes, args=(on_message_received, on_error), daemon=True).start() threading.Thread(target=self.recibir_mensajes, args=(on_message_received, on_error), daemon=True).start()
return f"[CONECTADO] Conectado al servidor {SERVER_HOST}:{SERVER_PORT}\n" return f"[CONECTADO] Conectado al servidor {self.host}:{self.port}\n"
except ConnectionRefusedError: except ConnectionRefusedError:
self.client_socket = None self.client_socket = None
return "[ERROR] El servidor no está disponible. Inícialo primero.\n" return "[ERROR] El servidor no está disponible. Inícialo primero.\n"

View File

@ -0,0 +1,38 @@
# 🗨️ Chat Cliente-Servidor en Python
Este proyecto es una aplicación de chat basada en una arquitectura cliente-servidor utilizando Python. Implementa `socket` para la comunicación en red, `threading` para manejar múltiples clientes simultáneamente y `tkinter` para la interfaz gráfica. Permite a los usuarios conectarse a un servidor de chat y enviar mensajes en tiempo real.
## 📌 Características
- ✅ **Servidor multicliente** basado en `socket` y `threading`.
- ✅ **Clientes con interfaz gráfica** (Tkinter) para conectarse y chatear.
- ✅ **Interfaz amigable** con opciones de conexión y envío de mensajes.
- ✅ **Soporte para múltiples clientes** en una misma sesión de chat.
- ✅ **Servidor ejecutable en segundo plano** desde la interfaz del cliente.
## 🔧 Dependencias
Este proyecto usa módulos estándar de Python, por lo que no es necesario instalar paquetes adicionales. Sin embargo, se recomienda usar un entorno virtual para mantener el aislamiento del proyecto.
Módulos utilizados:
- [`socket`](https://docs.python.org/3/library/socket.html) - Para la comunicación en red.
- [`threading`](https://docs.python.org/3/library/threading.html) - Para manejar múltiples clientes en paralelo.
- [`tkinter`](https://docs.python.org/3/library/tkinter.html) - Para la interfaz gráfica de la aplicación.
## 🎥 Video Tutorial
[![Ver en YouTube](https://img.youtube.com/vi/VIDEO_ID/maxresdefault.jpg)](https://youtu.be/GvhLM6T-Zhg)
## 🚀 Instalación y Ejecución
### 1⃣ Clonar el repositorio
```bash
git git clone https://git.ieslamar.org/DonWilliam/ChatPersonas.git
cd ChatPersonas

149
Vista.py
View File

@ -1,43 +1,144 @@
import tkinter as tk import tkinter as tk
from tkinter import scrolledtext from tkinter import scrolledtext
from server import start_server
class VistaChat: class VistaChat:
def __init__(self, root, controlador): def __init__(self, root, controlador):
self.root = root self.root = root
self.root.title("Chat Cliente-Servidor") self.root.title("Chat Cliente-Servidor")
self.root.geometry("500x400") # Establece tamaño fijo
self.root.resizable(False, False) # Bloquea el redimensionamiento
self.root.configure(bg="#2C3E50")
self.controlador = controlador self.controlador = controlador
# Botón para iniciar el servidor # Estilos de los botones
self.server_button = tk.Button(root, text="Iniciar Servidor", command=self.controlador.iniciar_servidor) btn_style = {"font": ("Arial", 10, "bold"), "fg": "white", "bg": "#2980B9", "bd": 0, "relief": "flat"}
self.server_button.grid(row=0, column=0, padx=10, pady=10)
# Botón para conectar el cliente # FRAME LATERAL PARA EL MENÚ
self.connect_button = tk.Button(root, text="Conectar Cliente", command=self.controlador.conectar_cliente) self.menu_frame = tk.Frame(root, width=100, bg="#34495E")
self.connect_button.grid(row=0, column=1, padx=10, pady=10) self.menu_frame.grid(row=0, column=0, rowspan=3, sticky="ns")
# Área de chat #self.button_inicio = tk.Button(self.menu_frame, text="Inicio", command=self.mostrar_inicio, width=12, **btn_style)
self.chat_display = scrolledtext.ScrolledText(root, wrap=tk.WORD, state='disabled', width=50, height=20) #self.button_inicio.pack(pady=10)
self.chat_display.grid(row=1, column=0, padx=10, pady=10, columnspan=2)
# Campo de entrada de mensajes self.button_conf = tk.Button(self.menu_frame, text="Conf", command=self.mostrar_config, width=12, **btn_style)
self.message_entry = tk.Entry(root, width=40) self.button_conf.pack(pady=10)
self.message_entry.grid(row=2, column=0, padx=10, pady=10)
# Botón para enviar mensajes # Almacenar botones de clientes conectados
self.send_button = tk.Button(root, text="Enviar", command=self.controlador.enviar_mensaje, state='disabled') self.chat_buttons = {}
self.send_button.grid(row=2, column=1, padx=10, pady=10) self.chats_frames = {}
def mostrar_mensaje(self, mensaje): # FRAME PRINCIPAL DONDE SE MUESTRA EL CHAT O LA CONFIGURACIÓN
"""Muestra un mensaje en el chat.""" self.main_frame = tk.Frame(root, bg="#2C3E50")
self.chat_display.config(state='normal') self.main_frame.grid(row=0, column=1, padx=10, pady=10, rowspan=3)
self.chat_display.insert(tk.END, mensaje + '\n')
self.chat_display.config(state='disabled')
self.chat_display.yview(tk.END)
def habilitar_envio(self): # INICIALIZA LOS CHATS Y CONFIGURACIÓN
"""Habilita el botón de enviar mensajes.""" self.inicializar_config()
self.send_button.config(state='normal') #self.inicializar_chat("127.0.0.1") # Chat local por defecto
#self.mostrar_chat("127.0.0.1")
def inicializar_chat(self, host):
"""Crea un nuevo chat con un host específico."""
chat_frame = tk.Frame(self.main_frame, bg="#2C3E50")
chat_display = scrolledtext.ScrolledText(chat_frame, wrap=tk.WORD, state='disabled', width=50, height=15, bg="#ECF0F1", fg="black")
chat_display.pack(padx=10, pady=10)
entry_frame = tk.Frame(chat_frame, bg="#2C3E50")
entry_frame.pack(pady=5)
message_entry = tk.Entry(entry_frame, width=40, font=("Arial", 10))
message_entry.pack(side=tk.LEFT, padx=10, pady=5)
send_button = tk.Button(entry_frame, text="Enviar", command=lambda: self.controlador.enviar_mensaje(host), state='disabled', font=("Arial", 10, "bold"), fg="white", bg="#27AE60", bd=0, relief="flat")
send_button.pack(side=tk.RIGHT, padx=5, pady=5)
self.chats_frames[host] = {
"frame": chat_frame,
"display": chat_display,
"entry": message_entry,
"send_button": send_button
}
def inicializar_config(self):
"""Configura la interfaz de configuración."""
self.config_frame = tk.Frame(self.main_frame, bg="#2C3E50")
tk.Label(self.config_frame, text="HOST:", bg="#2C3E50", fg="white", font=("Arial", 10, "bold")).pack(pady=2)
self.host_entry = tk.Entry(self.config_frame, width=20, font=("Arial", 10))
self.host_entry.pack(pady=5)
self.host_entry.insert(0, "127.0.0.1")
tk.Label(self.config_frame, text="PORT:", bg="#2C3E50", fg="white", font=("Arial", 10, "bold")).pack(pady=2)
self.port_entry = tk.Entry(self.config_frame, width=10, font=("Arial", 10))
self.port_entry.pack(pady=5)
self.port_entry.insert(0, "3333")
btn_style = {"font": ("Arial", 10, "bold"), "fg": "white", "bg": "#2980B9", "bd": 0, "relief": "flat"}
self.connect_button = tk.Button(self.config_frame, text="Conectar Cliente", command=self.controlador.conectar_cliente, **btn_style)
self.connect_button.pack(pady=10)
self.start_server = tk.Button(self.config_frame, text="Abrir servidor", command=self.controlador.iniciar_servidor, **btn_style)
self.start_server.pack(pady=20)
def mostrar_inicio(self):
"""Muestra la pantalla de chat predeterminado (127.0.0.1)."""
self.config_frame.pack_forget()
if "127.0.0.1" in self.chats_frames:
self.chats_frames["127.0.0.1"]["frame"].pack()
def mostrar_chat(self, host):
"""Muestra un chat específico y oculta los anteriores, incluyendo la configuración."""
# Ocultar la configuración si está visible
self.config_frame.pack_forget()
# Ocultar todos los chats antes de mostrar el nuevo
for chat_host, chat in self.chats_frames.items():
chat["frame"].pack_forget()
# Mostrar el chat seleccionado
if host in self.chats_frames:
self.chats_frames[host]["frame"].pack()
def mostrar_config(self):
"""Muestra la pantalla de configuración y oculta cualquier chat abierto."""
# Ocultar todos los chats
for chat in self.chats_frames.values():
chat["frame"].pack_forget()
# Mostrar la configuración
self.config_frame.pack()
def mostrar_mensaje(self, host, mensaje):
"""Muestra un mensaje en el chat correspondiente."""
if host in self.chats_frames:
chat_display = self.chats_frames[host]["display"]
chat_display.config(state='normal')
chat_display.insert(tk.END, mensaje + '\n')
chat_display.config(state='disabled')
chat_display.yview(tk.END)
def habilitar_envio(self, host):
"""Habilita el botón de enviar mensajes para el chat correspondiente."""
if host in self.chats_frames:
self.chats_frames[host]["send_button"].config(state='normal')
def deshabilitar_envio(self): def deshabilitar_envio(self):
"""Deshabilita el botón de enviar mensajes.""" """Deshabilita el botón de enviar mensajes."""
self.send_button.config(state='disabled') self.send_button.config(state='disabled')
def agregar_boton_chat(self, host):
"""Agrega un nuevo botón al menú lateral cuando se conecta un nuevo cliente."""
if host not in self.chat_buttons:
btn_style = {"font": ("Arial", 10, "bold"), "fg": "white", "bg": "#2980B9", "bd": 0, "relief": "flat"}
chat_button = tk.Button(self.menu_frame, text=f"Chat {host}", command=lambda: self.mostrar_chat(host), width=12, **btn_style)
chat_button.pack(pady=5)
self.chat_buttons[host] = chat_button
self.inicializar_chat(host)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.