Skip to content

Utkarsh464/chat-server

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Anonymous Chat Server

Python 3.7+ Build & Run Tests Status License Stars Last Commit

A multi-threaded TCP chat server built from scratch with Python's standard library. Supports concurrent clients, named message broadcasting, and server-side admin controls.


Background — What I Set Out to Learn

I built this to understand TCP networking and concurrency below the abstraction layer of web frameworks: the sockets API (bind, listen, accept, send, recv, and the TCP state machine), thread-per-connection design with shared mutable state, and the GIL's real-world impact on I/O-bound workloads.

Architecture

The server uses a thread-per-client model. One acceptor thread blocks on socket.accept() and spawns a ClientHandler thread per connection. Each handler runs an independent receive loop; messages fan out through a shared clients dict (username → socket).

                          ┌─────────────────────────────────────────────────┐
                          │                  SERVER (:9999)                  │
                          │                                                  │
                          │   ┌─────────────────────────────────────────┐   │
                          │   │         ChatServer Class                │   │
                          │   │  ┌─────────┐  ┌──────────┐  ┌───────┐  │   │
                          │   │  │ Accept  │  │Broadcast │  │Client │  │   │
                          │   │  │ Thread  │  │  Engine  │  │  Map  │  │   │
                          │   │  └────┬────┘  └────┬─────┘  └───┬───┘  │   │
                          │   └───────┼─────────────┼────────────┼──────┘   │
                          └───────────┼─────────────┼────────────┼──────────┘
                                      │             │            │
              ┌───────────────────────┼─────────────┼────────────┼───────────────────┐
              │              TCP Connection Pool     │            │                   │
              │                       │             │            │                   │
              │  ┌────────────────────┴──────┐  ┌───┴────────────┴──────┐            │
              │  │  Client Handler Thread 1  │  │  Client Handler Thd 2 │   ...      │
              │  │  ┌──────────────────────┐│  │  ┌──────────────────┐  │            │
              │  │  │ get_username()       ││  │  │ get_username()   │  │            │
              │  │  │ message loop         ││  │  │ message loop     │  │            │
              │  │  └──────────────────────┘│  │  └──────────────────┘  │            │
              │  └──────────────────────────┘  └────────────────────────┘            │
              └──────────────────────────────────────────────────────────────────────┘
                                      │                         │
                                      ▼                         ▼
                            ┌─────────────────┐     ┌─────────────────┐
                            │    Client A     │     │    Client B     │
                            │   (ephemeral)   │     │   (ephemeral)   │
                            └─────────────────┘     └─────────────────┘

Data Flow

 Client A                    Server                         Client B
    │                          │                               │
    │──── SYN ───────────────► │                               │
    │◄─── SYN-ACK ──────────── │                               │
    │──── ACK ────────────────►│                               │
    │                          │                               │
    │──── "Enter your ───────► │                               │
    │     username:"           │                               │
    │◄─── "Alice" ──────────── │                               │
    │                          │── "Alice has joined." ──────► │
    │                          │                               │
    │──── "hello everyone" ──► │                               │
    │                          │── "Alice: hello everyone" ──► │
    │◄── "Bob: hey Alice" ──── │◄─── "Bob: hey Alice" ─────── │
    │                          │                               │
    │──── FIN ────────────────►│                               │
    │                          │── "Alice has disconnected." ► │

Design Decisions

Daemon threads — Every handler thread is created with daemon=True. When the main thread terminates (via os._exit on shutdown), daemon threads are killed automatically. This avoids explicit join tracking, though in-flight send() calls may be interrupted without recovery. Safe dict iteration with list() copy — The broadcast loop iterates over list(self.clients.items()) rather than the dict directly. Without this intermediate copy, a mid-broadcast disconnect that removes an entry from clients raises RuntimeError: dictionary changed size during iteration. os._exit() for shutdownsys.exit() raises SystemExit, which a bare except: anywhere in the thread pool would catch and suppress. os._exit() terminates the process immediately, file descriptors and all. A cooperative threading.Event-based approach would be cleaner but was deferred. Binding to 0.0.0.0 — The server listens on all interfaces so LAN clients connect without configuration. A production deployment would bind to a specific interface; for a learning project, 0.0.0.0 is the pragmatic default. Cleanup on disconnect — When recv() returns empty bytes (FIN from peer), the handler removes its entry from clients and broadcasts a disconnect notification. The socket is closed via socket.close() to release the fd.

Problems Encountered

Broken pipe during broadcast — Writing to a socket whose peer has already sent FIN raises BrokenPipeError (SIGPIPE on UNIX). The broadcast loop now catches OSError (parent class of both BrokenPipeError and ConnectionResetError) per-client, removes the dead entry, and continues. Without this, one abrupt disconnect crashed the entire server. Stale client entries after handler crash — If a handler thread exited without cleanup (e.g., unhandled exception), its entry remained in clients. Subsequent broadcasts would silently fail on that socket. Fixed by wrapping the message loop in try/finally that guarantees removal from the client map. Blocking accept() on shutdown — After the shutdown command, the acceptor thread blocks on accept() forever. The fix: close the server socket so accept() raises OSError, which the loop interprets as the signal to exit.

What I'd Do Differently

  • Test suite — Zero tests. A pytest suite with loopback socket tests would make refactoring safe.
  • Structured logging — Replace print with logging (INFO for joins, DEBUG for raw bytes, ERROR for failures).
  • Package with pyproject.toml — Define entry points so pip install -e . works.
  • Cooperative shutdown — Use threading.Event to drain work before exit instead of daemon-thread semantics.
  • Rate limiting — No back-pressure mechanism; a malicious client could flood the broadcast loop.

Project Structure

chat-server/
├── client/
│   └── client.py                # TCP chat client
├── server/
│   ├── __init__.py
│   ├── main.py                  # Server entry point
│   ├── chat_server.py           # ChatServer class
│   └── client_handler.py        # Per-client handling logic
├── .gitignore
├── LICENSE
└── README.md

Installation & Usage

git clone https://github.com/Utkarsh464/chat-server.git && cd chat-server

No external dependencies — pure Python standard library (3.7+).

Server

cd server && python main.py
Command Effect
text message Broadcasts [Server]: <message> to all clients
shutdown Notifies all clients and stops the server
exit Exits the admin input loop (server keeps running)

Client

cd client && python client.py
Command Effect
any text Sends message to all connected users
exit Disconnects from server

Demo

Server Terminal

Enter the port number for the chat server: 9999
Server IP: &lt;server-ip&gt;
Server is listening on port 9999...
A new client has connected.
Alice has joined.
A new client has connected.
Bob has joined.
Bob: hi ..anyone here?
Alice: hey Bob! I was waiting for you
[Server]: focus on the fight guys
shutdown
Server shut down.

Client (Alice) Terminal

Enter the server IP address: &lt;server-ip&gt;
Enter the server port: 9999
Enter your username: Alice
Bob: hi ..anyone here?
hey Bob! I was waiting for you
[Server]: focus on the fight guys
[Server]: Server is shutting down.

Roadmap

  • End-to-end encryption for private messaging
  • SQLite-backed persistent chat history
  • File sharing and image transfer
  • Private messaging (/msg <user> <message>)
  • Nickname changes and color-coded usernames
  • Web-based client using WebSockets
  • Rate limiting and anti-spam measures
  • Docker containerization
  • CI/CD pipeline with GitHub Actions

License

Distributed under the MIT License. See LICENSE for more information.

About

Multi-threaded TCP chat server with real-time broadcasting, username authentication, and admin controls — built with Python sockets and threading

Topics

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages