mirror of
https://github.com/BreizhHardware/py_CIPA3.git
synced 2026-01-18 16:37:30 +01:00
add client-server architecture with GUI and message handling improvements
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -5,4 +5,7 @@
|
||||
.venv/*
|
||||
|
||||
.winvenv
|
||||
.winvenv/*
|
||||
.winvenv/*
|
||||
|
||||
.macvenv
|
||||
.macvenv/*
|
||||
@@ -9,6 +9,7 @@ class Client:
|
||||
self.server_port = server_port
|
||||
self.socket = socket.socket()
|
||||
self.running = True
|
||||
self.callbacks = [] # List of callback functions for received messages
|
||||
|
||||
try:
|
||||
self.socket.connect((self.server_address, self.server_port))
|
||||
@@ -24,47 +25,63 @@ class Client:
|
||||
except Exception as e:
|
||||
print(f"Error sending message: {e}")
|
||||
self.running = False
|
||||
raise e
|
||||
|
||||
def shutdown(self):
|
||||
"""Shutdown the socket communication."""
|
||||
try:
|
||||
self.running = False
|
||||
self.socket.shutdown(2)
|
||||
self.socket.shutdown(socket.SHUT_RDWR)
|
||||
except Exception as e:
|
||||
print(f"Error during shutdown: {e}")
|
||||
|
||||
def close(self):
|
||||
"""Close the connection and wait for listening thread to end."""
|
||||
if self.listening_thread:
|
||||
if hasattr(self, "listening_thread"):
|
||||
self.listening_thread.join()
|
||||
try:
|
||||
self.socket.close()
|
||||
except Exception as e:
|
||||
print(f"Error closing socket: {e}")
|
||||
|
||||
def register_callback(self, callback):
|
||||
"""Register a callback function for received messages."""
|
||||
self.callbacks.append(callback)
|
||||
|
||||
def listen_messages(self):
|
||||
"""Listen for incoming messages from the server."""
|
||||
while self.running:
|
||||
try:
|
||||
msg = self.socket.recv(1024)
|
||||
if msg:
|
||||
print(f"Message received: {msg.decode()}")
|
||||
decoded_msg = msg.decode()
|
||||
# Notify all registered callbacks
|
||||
for callback in self.callbacks:
|
||||
callback(f"{decoded_msg}\n")
|
||||
else:
|
||||
break
|
||||
except Exception as e:
|
||||
print(f"Error receiving message: {e}")
|
||||
if self.running: # Only print error if not shutting down
|
||||
print(f"Error receiving message: {e}")
|
||||
break
|
||||
self.running = False
|
||||
|
||||
|
||||
def main():
|
||||
"""Test client in console mode."""
|
||||
client = Client("localhost", 6060)
|
||||
|
||||
def print_message(msg: str):
|
||||
"""Simple callback to print received messages."""
|
||||
print(f"Received: {msg}", end="")
|
||||
|
||||
client.register_callback(print_message)
|
||||
|
||||
try:
|
||||
while client.running:
|
||||
message = input("Enter message (or Ctrl+C to quit): ")
|
||||
client.send(message)
|
||||
if message.lower() == "stop":
|
||||
if message.lower() == "quit":
|
||||
break
|
||||
except KeyboardInterrupt:
|
||||
print("\nClosing connection...")
|
||||
|
||||
54
TP10/10.2/client.py
Normal file
54
TP10/10.2/client.py
Normal file
@@ -0,0 +1,54 @@
|
||||
import socket
|
||||
import threading
|
||||
|
||||
|
||||
class Client:
|
||||
def __init__(self, host, port):
|
||||
"""Initialize client and connect to server."""
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.socket.connect((host, port))
|
||||
self.running = True
|
||||
self.callback = None
|
||||
|
||||
def register_callback(self, callback):
|
||||
"""Register a callback function to handle received messages.
|
||||
|
||||
Args:
|
||||
callback (callable): Function to call when messages are received
|
||||
"""
|
||||
self.callback = callback
|
||||
# Start a thread to listen for incoming messages
|
||||
threading.Thread(target=self.receive_messages, daemon=True).start()
|
||||
|
||||
def receive_messages(self):
|
||||
"""Listen for incoming messages and process them."""
|
||||
while self.running:
|
||||
try:
|
||||
data = self.socket.recv(1024)
|
||||
if data:
|
||||
message = data.decode("utf-8")
|
||||
if self.callback:
|
||||
self.callback(message)
|
||||
else:
|
||||
# Empty data means server closed connection
|
||||
if self.callback:
|
||||
self.callback("Disconnected from server\n")
|
||||
break
|
||||
except Exception as e:
|
||||
if self.running and self.callback: # Only show error if still running
|
||||
self.callback(f"Error receiving message: {e}\n")
|
||||
break
|
||||
|
||||
def send(self, message):
|
||||
"""Send a message to the server."""
|
||||
self.socket.send(message.encode("utf-8"))
|
||||
|
||||
def shutdown(self):
|
||||
"""Signal the receiving thread to stop."""
|
||||
self.running = False
|
||||
|
||||
def close(self):
|
||||
"""Close the socket connection."""
|
||||
self.socket.close()
|
||||
103
TP10/10.2/gui.py
Normal file
103
TP10/10.2/gui.py
Normal file
@@ -0,0 +1,103 @@
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from client import Client
|
||||
import datetime
|
||||
|
||||
|
||||
class ClientWindow:
|
||||
def __init__(self, root: tk.Tk):
|
||||
"""Initialize the chat window."""
|
||||
self.root = root
|
||||
self.root.title("Chat Client")
|
||||
self.root.geometry("600x400")
|
||||
|
||||
self.client = None
|
||||
|
||||
# Server connection fields
|
||||
self.address = tk.StringVar(value="localhost")
|
||||
self.port = tk.StringVar(value="6060")
|
||||
|
||||
# Address field
|
||||
address_label = ttk.Label(root, text="Address:")
|
||||
address_label.grid(column=0, row=0, padx=5, pady=5)
|
||||
address_entry = ttk.Entry(root, textvariable=self.address)
|
||||
address_entry.grid(column=1, row=0, padx=5, pady=5)
|
||||
|
||||
# Port field
|
||||
port_label = ttk.Label(root, text="Port:")
|
||||
port_label.grid(column=2, row=0, padx=5, pady=5)
|
||||
port_entry = ttk.Entry(root, textvariable=self.port)
|
||||
port_entry.grid(column=3, row=0, padx=5, pady=5)
|
||||
|
||||
# Connect button
|
||||
self.connect_button = ttk.Button(root, text="Connect", command=self.connect)
|
||||
self.connect_button.grid(column=4, row=0, padx=5, pady=5)
|
||||
|
||||
# Messages display
|
||||
self.text_area = tk.Text(root, wrap=tk.WORD, width=50, height=20)
|
||||
self.text_area.grid(column=0, row=1, columnspan=5, padx=5, pady=5)
|
||||
self.text_area.config(state="disabled")
|
||||
|
||||
# Message input
|
||||
self.message = tk.StringVar()
|
||||
self.msg_entry = ttk.Entry(root, textvariable=self.message)
|
||||
self.msg_entry.grid(column=0, row=2, columnspan=5, padx=5, pady=5, sticky="ew")
|
||||
self.msg_entry.bind("<Return>", self.send)
|
||||
|
||||
# Configure grid weights
|
||||
root.grid_rowconfigure(1, weight=1)
|
||||
root.grid_columnconfigure(0, weight=1)
|
||||
|
||||
def connect(self):
|
||||
"""Connect to the server."""
|
||||
try:
|
||||
self.client = Client(self.address.get(), int(self.port.get()))
|
||||
if self.client: # Only register callback if client is created successfully
|
||||
self.client.register_callback(self.append_text)
|
||||
self.connect_button["text"] = "Disconnect"
|
||||
self.connect_button["command"] = self.disconnect
|
||||
self.append_text("Connected to server\n")
|
||||
except Exception as e:
|
||||
self.append_text(f"Connection error: {e}\n")
|
||||
self.client = None # Reset client on failed connection
|
||||
|
||||
def disconnect(self):
|
||||
"""Disconnect from the server."""
|
||||
if self.client:
|
||||
self.client.shutdown()
|
||||
self.client.close()
|
||||
self.client = None
|
||||
self.connect_button["text"] = "Connect"
|
||||
self.connect_button["command"] = self.connect
|
||||
self.append_text("Disconnected from server\n")
|
||||
|
||||
def send(self, event=None):
|
||||
"""Send message to server."""
|
||||
if self.client and self.message.get():
|
||||
try:
|
||||
self.client.send(self.message.get())
|
||||
self.message.set("") # Clear input field
|
||||
except Exception as e:
|
||||
self.append_text(f"Error sending message: {e}\n")
|
||||
|
||||
def append_text(self, message: str):
|
||||
"""Append text to message display area with timestamp."""
|
||||
self.text_area.config(state="normal")
|
||||
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
# Ensure the message ends with a newline
|
||||
if not message.endswith("\n"):
|
||||
message += "\n"
|
||||
|
||||
# Add timestamp and ensure double line break between messages
|
||||
formatted_message = f"[{timestamp}] {message}\n"
|
||||
|
||||
self.text_area.insert("end", formatted_message)
|
||||
self.text_area.see("end")
|
||||
self.text_area.config(state="disabled")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
root = tk.Tk()
|
||||
ClientWindow(root)
|
||||
root.mainloop()
|
||||
99
TP10/10.2/server.py
Normal file
99
TP10/10.2/server.py
Normal file
@@ -0,0 +1,99 @@
|
||||
import socket
|
||||
import threading
|
||||
|
||||
|
||||
class ClientThread(threading.Thread):
|
||||
def __init__(self, server, client_socket: socket.socket):
|
||||
"""Initialize client thread with server reference and socket."""
|
||||
super().__init__()
|
||||
self.server = server
|
||||
self.socket = client_socket
|
||||
|
||||
def send(self, msg: str):
|
||||
"""Send message to the client."""
|
||||
try:
|
||||
self.socket.send(msg.encode())
|
||||
except Exception as e:
|
||||
print(f"Error sending message to client: {e}")
|
||||
self.server.remove_client(self)
|
||||
|
||||
def shutdown(self):
|
||||
"""Shutdown the client socket."""
|
||||
try:
|
||||
self.socket.shutdown(socket.SHUT_RDWR)
|
||||
except Exception as e:
|
||||
print(f"Error shutting down client socket: {e}")
|
||||
|
||||
def close(self):
|
||||
"""Close the client socket."""
|
||||
try:
|
||||
self.socket.close()
|
||||
except Exception as e:
|
||||
print(f"Error closing client socket: {e}")
|
||||
|
||||
def run(self):
|
||||
"""Read messages from client continuously."""
|
||||
try:
|
||||
while True:
|
||||
msg = self.socket.recv(1024)
|
||||
if msg:
|
||||
self.server.send_to_all(msg)
|
||||
else:
|
||||
break
|
||||
except Exception as e:
|
||||
print(f"Error receiving message: {e}")
|
||||
finally:
|
||||
self.server.remove_client(self)
|
||||
self.close()
|
||||
|
||||
|
||||
class Server:
|
||||
def __init__(self, port: int = 12345):
|
||||
"""Initialize server with port."""
|
||||
self.port = port
|
||||
self.socket = socket.socket()
|
||||
self.clients = []
|
||||
|
||||
self.socket.bind(("", self.port))
|
||||
self.socket.listen()
|
||||
|
||||
def run(self):
|
||||
"""Accept client connections continuously."""
|
||||
print("Server is listening for connections...")
|
||||
try:
|
||||
while True:
|
||||
client_socket, addr = self.socket.accept()
|
||||
print(f"Connection from {addr}")
|
||||
client_thread = ClientThread(self, client_socket)
|
||||
self.clients.append(client_thread)
|
||||
client_thread.start()
|
||||
except KeyboardInterrupt:
|
||||
print("\nServer stopped")
|
||||
finally:
|
||||
for client in self.clients[:]:
|
||||
client.shutdown()
|
||||
client.close()
|
||||
self.socket.close()
|
||||
print("Server closed")
|
||||
|
||||
def send_to_all(self, message: bytes):
|
||||
"""Send message to all connected clients."""
|
||||
disconnected_clients = []
|
||||
for client in self.clients:
|
||||
try:
|
||||
client.send(message.decode())
|
||||
except Exception:
|
||||
disconnected_clients.append(client)
|
||||
|
||||
for client in disconnected_clients:
|
||||
self.remove_client(client)
|
||||
|
||||
def remove_client(self, client: ClientThread):
|
||||
"""Remove client from clients list."""
|
||||
if client in self.clients:
|
||||
self.clients.remove(client)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
s = Server(6060)
|
||||
s.run()
|
||||
Reference in New Issue
Block a user