multiple sockets can serve the same game

This commit is contained in:
troido 2019-09-26 22:04:50 +02:00
parent 72210a501b
commit a8598a309f
5 changed files with 58 additions and 48 deletions

View File

@ -13,7 +13,7 @@ class InvalidMessageError(Exception):
self.errType = errType
def toMessage(self):
return ErrorMessage(self.errType, self.desctiption)
return ErrorMessage(self.errType, self.description)
class InvalidNameError(InvalidMessageError):
errType = "invalidname"

View File

@ -15,9 +15,9 @@ saveExt = ".save.json"
class Game:
def __init__(self, socketType, address, worldFile=None, saveDir=None, saveInterval=1):
def __init__(self, sockets, worldFile=None, saveDir=None, saveInterval=1):
self.server = gameserver.GameServer(socketType, address)
self.server = gameserver.GameServer(sockets)
worldLoader = worldloader.WorldLoader(saveDir)
roomLoader = roomloader.RoomLoader(worldFile, os.path.join(saveDir, "rooms"))

View File

@ -20,9 +20,9 @@ import selectors
class GameServer:
def __init__(self, socketType, address):
def __init__(self, sockets):
self.serv = server.Server(socketType, address, self.newConnection, self.receive, self.close)
self.servers = [server.Server(socket, self.newConnection, self.receive, self.close) for socket in sockets]
self.connections = {}
self.players = {}
@ -30,7 +30,8 @@ class GameServer:
def start(self):
selector = selectors.DefaultSelector()
self.serv.listen(selector)
for serv in self.servers:
serv.listen(selector)
self.listener = threading.Thread(target=self.listen, daemon=True, args=(selector,))
self.listener.start()
@ -51,10 +52,10 @@ class GameServer:
continue
self.sendMessage(connection, messages.WorldMessage(data))
def newConnection(self, n):
def newConnection(self, connection):
pass
def receive(self, n, data):
def receive(self, connection, data):
try:
data = json.loads(data.decode('utf-8'))
if not isinstance(data, list) or len(data) != 2:
@ -68,56 +69,58 @@ class GameServer:
raise messages.InvalidMessageError("Unknown message type '{}'".format(msgType))
message = msgClass.from_json(msg)
self.handleMessage(n, message)
self.handleMessage(connection, message)
except messages.InvalidMessageError as e:
self.sendMessage(n, e.toMessage)
self.sendMessage(connection, e.toMessage())
except json.JSONDecodeError:
self.error(n, "invalidmessage", "Invalid JSON")
self.error(connection, "invalidmessage", "Invalid JSON")
except Exception as e:
print(e)
self.error(n, "invalidmessage", "An unknown error occured in handling the message")
self.error(connection, "invalidmessage", "An unknown error occured in handling the message")
def handleMessage(self, n, message):
def handleMessage(self, connection, message):
# I wish I had type overloading
if isinstance(message, messages.NameMessage):
self.handleNameMessage(n, message)
self.handleNameMessage(connection, message)
elif isinstance(message, messages.InputMessage):
self.handleInputMessage(n, message)
self.handleInputMessage(connection, message)
elif isinstance(message, messages.ChatMessage):
self.handleChatMessage(n, message)
self.handleChatMessage(connection, message)
else:
self.error(n, "invalidmessage", "unknown message '{}'".format(message.__class__))
self.error(connection, "invalidmessage", "unknown message '{}'".format(message.__class__))
def handleNameMessage(self, n, message):
def handleNameMessage(self, connection, message):
name = message.name
if name[0] == "~" and name[1:] != self.serv.getUsername(n):
serv, client = connection
if name[0] == "~" and name[1:] != serv.getUsername(client):
raise messages.InvalidNameError("tildenames are only available on unix sockets and when the rest of the name equals the username")
if name in self.players:
raise messages.InvalidNameError("another connection to this player already exists", "nametaken")
return
self.connections[n] = name
self.players[name] = n
self.connections[connection] = name
self.players[name] = connection
self.messages.put(("join", name))
print("new player: "+name)
self.broadcast("{} has connected".format(name), "connect")
def handleInputMessage(self, n, message):
if n in self.connections:
self.messages.put(("input", self.connections[n], message.inp))
def handleInputMessage(self, connection, message):
if connection in self.connections:
self.messages.put(("input", self.connections[connection], message.inp))
def handleChatMessage(self, n, msg):
name = self.connections[n]
def handleChatMessage(self, connection, msg):
name = self.connections[connection]
message = name + ": " + msg.text
print(message)
self.broadcast(message, "chat")
def sendMessage(self, n, message):
self.serv.send(n, message.to_json_bytes())
def sendMessage(self, connection, message):
serv, client = connection
serv.send(client, message.to_json_bytes())
def error(self, n, errtype, description=""):
self.sendMessage(n, messages.ErrorMessage(errtype, description))
def error(self, connection, errtype, description=""):
self.sendMessage(connection, messages.ErrorMessage(errtype, description))
def close(self, connection):
if connection in self.connections:

