1 The Problem
We want a chat server: several people connect over the network and every message one sends is broadcast to all the others, live. It teaches socket programming — the raw networking beneath every chat app, game server, and real-time service — plus handling many connections at once.
2 How to Think About It
Think about how a server handles many clients, before any code:
3 The Build — explained part by part
Here is a complete chat server. The broadcast logic is separated so it can be tested. Each part is explained below.
import socket
import threading
clients = [] # every connected client socket
def broadcast(message, sender, client_list):
# Send a message to everyone except the sender.
for client in client_list:
if client is not sender:
try:
client.send(message)
except OSError:
pass # client gone; ignore
def handle_client(client):
while True:
try:
message = client.recv(1024)
if not message:
break
broadcast(message, client, clients)
except OSError:
break
clients.remove(client)
client.close()
def start(host="localhost", port=9000):
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((host, port))
server.listen()
print(f"Chat server on {host}:{port}")
while True:
client, address = server.accept()
clients.append(client)
# Each client runs in its own thread.
threading.Thread(target=handle_client, args=(client,)).start()
if __name__ == "__main__":
start()
socket is the raw network connection; threading lets us handle many clients at once.broadcast(message, sender, client_list) — the core: send the message to every client except the one who sent it. We pull this out as its own function so we can test it without a real network.
handle_client — runs for each connected client: wait for a message (
recv), broadcast it, repeat. An empty message means they disconnected, so we break and clean up.server.bind / listen / accept — the standard server setup: claim a port, listen for connections, and accept each one.
accept() waits until someone connects.threading.Thread(target=handle_client, ...).start() — the key to handling many clients: each gets its own thread, so they run independently and one cannot block another.
4 Test & Prove Each Part
Live networking is hard to test, so we test the broadcast logic with fake client objects that just record what they receive.
def broadcast(message, sender, client_list):
for client in client_list:
if client is not sender:
try:
client.send(message)
except OSError:
pass
class FakeClient:
"""A stand-in for a real socket that records messages."""
def __init__(self, broken=False):
self.received = []
self.broken = broken
def send(self, message):
if self.broken:
raise OSError("broken pipe")
self.received.append(message)
def test_others_receive():
"""Other clients get the message."""
a, b = FakeClient(), FakeClient()
broadcast(b"hi", a, [a, b])
assert b.received == [b"hi"]
def test_sender_excluded():
"""The sender does not receive their own message."""
a, b = FakeClient(), FakeClient()
broadcast(b"hi", a, [a, b])
assert a.received == []
def test_broken_client_ignored():
"""A broken client does not stop the broadcast."""
a, broken, c = FakeClient(), FakeClient(broken=True), FakeClient()
broadcast(b"hi", a, [a, broken, c])
assert c.received == [b"hi"] # c still gets it
Run with pytest -v. The FakeClient trick lets us test broadcast logic without any real sockets — fast and reliable. The broken-client test proves one bad connection will not take down the whole chat.
5 The Interface
Flow
Client A sends: "Hello"
Clients B, C receive: "Hello"
Client A does not6 Run It & Automate It
python3 chat_server.pyThen connect with several terminals:
nc localhost 9000 (or telnet). Type in one — it appears in the others.Jenkins runs the tests automatically — each line explained below.
Chat server on localhost:9000
# Client A types "Hello" → Clients B and C see "Hello"OSError: Address already in use.// Jenkinsfile — automated tests on every change.
pipeline {
agent any
stages {
stage('Get the code') { steps { checkout scm } } // download the code
stage('Install') { steps { sh 'python3 -m venv venv && . venv/bin/activate && pip install pytest' } } // test tool
stage('Test') { steps { sh '. venv/bin/activate && pytest -v' } } // run every test
}
post {
success { echo 'All tests passed.' }
failure { echo 'A test failed — look above.' }
}
}
- Usernames. Prefix each message with who sent it. (Teaches: per-client state.)
- Join/leave notices. Announce when people connect or disconnect. (Teaches: system messages.)
- Private messages. Direct a message to one user. (Teaches: routing.)
FakeClient technique for testing network code. Sockets underlie every real-time application. Related: socket, Concurrency.