The following example focuses on sending messages from the server to the client. Key concepts include:
- Singleton pattern
- Multithreading
- Redis
- WebSocket
- 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.