View File

@ -23,20 +23,26 @@ defaultSaveDir = os.path.join(farmsPath, "saves")
def main(argv=None):
parser = argparse.ArgumentParser()
parser.add_argument("-a", "--address", help="The address of the socket. When the socket type is 'abstract' this is just a name. When it is 'unix' this is a filename. When it is 'inet' is should be in the format 'address:port', eg 'localhost:8080'. Defaults depends on the socket type")
parser.add_argument("-s", "--socket", help="the socket type. 'unix' is unix domain sockets, 'abstract' is abstract unix domain sockets and 'inet' is inet sockets. ", choices=["abstract", "unix", "inet"], default="abstract")
parser.add_argument("-s", "--socket", help="the socket type and adress. For the type: 'unix' is unix domain sockets, 'abstract' is abstract unix domain sockets and 'inet' is inet sockets. Unix sockets should have a filename as address, abstract sockets just a string and inet sockets should be in the format 'hostname:port'", nargs=2, action="append")
loadGroup = parser.add_mutually_exclusive_group()
loadGroup.add_argument("-w", "--world", help="A json file to load the world from.", default=defaultWorld)
parser.add_argument("-e", "--savedir", help="Directory to save the world to periodically", default=defaultSaveDir)
args = parser.parse_args(argv)
address = args.address
if address is None:
address = defaultAdresses[args.socket]
if args.socket == "abstract":
address = '\0' + address
elif args.socket == "inet":
hostname, sep, port = address.partition(':')
address = (hostname, int(port))
socketargs = args.socket
if len(socketargs) == 0:
socketargs.append(["abstract", "asciifarm"])
game.Game(args.socket, address, args.world, args.savedir, 300).start()
sockets = []
for socktype, address in socketargs:
assert socktype in ["abstract", "unix", "inet"]
if address is "":
address = defaultAdresses[socktype]
if socktype == "abstract":
address = '\0' + address
elif socktype == "inet":
hostname, sep, port = address.partition(':')
address = (hostname, int(port))
sockets.append((socktype, address))
game.Game(sockets, args.world, args.savedir, 300).start()

View File

@ -40,7 +40,9 @@ class _BytesBuffer:
class Server:
def __init__(self, socketType, address, onConnection=(lambda *_:None), onMessage=(lambda *_:None), onConnectionClose=(lambda *_:None)):
def __init__(self, socketInfo, onConnection=(lambda *_:None), onMessage=(lambda *_:None), onConnectionClose=(lambda *_:None)):
socketType, address = socketInfo
if socketType == "abstract" or socketType == "unix":
self.sockType = socket.AF_UNIX
@ -58,14 +60,13 @@ class Server:
def listen(self, selector):
print("starting {} socket server on address {}".format(self.socketType, self.address))
try:
self.sock.bind(self.address)
except PermissionError:
print("You don't have permission to use this socket file.\nRun the server with the '-s' option to specify another socket file path.\nWARNING: if an existing file is given, it will be overwritten.")
sys.exit(-1)
except OSError:
print("Unable to bind to the socket address.\nMost likely this means that a server is already running and using the same address.\n Try another socket address (and tell all players to connect to that)")
print("Unable to bind to the {} socket address '{}'.\nMost likely this means that a server is already running and using the same address.\n Try another socket address (and tell all players to connect to that)".format(self.socketType, self.address))
sys.exit(-1)
self.sock.listen()
@ -77,14 +78,14 @@ class Server:
selector.register(self.sock, selectors.EVENT_READ, self._accept)
self.connections = {}
print("listening")
print("started {} socket server on address {}".format(self.socketType, self.address))
def _accept(self, sock, selector):
connection, client_address = sock.accept()
connection.setblocking(False)
selector.register(connection, selectors.EVENT_READ, self._receive)
self.connections[connection] = _BytesBuffer()
self.onConnection(connection)
self.onConnection((self, connection))
def _receive(self, connection, selector):
try:
@ -95,7 +96,7 @@ class Server:
buff = self.connections[connection]
buff.addBytes(data)
for message in buff.readMessages():
self.onMessage(connection, message)
self.onMessage((self, connection), message)
else:
self.closeConnection(connection)
@ -106,7 +107,7 @@ class Server:
return
connection.close()
self.selector.unregister(connection)
self.onConnectionClose(connection)
self.onConnectionClose((self, connection))
def getUsername(self, connection):