multiple sockets can serve the same game
This commit is contained in:
parent
72210a501b
commit
a8598a309f
|
@ -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"
|
||||
|
|
|
@ -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"))
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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):
|
||||
|
||||
|
|
Loading…
Reference in New Issue