Skip to content

Commit dd37db6

Browse files
committed
Whole code (check riccardo0731/ChatBot for original root - this is my part of code)
0 parents  commit dd37db6

File tree

13 files changed

+1007
-0
lines changed

13 files changed

+1007
-0
lines changed

.dockerignore

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Exclude client implementations to keep the server image small
2+
client/
3+
4+
# Exclude Version Control System files
5+
.git
6+
.gitignore
7+
8+
# Exclude IDE configurations
9+
.vscode
10+
.idea
11+
12+
# Exclude Python cache and compiled files
13+
__pycache__
14+
*.pyc
15+
*.pyo
16+
*.pyd
17+
18+
# Exclude local virtual environments
19+
venv/
20+
env/
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
name: Docker Publish
2+
3+
on:
4+
push:
5+
branches: [ "main" ] # O 'master', controlla come si chiama il vostro branch
6+
# Pubblica solo se vengono modificati file del server o utils
7+
paths:
8+
- 'server/**'
9+
- 'utils.py'
10+
- 'Dockerfile'
11+
12+
env:
13+
REGISTRY: ghcr.io
14+
IMAGE_NAME: gizano/multithreaded-python-chat
15+
16+
jobs:
17+
build-and-push:
18+
runs-on: ubuntu-latest
19+
permissions:
20+
contents: read
21+
packages: write
22+
23+
steps:
24+
- name: Checkout repository
25+
uses: actions/checkout@v3
26+
27+
- name: Log in to the Container registry
28+
uses: docker/login-action@v2
29+
with:
30+
registry: ${{ env.REGISTRY }}
31+
username: ${{ github.actor }}
32+
password: ${{ secrets.GITHUB_TOKEN }}
33+
34+
- name: Extract metadata (tags, labels) for Docker
35+
id: meta
36+
uses: docker/metadata-action@v4
37+
with:
38+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
39+
40+
- name: Build and push Docker image
41+
uses: docker/build-push-action@v4
42+
with:
43+
context: .
44+
push: true
45+
tags: ${{ steps.meta.outputs.tags }}
46+
labels: ${{ steps.meta.outputs.labels }}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__pycache__/

Dockerfile

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Use a lightweight Python base image to minimize container size
2+
FROM python:3.9-slim
3+
4+
# Set the working directory inside the container
5+
WORKDIR /app
6+
7+
# 1. Copy the shared utility module to the project root
8+
# This is crucial because the server script expects utils.py in the parent directory
9+
COPY utils.py .
10+
11+
# 2. Copy the entire server directory into /app/server
12+
COPY server/ ./server/
13+
14+
# Environment Variables:
15+
# PYTHONDONTWRITEBYTECODE: Prevents Python from writing .pyc files
16+
# PYTHONUNBUFFERED: Ensures logs are flushed immediately to the terminal
17+
ENV PYTHONDONTWRITEBYTECODE=1
18+
ENV PYTHONUNBUFFERED=1
19+
20+
# Expose the TCP port defined in the protocol
21+
EXPOSE 65432
22+
23+
# Define the entry point command
24+
# We execute from /app so the script can resolve paths correctly
25+
CMD ["python", "server/server_main.py"]

