Final sin video
This commit is contained in:
parent
9cd3eb56a7
commit
b9aef3fee3
|
@ -5,29 +5,47 @@ from server import start_server
|
|||
class ControladorChat:
|
||||
def __init__(self, vista):
|
||||
self.vista = vista
|
||||
self.modelo = ModeloCliente()
|
||||
self.modelos = {}
|
||||
|
||||
def iniciar_servidor(self):
|
||||
"""Inicia el servidor en un hilo separado."""
|
||||
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):
|
||||
"""Intenta conectar el cliente al servidor."""
|
||||
mensaje = self.modelo.conectar(
|
||||
on_message_received=self.vista.mostrar_mensaje,
|
||||
on_error=self.vista.mostrar_mensaje
|
||||
)
|
||||
self.vista.mostrar_mensaje(mensaje)
|
||||
if self.modelo.connected:
|
||||
self.vista.habilitar_envio()
|
||||
"""Conecta un nuevo cliente y agrega su botón en el menú."""
|
||||
host = self.vista.host_entry.get()
|
||||
port = int(self.vista.port_entry.get())
|
||||
|
||||
def enviar_mensaje(self):
|
||||
"""Obtiene el mensaje de la vista y lo envía al servidor, además lo imprime en la interfaz."""
|
||||
mensaje = self.vista.message_entry.get()
|
||||
if mensaje:
|
||||
self.vista.mostrar_mensaje(f"[TÚ] {mensaje}") # Agregar el mensaje a la vista
|
||||
error = self.modelo.enviar_mensaje(mensaje)
|
||||
if error:
|
||||
self.vista.mostrar_mensaje(error)
|
||||
self.vista.message_entry.delete(0, 'end') # Limpiar el campo de entrada
|
||||
if host not in self.modelos:
|
||||
modelo = ModeloCliente()
|
||||
modelo.set_host_port(host, port)
|
||||
mensaje = modelo.conectar(
|
||||
on_message_received=lambda msg: self.vista.mostrar_mensaje(host, msg),
|
||||
on_error=lambda err: self.vista.mostrar_mensaje(host, err)
|
||||
)
|
||||
|
||||
if modelo.connected:
|
||||
self.modelos[host] = modelo
|
||||
self.vista.agregar_boton_chat(host)
|
||||
self.vista.habilitar_envio(host)
|
||||
|
||||
self.vista.mostrar_mensaje(host, mensaje)
|
16
Modelo.py
16
Modelo.py
|
@ -1,26 +1,30 @@
|
|||
import socket
|
||||
import threading
|
||||
|
||||
SERVER_HOST = '127.0.0.1'
|
||||
SERVER_PORT = 3333
|
||||
|
||||
class ModeloCliente:
|
||||
def __init__(self):
|
||||
self.client_socket = None
|
||||
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):
|
||||
"""Conecta el cliente al servidor."""
|
||||
"""Conecta el cliente al servidor con el host y puerto definidos."""
|
||||
if self.connected:
|
||||
on_error("[CLIENTE] Ya estás conectado al servidor.\n")
|
||||
return
|
||||
|
||||
self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
try:
|
||||
self.client_socket.connect((SERVER_HOST, SERVER_PORT))
|
||||
self.client_socket.connect((self.host, self.port))
|
||||
self.connected = True
|
||||
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:
|
||||
self.client_socket = None
|
||||
return "[ERROR] El servidor no está disponible. Inícialo primero.\n"
|
||||
|
|
38
README.md
38
README.md
|
@ -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
|
||||
|
||||
[](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
149
Vista.py
|
@ -1,43 +1,144 @@
|
|||
import tkinter as tk
|
||||
from tkinter import scrolledtext
|
||||
from server import start_server
|
||||
|
||||
class VistaChat:
|
||||
def __init__(self, root, controlador):
|
||||
self.root = root
|
||||
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
|
||||
|
||||
# Botón para iniciar el servidor
|
||||
self.server_button = tk.Button(root, text="Iniciar Servidor", command=self.controlador.iniciar_servidor)
|
||||
self.server_button.grid(row=0, column=0, padx=10, pady=10)
|
||||
# Estilos de los botones
|
||||
btn_style = {"font": ("Arial", 10, "bold"), "fg": "white", "bg": "#2980B9", "bd": 0, "relief": "flat"}
|
||||
|
||||
# Botón para conectar el cliente
|
||||
self.connect_button = tk.Button(root, text="Conectar Cliente", command=self.controlador.conectar_cliente)
|
||||
self.connect_button.grid(row=0, column=1, padx=10, pady=10)
|
||||
# FRAME LATERAL PARA EL MENÚ
|
||||
self.menu_frame = tk.Frame(root, width=100, bg="#34495E")
|
||||
self.menu_frame.grid(row=0, column=0, rowspan=3, sticky="ns")
|
||||
|
||||
# Área de chat
|
||||
self.chat_display = scrolledtext.ScrolledText(root, wrap=tk.WORD, state='disabled', width=50, height=20)
|
||||
self.chat_display.grid(row=1, column=0, padx=10, pady=10, columnspan=2)
|
||||
#self.button_inicio = tk.Button(self.menu_frame, text="Inicio", command=self.mostrar_inicio, width=12, **btn_style)
|
||||
#self.button_inicio.pack(pady=10)
|
||||
|
||||
# Campo de entrada de mensajes
|
||||
self.message_entry = tk.Entry(root, width=40)
|
||||
self.message_entry.grid(row=2, column=0, padx=10, pady=10)
|
||||
self.button_conf = tk.Button(self.menu_frame, text="Conf", command=self.mostrar_config, width=12, **btn_style)
|
||||
self.button_conf.pack(pady=10)
|
||||
|
||||
# Botón para enviar mensajes
|
||||
self.send_button = tk.Button(root, text="Enviar", command=self.controlador.enviar_mensaje, state='disabled')
|
||||
self.send_button.grid(row=2, column=1, padx=10, pady=10)
|
||||
# Almacenar botones de clientes conectados
|
||||
self.chat_buttons = {}
|
||||
self.chats_frames = {}
|
||||
|
||||
# FRAME PRINCIPAL DONDE SE MUESTRA EL CHAT O LA CONFIGURACIÓN
|
||||
self.main_frame = tk.Frame(root, bg="#2C3E50")
|
||||
self.main_frame.grid(row=0, column=1, padx=10, pady=10, rowspan=3)
|
||||
|
||||
def mostrar_mensaje(self, mensaje):
|
||||
"""Muestra un mensaje en el chat."""
|
||||
self.chat_display.config(state='normal')
|
||||
self.chat_display.insert(tk.END, mensaje + '\n')
|
||||
self.chat_display.config(state='disabled')
|
||||
self.chat_display.yview(tk.END)
|
||||
# INICIALIZA LOS CHATS Y CONFIGURACIÓN
|
||||
self.inicializar_config()
|
||||
#self.inicializar_chat("127.0.0.1") # Chat local por defecto
|
||||
#self.mostrar_chat("127.0.0.1")
|
||||
|
||||
|
||||
|
||||
def habilitar_envio(self):
|
||||
"""Habilita el botón de enviar mensajes."""
|
||||
self.send_button.config(state='normal')
|
||||
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):
|
||||
"""Deshabilita el botón de enviar mensajes."""
|
||||
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.
Loading…
Reference in New Issue