Real-time communication with Python

Chatterbox

© Photo by Loan on Unsplash

© Photo by Loan on Unsplash

Article from Issue 299/2025
Author(s):

A beginner-friendly guide to building a chat client-server with sockets and multithreading.

Network communication is a fundamental aspect of modern applications, enabling real-time data exchange between distributed systems. In this article, I will explore the development of a simple yet functional chat application using Python, focusing on socket programming and multithreading.

Python is an excellent choice for this task due to its simplicity, extensive standard library, and built-in support for networking and concurrency. The socket module provides a straightforward way to establish TCP/IP connections, while the threading module allows you to manage multiple clients concurrently without blocking execution.

This project introduces key concepts in socket programming by implementing a server that listens for incoming client connections, manages them using threads, and facilitates message exchange. Each client connects to the server, sends messages, and receives real-time updates from other participants, including the server itself. The implementation also features a broadcast system to relay messages efficiently among connected clients. By understanding these mechanisms, you will gain practical experience in building real-time communication systems, laying a foundation for more complex networking applications.

VS Code Setup

To follow along with this article and fully understand the implementation of the chat application, it is essential to have a basic understanding of the Python programming language, including fundamental concepts such as functions, loops, and object-oriented programming. Additionally, familiarity with network programming concepts such as TCP/IP, sockets, and client-server architecture will be beneficial but not strictly necessary, because these topics will be introduced throughout the article. Because this project involves handling multiple clients concurrently, a basic grasp of multithreading in Python is also recommended. To write and run the code efficiently, I suggest using Visual Studio Code (VS Code), a lightweight yet powerful code editor that supports Python development with essential features such as IntelliSense, debugging, and integrated terminal support. VS Code can be installed via package managers such as apt for Debian-based distributions like Ubuntu:

sudo apt install code

Alternatively, you can download the .deb package from the official VS Code website and install it manually. To ensure Python is correctly set up, install it via your package manager:

sudo apt install python3

on Debian/Ubuntu and verify the installation by running

python3 --version

in the terminal. Additionally, installing extensions such as Pylance (for improved IntelliSense and type checking), Python Indent (for better indentation handling), and autoDocstring (for automatic documentation generation) can significantly enhance the development experience. A terminal emulator such as a standard Linux terminal will be required to run the chat server and clients. With these prerequisites in place, you will be ready to dive into the implementation and understand how to build a simple yet functional chat application using Python, sockets, and multithreading.

Code Architecture and Functionality

The application is a simple real-time chat system built using Python, utilizing socket programming for network communication and multithreading to handle multiple clients simultaneously. It consists of a server that listens for incoming client connections and relays messages between them, ensuring seamless communication. Each client connects to the server, sends messages, and receives updates from other participants. The server maintains a list of active connections and uses a broadcast mechanism to distribute messages efficiently. Additionally, the server itself can send messages to all clients. The implementation relies on TCP sockets for reliable data transmission and threads to manage concurrent connections without blocking execution, making it a scalable and efficient solution for basic chat applications.

The chat application (Listing 1) consists of two main components: a server and a client, both built using Python's socket and threading modules. The server is responsible for handling multiple clients simultaneously, listening for incoming messages, and broadcasting them to all connected users. The client connects to the server, sends messages, and receives real-time updates from other participants. The program begins by defining a function to handle individual client connections (handle_client) that continuously listens for messages from a connected client. If a message is received, it is printed on the server console and then relayed to all other clients using the broadcast function. This function iterates through the list of active client sockets and sends the message to each one, except the sender, ensuring smooth communication between users. The server is started with the start_server function, which initializes a TCP socket, binds it to a specified host and port, and begins listening for incoming connections. When a client connects, the server appends the client's socket to the list of active connections and starts a new thread to handle communication with that client, allowing multiple users to interact concurrently. Additionally, a separate thread is started for send_server_messages, a function that allows the server to send messages to all clients, making it possible for the server to actively participate in the chat.

Listing 1

chat.py

01 import socket
02 import threading
03
04 # Server Code
05 def handle_client(client_socket, client_address):
06   print(f"[NEW CONNECTION] {client_address} connected.")
07   while True:
08     try:
09       message = client_socket.recv(1024).decode('utf-8')
10       if not message:
11         break
12       print(f"{client_address}: {message}")
13       broadcast(message, client_socket)
14     except:
15       break
16   print(f"[DISCONNECT] {client_address} disconnected.")
17   client_socket.close()
18
19 def broadcast(message, sender_socket=None):
20   for client in clients:
21     if client != sender_socket:
22       try:
23         client.send(message.encode('utf-8'))
24       except:
25         client.close()
26         clients.remove(client)
27
28 def send_server_messages():
29   while True:
30     message = input()
31     broadcast(f"[SERVER]: {message}")
32
33 def start_server():
34   server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
35   server.bind((HOST, PORT))
36   server.listen()
37   print(f"[LISTENING] Server is listening on {HOST}:{PORT}")
38
39   threading.Thread(target=send_server_messages, daemon=True).start()
40
41   while True:
42     client_socket, client_address = server.accept()
43     clients.append(client_socket)
44     thread = threading.Thread(target=handle_client, args=(client_socket, client_address))
45     thread.start()
46
47 # Client Code
48 def receive_messages(client_socket):
49   while True:
50     try:
51       message = client_socket.recv(1024).decode('utf-8')
52       if not message:
53         break
54       print(message)
55     except:
56       print("[ERROR] Connection lost.")
57       break
58
59 def start_client():
60   client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
61   client.connect((HOST, PORT))
62
63   receive_thread = threading.Thread(target=receive_messages, args=(client,))
64   receive_thread.start()
65
66   while True:
67     message = input()
68     client.send(message.encode('utf-8'))
69   client.close()
70
71 # Configuration
72 HOST = '127.0.0.1'
73 PORT = 5555
74 clients = []
75
76 if __name__ == "__main__":
77   choice = input("Start server or client? (s/c): ").strip().lower()
78   if choice == 's':
79     start_server()
80   elif choice == 'c':
81     start_client()