README.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# 🚀 Private On-Premise TCP Chat System
2+
3+
[![Python Version](https://img.shields.io/badge/python-3.8%2B-blue.svg?style=for-the-badge&logo=python)](https://www.python.org/)
4+
[![Docker Support](https://img.shields.io/badge/Docker-Ready-2496ED?style=for-the-badge&logo=docker)](https://www.docker.com/)
5+
[![Server Capacity](https://img.shields.io/badge/Capacity-%3E6000_Clients-brightgreen?style=for-the-badge)](https://github.com/riccard00731/ChatBot)
6+
7+
An ultra-lightweight, high-performance, on-premise chat solution designed for secure communication within local networks. This system ensures data privacy by keeping traffic internal to the local network.
8+
9+
## 🌟 Key Features
10+
* **Massive Scalability**: Engineered to handle over **6,000 concurrent connections** using optimized multithreading.
11+
* **Zero-Internet Dependency**: Works entirely within your LAN, preventing external data interception.
12+
* **Dockerized Deployment**: Includes a Docker configuration to make the server easily runnable everywher.
13+
* **Strict JSON Protocol**: A standardized messaging format used to communicate between all architecture components.
14+
* **Interactive Endpoints**: Built-in server commands for listing users, broadcasting, and server-time synchronization.
15+
16+
---
17+
18+
## 🏗️ Architecture
19+
The project implements a **Client-Server architecture**, also known as a **Star Topology**.
20+
* **Server**: A multithreaded TCP engine that manages a client registry and routes JSON packets.
21+
* **Client**: Utilizes two independent threads to handle simultaneous message sending and receiving.
22+
23+
---
24+
25+
## 🛠️ Tech Stack
26+
* **Language**: Python for rapid development and extensive library support.
27+
* **Core Libraries**: `socket` for networking, `threading` for concurrency, and `json` for data management.
28+
* **DevOps**: Docker for containerization and GitHub Actions for CI/CD.
29+
30+
---
31+
32+
## 🚀 Quick Start
33+
34+
### Running with Docker
35+
1. **Clone the repo**:
36+
```bash
37+
git clone https://github.com/gizano/multithreaded-python-chat.git
38+
cd multithreaded-python-chat
39+
```
40+
2. **Launch the server**:
41+
```bash
42+
docker-compose up -d
43+
```
44+
45+
### Manual Execution
46+
1. **Start the Server**:
47+
```bash
48+
python server/server_main.py
49+
```
50+
2. **Launch the Client**:
51+
```bash
52+
python client/client_main.py
53+
```
54+
55+
---
56+
57+
## 📊 Performance & Testing
58+
Stability is our priority. We utilized **AI-generated testing suites** to push the server to its limits.
59+
* **Stress Test**: Simulates 50 bots performing handshake and random messaging.
60+
* **Breakpoint Test**: Confirmed hardware-bound stability at **6,000+ active users**.
61+
62+
> **Technical Note**: The "TCP Coalescing" issue was addressed using the `time` library to prevent packet merging during registration.
63+
64+
## 📡 Protocol Standard
65+
All communication must adhere to the following JSON schema:
66+
```json
67+
{
68+
"from": { "name": "...", "ip": "..." },
69+
"to": "...",
70+
"msg": "..."
71+
}
72+
```

client/client_lib.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import sys
2+
import socket
3+
import utils
4+
5+
"""
6+
Name: receive_messages
7+
8+
Parameters:
9+
sock | socket object | socket of the client
10+
11+
Exceptions:
12+
Any | Error logged in the server terminal
13+
14+
Description: Function for the receiver thread, listens for incoming messages from the server and prints them on the screen.
15+
16+
"""
17+
def receive_messages(sock):
18+
"""
19+
Listens for incoming messages from the server and prints them to the standard output.
20+
This function is intended to be executed in a separate thread to avoid blocking the main execution.
21+
22+
Args:
23+
sock (socket.socket): The active socket connection to the server.
24+
"""
25+
while True:
26+
try:
27+
data = sock.recv(1024)
28+
if not data:
29+
print("\n[!] Disconnected from server.")
30+
break
31+
32+
# Decode the received data using the shared utility module
33+
msg = utils.decode_json_msg(data)
34+
35+
if msg:
36+
# Format and display the message content
37+
sender = msg['from']['name']
38+
text = msg['msg']
39+
print(f"\n >>> {sender}: {text}")
40+
41+
# Restore the user input prompt to maintain UI consistency
42+
print("To: ", end="", flush=True)
43+
44+
except Exception as e:
45+
print(f"\n[!] Receive Error: {e}")
46+
break
47+
48+
# If the connection loop terminates (e.g., server downtime), exit the application
49+
sys.exit()

client/client_main.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import sys
2+
import os
3+
4+
# 1. Retrieve the current directory path
5+
current_dir = os.path.dirname(os.path.abspath(__file__))
6+
# 2. Navigate two levels up to locate the project root directory
7+
root_dir = os.path.abspath(os.path.join(current_dir, '..', '..'))
8+
# 3. Append the root directory to the system path to resolve shared modules
9+
sys.path.append(root_dir)
10+
11+
import socket as sck
12+
import threading as thr
13+
import utils
14+
import client_lib
15+
import time
16+
17+
def start_client():
18+
my_ip = utils.get_local_ip()
19+
20+
# Initial configuration and user setup
21+
print(f"--- Client Group 3 ---")
22+
server_host = input("Insert Server IP (press enter for localhost): ").strip() or "127.0.0.1"
23+
my_name = input("Your Username: ").strip()
24+
25+
sock = sck.socket(sck.AF_INET, sck.SOCK_STREAM)
26+
27+
try:
28+
# Establish connection to the server
29+
sock.connect((server_host, utils.PORT))
30+
except ConnectionRefusedError:
31+
print("[!] Impossible to connect to server. Check connectivity.")
32+
return
33+
34+
# 1. Send username for registration (Handshake Protocol)
35+
sock.send(my_name.encode(utils.ENCODING))
36+
37+
# 2. Start the receiving thread using the library function
38+
# Note: Referencing client_lib.receive_messages
39+
receiver_thread = thr.Thread(
40+
target=client_lib.receive_messages,
41+
args=(sock,),
42+
daemon=True
43+
)
44+
receiver_thread.start()
45+
46+
print(f"--- Connected as {my_name} ({my_ip}) ---")
47+
print("Message format -> Receiver, enter, message.")
48+
49+
# Little sleep to avaoid TCP Coalescing
50+
time.sleep(0.1)
51+
52+
# Get commands list from server to show at the start to the user
53+
to_user = "/help"
54+
text = "x"
55+
json_packet = utils.create_json_msg(my_name, my_ip, to_user, text)
56+
sock.send(json_packet.encode(utils.ENCODING))
57+
58+
59+
# 3. Sending Loop (Executes in the main thread)
60+
try:
61+
while True:
62+
to_user = input("To: ")
63+
text = input("Msg: ")
64+
65+
# Utilize the utility function to construct a valid JSON packet
66+
json_packet = utils.create_json_msg(my_name, my_ip, to_user, text)
67+
68+
sock.send(json_packet.encode(utils.ENCODING))
69+
70+
except KeyboardInterrupt:
71+
print("\nExiting...")
72+
finally:
73+
# Ensure the socket is properly closed upon termination
74+
sock.close()
75+
76+
77+
if __name__ == "__main__":
78+
start_client()

docker-compose.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
version: '3.8'
2+
3+
services:
4+
chat-server:
5+
# Build the image using the Dockerfile in the current context (root)
6+
build:
7+
context: .
8+
dockerfile: Dockerfile
9+
10+
# Assign a custom name to the container for easy identification
11+
container_name: group3_server
12+
13+
# Map host port 65432 to container port 65432
14+
# Format: "HOST_PORT:CONTAINER_PORT"
15+
ports:
16+
- "65432:65432"
17+
18+
# Restart policy: The container will restart automatically unless stopped manually
19+
restart: unless-stopped
20+
21+
# Resource limits (Optional: simulates a constrained production environment)
22+
deploy:
23+
resources:
24+
limits:
25+
cpus: '0.50'
26+
memory: 512M

0 commit comments

Comments
 (0)