'''
Host a multiplayer sever via WebSocket protocol
https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers
'''
import base64
import hashlib
import socket
from struct import pack, unpack
import threading
import json
class WebSocketConn:
def __init__(self, conn):
self.conn = conn
request = self.conn.recv(1024).strip().decode('utf-8', 'ignore').split('\r\n')
# parse headers into dict
self.headers = dict([line.split(': ', 1) for line in request[1:]])
# perform WebSocket handshake
self._handshake()
def _handshake(self):
key = self.headers.get('Sec-WebSocket-Key') + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
resp_key = base64.standard_b64encode(hashlib.sha1(key.encode()).digest()).decode()
res_header = {
'Upgrade': 'websocket',
'Connection': 'Upgrade',
'Sec-WebSocket-Accept': resp_key,
}
response = 'HTTP/1.1 101 Switching Protocols\r\n'
for i in res_header:
response += '%s: %s\r\n' % (i, res_header[i])
response += '\r\n'
self.conn.send(response.encode())
def recv(self):
'''
retrieve data from the client.
'''
buffer = self.conn.recv(2)
if buffer:
# read the three possible content-length number
length = buffer[1] - 2**7
if length == 126:
length, = unpack('>H', self.conn.recv(2))
elif length == 127:
length, = unpack('>Q', self.conn.recv(8))
# get the masking key for the content
mask = self.conn.recv(4)
# encoded content
buffer = self.conn.recv(length)
decoded = ''
for i in range(length):
# decode the content
decoded += chr(buffer[i] ^ mask[i % 4])
return decoded
def send(self, data):
'''
send content in form of WebSocket data frame.
'''
buffer = b''
# initial 4 bits
buffer += pack('>B', 129)
# length of the content
if len(data) > 126:
if len(data) < 2 ** 10:
buffer += pack('>BH', 126, len(data))
else:
buffer += pack('>BQ', 127, len(data))
else:
buffer += pack('>B', len(data))
# append content
buffer += data.encode()
self.conn.send(buffer)
def close(self):
'''
close the connection.
'''
self.conn.close()
class WebSocket:
def __init__(self, addr):
'''
a WebSocket socket.
@param addr: the address to bind with, i.e. (host, port)
'''
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
self.sock.bind(addr)
def listen(self, num):
'''
maximum clients to listen to.
'''
self.sock.listen(num)
def accept(self):
'''
accept the connection from a client.
'''
conn, self.addr = self.sock.accept()
self.conn = WebSocketConn(conn)
return self.conn, self.addr
class WebSocketServer:
def __init__(self, sock):
'''
a WebSocket server class with multithreading.
'''
self.sock = sock
self.player_pool = []
def run(self):
'''
run server.
'''
# lock for controlling the player pool
lock = threading.Lock()
while True:
conn, addr = self.sock.accept()
threading.Thread(target=self.handle, args=(conn, addr, lock)).start()
def handle(self, conn, addr, lock):
while True:
try:
buffer = conn.recv()
if buffer:
try:
req = json.loads(buffer.strip())
except Exception as e:
print(e)
if req.get('init'):
conn.send(json.dumps({'init': 1, 'id': len(self.player_pool)}))
else:
if req.get('id') >= len(self.player_pool):
lock.acquire()
self.player_pool.append(req)
lock.release()
else:
self.player_pool[req.get('id')] = req
conn.send(json.dumps(self.player_pool))
except Exception as e:
conn.close()
return 0
if name == '__main__':
sock = WebSocket(('0.0.0.0', 10002))
sock.listen(10)
server = WebSocketServer(sock)
server.run()
Untitled Page