On the client side, the start_client function establishes a connection with the server and creates a dedicated thread to listen for incoming messages using the receive_messages function. This function continuously reads data from the server and prints it to the console, ensuring that the client can receive messages from both the server and other users. The main client loop allows the user to input messages and send them to the server; they are then broadcast to all connected clients. The program architecture ensures that both sending and receiving occur independently through separate threads, preventing blocking and allowing real-time communication.

To maintain a persistent connection, the server uses a list to track all active client sockets, removing any that become unresponsive. The use of Python's threading module enables each client to have its own dedicated communication thread, preventing one unresponsive client from affecting others. The TCP protocol is used for reliable data transmission, ensuring that messages are received in the correct order without loss. The program is designed for local network communication but can be expanded for use over the Internet by modifying the host and port settings and implementing additional security measures such as authentication and encryption.

Please note that the script shown in Listing 1 is a simple case study demonstrating a basic chat application. It is not a production-ready solution and lacks many refinements that would improve security, error handling, and performance. However, adding such features would increase complexity and detract from the main educational purpose of this example.

A Practical Example

To provide a technical example of how our code works, let's walk through the process of setting up the server, connecting clients, and demonstrating the communication between them. This example will illustrate the code's functionality in a step-by-step manner, showing how the server and clients interact in a chat-like environment.

First, start the server by running the script and selecting the server option. When prompted with

Start server or client? (s/c):

type s and press Enter. The server will initialize and begin listening for incoming connections on the specified host (127.0.0.1) and port (5555). You will see a message in the server terminal indicating that it is listening:

[LISTENING] Server is listening on 127.0.0.1:5555

This means the server is now ready to accept client connections (Figure 1).

Figure 1: Server's listening output.

Next, open a new terminal window to start the first client. Run the script again, and this time, type c when prompted to start the client. The client will attempt to connect to the server at 127.0.0.1:5555. Upon successful connection, the server terminal will log the new connection with a message like

[NEW CONNECTION] ('127.0.0.1', 12345) connected.

where 12345 represents the client's port number. The client terminal will remain active, waiting for the user to input messages (Figure 2).

Figure 2: Client sending strings.

To demonstrate multi-client functionality, open a second terminal window and start another client by running the script and selecting the client option again. The server will log this new connection with a similar message, such as

[NEW CONNECTION] ('127.0.0.1', 54321) connected.

Now, you have two clients connected to the server, and both can send and receive messages.

In the first client terminal, type a message, for example,

Hello from Client 1!

and press Enter. The server will receive this message and broadcast it to all connected clients except the sender. As a result, the second client will display the message in its terminal:

('127.0.0.1', 12345): Hello from Client 1!

Similarly, if you type a message in the second client terminal, such as

Hi from Client 2!

the first client will receive and display it:

('127.0.0.1', 54321): Hi from Client 2!

The server also has the ability to send messages to all connected clients. In the server terminal, type a message, for example,

This is a message from the server!

and press Enter. This message will be broadcast to all clients, and both client terminals will display (Figure 3)

[SERVER]: This is a message from the server!
Figure 3: Chat between two clients and a server.

This example demonstrates the core functionality of your code, showcasing how the server handles multiple clients, broadcasts messages, and manages connections. The server and clients communicate seamlessly, allowing for real-time message exchange.

Buy this article as PDF

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy Linux Magazine

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

  • Fast as Lightning

    The WebSocket protocol helps browsers to immediately reload a page if the server-side content changes. In this month's column, Mike Schilli whips up his own WebSocket server in Go for this task.

  • Sending Notifications

    If you use sensors with a Raspberry Pi or Arduino in your home network, you may want to get desktop notifications on your Linux PC whenever some interesting event is detected. You can send messages via SSH or through simple TCP connections and display them with notify-send.

  • Twisted

    The Twisted framework makes it so easy to create network-aware applications in Python. Twisted speaks all the major Internet protocols, from mail through chat, and it can handle encryption. We’ll show you how to set up a personal web server with Twisted.

  • Nerf Target Game

    A cool Nerf gun game for a neighborhood party provides a lesson in Python coding with multiple processors.

  • Epoptes

    If your school's computer lab consists of Linux machines, Epoptes provides an interesting alternative to conventional management and monitoring programs.

comments powered by Disqus
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters

Support Our Work

Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.

Learn More

News