'''
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()
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="author" content="-T.K.-">
<meta name="copyright" content="-T.K.-">
<title>Untitled Page</title>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>
<canvas id="myCanvas" width="800" height="600" style="border:1px solid #000000;"></canvas>
<script>
var Vec3 = function(x, y, z) {
this.x = x;
this.y = y;
this.z = z;
};
var Player = function(id) {
this.id = id;
this.pos = new Vec3(0, 0, 0);
this.get_data = function() {
return JSON.stringify({id: this.id, pos: this.pos});
}
};
</script>
<script>
var ws;
$(document).ready(function() {
if('WebSocket' in window) console.log('broswer support');
ws = new WebSocket('ws://192.168.1.8:10002');
ws.onopen = function() {
console.log('socket connection established');
ws.send(JSON.stringify({init: 1}));
setInterval(function(){
ws.send(player.get_data());
}, 50);
};
ws.onmessage = function(res) {
data = JSON.parse(res.data);
console.log(data);
if(data.init) {
player = new Player(data.id);
} else {
ctx.clearRect(0, 0, c.width, c.height);
for(p in data) {
ctx.fillRect(data[p].pos.x, data[p].pos.y, 64, 64);
}
}
};
ws.onclose = function() {
console.log('sock closed');
};
});
</script>
<script>
var player;
var status = 4;
var c = document.getElementById('myCanvas');
var ctx = c.getContext('2d');
ctx.fillStyle = '#FF0000';
$(document).keydown(function(e) {
switch(e.keyCode) {
case 37:
player.pos.x -= 10;
break;
case 39:
player.pos.x += 10;
break;
case 38:
player.pos.y -= 10;
break;
case 40:
player.pos.y += 10;
break;
}
});
</script>