Implementing WebSocket in Python

The following example focuses on sending messages from the server to the client. Key concepts include:

  1. Singleton pattern
  2. Multithreading
  3. Redis
  4. WebSocket
  5. Running with in Docker containers

#!/usr/bin/env python3
import redis
import threading, sched, json, socket, base64, hashlib, logging

client_data = ""  # Data sent to clients
active_connections = []  # Active socket connections

# Singleton decorator
def singleton_decorator(cls):
    instances = {}
    def get_instance(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return get_instance

@singleton_decorator
class RedisHandler:
    def __init__(self, port_num, db_index):
        self.redis_client = redis.Redis('127.0.0.1', port_num, db_index)
        self.key_values = []

    def fetch_all_keys(self):
        keys = self.redis_client.keys()
        self.key_values = [key for key in keys if self.redis_client.ttl(key) is not None]

class SchedulerTask:
    all_keys = []

    @classmethod
    def update_host_status(cls):
        global client_data
        rh = RedisHandler(6379, 1)
        rh.fetch_all_keys()
        cls.all_keys = rh.key_values
        client_data = json.dumps(cls.all_keys)
        print(client_data)

    @classmethod
    def schedule_task(cls):
        threading.Thread(target=cls.update_host_status).start()

    @classmethod
    def run_schedule(cls, interval):
        while True:
            scheduler = sched.scheduler(time.time, time.sleep)
            scheduler.enter(interval, 1, cls.schedule_task, ())
            scheduler.run()

class WSConnectionManager(threading.Thread):
    MAGIC_KEY = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
    HANDSHAKE_RESPONSE = "HTTP/1.1 101 Switching Protocols\r\n" \
                         "Upgrade:WebSocket\r\n" \
                         "Connection: Upgrade\r\n" \
                         "Sec-WebSocket-Accept: {1}\r\n" \
                         "WebSocket-Location: ws://{2}/chat\r\n" \
                         "WebSocket-Protocol:chat\r\n\r\n"

    def __init__(self, host_addr, port_no):
        if not isinstance(host_addr, str):
            raise ValueError("Host must be a string like '127.0.0.1'")
        else:
            self.host = host_addr
        if not isinstance(port_no, int):
            raise ValueError('Port must be an integer')
        else:
            self.port = port_no
        try:
            self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            self.server_socket.bind((host_addr, port_no))
            print('Server started at:', host_addr, port_no)
            self.server_socket.listen(100)
        except Exception as e:
            print(str(e))
            print('Socket initialization failed')
        super(WSConnectionManager, self).__init__()

    def perform_handshake(self, connection):
        headers = {}
        handshake = connection.recv(1024)

        if not len(handshake):
            print('Invalid handshake length')
            return False

        header_part, data_part = handshake.split(b'\r\n\r\n', 1)
        for line in header_part.split(b'\r\n')[1:]:
            key, value = line.decode().split(': ', 1)
            headers[key] = value

        if 'Sec-WebSocket-Key' not in headers:
            print('Not a WebSocket connection, closing client.')
            connection.close()
            return False

        sec_key = headers['Sec-WebSocket-Key']
        response_key = base64.b64encode(hashlib.sha1((sec_key + WSConnectionManager.MAGIC_KEY).encode()).digest()).decode()

        handshake_response = WSConnectionManager.HANDSHAKE_RESPONSE.format(1=response_key, 2=self.host + ":" + str(self.port))
        connection.send(handshake_response.encode())

    msg_counter = 0
    def send_message(self):
        WSConnectionManager.msg_counter += 1
        global active_connections
        print('Sending message:', WSConnectionManager.msg_counter)
        for conn in active_connections:
            try:
                conn.send(f'\x81{len(client_data)}{client_data}'.encode())
            except:
                print('Message sending error')
                conn.close()
                active_connections.remove(conn)

    def scheduled_send(self):
        while True:
            scheduler = sched.scheduler(time.time, time.sleep)
            scheduler.enter(2, 1, self.send_message, ())
            scheduler.run()

    def run(self):
        threading.Thread(target=self.scheduled_send).start()
        global active_connections
        while True:
            print('Waiting for connection...')
            try:
                self.client_conn, addr = self.server_socket.accept()
                print('Connection established with:', addr)
                self.perform_handshake(self.client_conn)
                active_connections.append(self.client_conn)
            except Exception as e:
                print(str(e))
                print('Error during connection handling')
            time.sleep(3)




<script type="text/javascript">
var ws = new WebSocket('ws://192.168.81:9000');
console.log('WebSocket:', ws);

ws.onopen = function(event) {
    console.log('Connection opened:', event);
    var display = document.getElementById("output");
    display.innerHTML += 'Opened: ' + event.type + '<br>';
}

ws.onclose = function(event) {
    console.log('Connection closed:', event);
    var display = document.getElementById("output");
    display.innerHTML += 'Closed: ' + event.type + '<br>';
}

ws.onmessage = function(event) {
    console.log('Message received:', event.data);
    ws.send('acknowledged');
    var display = document.getElementById("output");
    display.innerHTML += 'Message: ' + event.data + '<br>';
}
</script>


<div id="output" style="width:600px; height:300px"></div>



Ensure the server runs inside a Docker container with port mapping (e.g., 9000:9000). The client-side JavaScript must use the server's host machine IP. Commands might involve firewall management and port checking using tools like telnet or netstat.

Tags: python WebSocket Redis multithreading docker

Posted on Tue, 29 Sep 2026 16:03:10 +0000 by sajy2k