Table of Content
1. Introduction
A TCP socket-based chat application allows multiple clients to communicate with a central server in real time. This tutorial walks through building a multi-client chat application using Python’s socket
and threading
modules for backend communication and Tkinter
for a graphical user interface (GUI). This is a great project to learn about networking, concurrency, and GUI development.
2. Technical Breakdown
Understanding Sockets in Python
Sockets enable network communication between processes. Python’s socket
module provides the necessary tools to create a server-client architecture where clients send and receive messages through a central server.
Server-Client Architecture for Multi-Client Setup
- Server: Listens for incoming connections and handles multiple clients simultaneously.
- Clients: Connect to the server, send messages, and receive broadcasts from other clients.
Implementing Threading for Multiple Clients
Since a single-threaded server would block while handling one client, we use Python’s threading
module to allow concurrent client connections.
Developing a Tkinter-Based GUI
A simple Tkinter
interface will provide a user-friendly way to send and receive messages.
3. Diagrams & Visuals
Network Architecture
Message Flowchart
4. Code Walkthrough
Server.py
import socket # Importing socket for network communication
import threading # Importing threading to handle multiple clients concurrently
import tkinter as tk # Importing Tkinter for GUI
from tkinter import scrolledtext # Importing scrolledtext for displaying chat messages
class ChatServer:
def __init__(self, root):
self.root = root
self.root.title("Server Chat") # Setting the window title
# Creating a server socket (IPv4, TCP)
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Binding the server to localhost (127.0.0.1) on port 8888
self.server_socket.bind(("127.0.0.1", 8888))
# Listening for up to 5 simultaneous connections
self.server_socket.listen(5)
# List to keep track of connected clients
self.clients = []
# Starting a background thread to accept new clients
threading.Thread(target=self.accept_clients, daemon=True).start()
# Creating a scrollable text widget to display chat messages
self.chat_display = scrolledtext.ScrolledText(root, state='disabled', width=50, height=15)
self.chat_display.pack(pady=10)
def accept_clients(self):
"""Accepts incoming client connections and starts a new thread for each."""
while True:
client_socket, addr = self.server_socket.accept() # Accept client connection
self.clients.append(client_socket) # Add the client to the list
threading.Thread(target=self.handle_client, args=(client_socket,)).start() # Start handling client
def handle_client(self, client_socket):
"""Handles receiving messages from a client and broadcasting them."""
while True:
try:
# Receive message from the client (max 1024 bytes)
message = client_socket.recv(1024).decode("utf-8")
# If the client disconnects, exit the loop
if not message:
break
# Send the received message to all other clients
self.broadcast(message, client_socket)
except: # If an error occurs (e.g., client disconnects)
self.clients.remove(client_socket) # Remove client from the list
client_socket.close() # Close the connection
break # Exit loop
def broadcast(self, message, sender_socket):
"""Sends received messages to all clients except the sender."""
for client in self.clients:
if client != sender_socket: # Ensure the sender doesn't receive their own message
client.send(message.encode("utf-8")) # Send message to client
# Display the message in the server GUI
self.chat_display.config(state='normal') # Enable text editing
self.chat_display.insert(tk.END, message + "\n") # Insert message
self.chat_display.config(state='disabled') # Disable text editing
self.chat_display.yview(tk.END) # Scroll to the latest message
if __name__ == "__main__":
root = tk.Tk() # Create the main Tkinter window
server = ChatServer(root) # Initialize and start the chat server
root.mainloop() # Run the Tkinter event loop
Client.py
import socket # Importing socket for network communication
import threading # Importing threading to handle receiving messages concurrently
import tkinter as tk # Importing Tkinter for GUI
from tkinter import scrolledtext, messagebox # Importing scrolledtext for chat display, messagebox for alerts
class ChatClient:
def __init__(self, root):
self.root = root
self.root.title("Client Chat") # Setting the window title
self.client_socket = None # Will hold the socket connection
self.running = False # Tracks whether the client is connected
# GUI Elements
self.username_label = tk.Label(root, text="Username:") # Username label
self.username_label.pack()
self.username_entry = tk.Entry(root) # Input field for username
self.username_entry.pack()
self.connect_button = tk.Button(root, text="Connect", command=self.connect_to_server) # Connect button
self.connect_button.pack(pady=5)
self.chat_display = scrolledtext.ScrolledText(root, state='disabled', width=50, height=15) # Chat display area
self.chat_display.pack(pady=10)
self.message_entry = tk.Entry(root, width=40) # Input field for messages
self.message_entry.pack(pady=5)
self.send_button = tk.Button(root, text="Send", command=self.send_message) # Send button
self.send_button.pack(pady=5)
self.disconnect_button = tk.Button(root, text="Disconnect", command=self.disconnect) # Disconnect button
self.disconnect_button.pack(pady=5)
def connect_to_server(self):
"""Establishes a connection to the chat server."""
if self.running:
messagebox.showwarning("Warning", "Already connected!")
return
username = self.username_entry.get().strip() # Get username from input
if not username:
messagebox.showerror("Error", "Username is required!") # Show error if username is empty
return
try:
self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # Create a TCP socket
self.client_socket.connect(("127.0.0.1", 8888)) # Connect to the server
self.client_socket.send((username + " joined the chat!").encode("utf-8")) # Send username as the first message
self.running = True
# Start a background thread to receive messages
threading.Thread(target=self.receive_messages, daemon=True).start()
# Update chat display to show successful connection
self.chat_display.config(state='normal')
self.chat_display.insert(tk.END, f"Connected to 127.0.0.1:8888 as {username}\n")
self.chat_display.config(state='disabled')
except Exception as e:
messagebox.showerror("Error", f"Connection failed: {e}") # Show error message if connection fails
def receive_messages(self):
"""Continuously receives and displays messages from the server."""
while self.running:
try:
message = self.client_socket.recv(1024).decode("utf-8") # Receive message (max 1024 bytes)
if not message: # If message is empty, exit loop
break
self.chat_display.config(state='normal')
# Display received message in chat window
self.chat_display.insert(tk.END, message + "\n")
self.chat_display.config(state='disabled')
self.chat_display.yview(tk.END) # Auto-scroll to the latest message
except ConnectionResetError: # If the server disconnects
self.chat_display.config(state='normal')
self.chat_display.insert(tk.END, "Server disconnected.\n")
self.chat_display.config(state='disabled')
self.running = False
break # Exit loop
def send_message(self):
"""Sends a message to the server."""
if not self.running:
messagebox.showerror("Error", "Not connected to any server!") # Show error if not connected
return
message = self.message_entry.get().strip() # Get the input message
self.chat_display.config(state='normal')
# Show the user's message in the chat window
self.chat_display.insert(tk.END, str(self.username_entry.get()) + "(You): " + message + "\n")
self.chat_display.config(state='disabled')
self.chat_display.yview(tk.END) # Auto-scroll to latest message
if message: # If message is not empty
try:
# Send the message to the server, prefixed with the username
self.client_socket.send((str(self.username_entry.get()) + ": " + message).encode("utf-8"))
self.message_entry.delete(0, tk.END) # Clear the input field after sending
except Exception as e:
messagebox.showerror("Error", f"Message sending failed: {e}") # Show error if sending fails
def disconnect(self):
"""Disconnects from the chat server."""
if not self.running:
messagebox.showwarning("Warning", "Already disconnected!") # Show warning if already disconnected
return
self.running = False # Set running to False
self.client_socket.close() # Close the socket connection
# Update chat display to show disconnection message
self.chat_display.config(state='normal')
self.chat_display.insert(tk.END, "Disconnected from server.\n")
self.chat_display.config(state='disabled')
if __name__ == "__main__":
root = tk.Tk() # Create the main Tkinter window
client = ChatClient(root) # Initialize and start the chat client
root.mainloop() # Run the Tkinter event loop
Top comments (0)