class Game(object): ''' The game class stores the basic information for games.''' def __init__(self): ''' Creates a new game.''' self.id = -1 self.name = '' self.numPlayers = 0 self.turnNumber = 0 self.startTime = 0 self.mapFile = '' self.map = None self.mapSize = None self._mapStore = MapStore() def loadMap(self, filename='', id=''): ''' Load the map TODO - Can this be removed? ''' if id is not '': self.mapFile = self._mapStore.getMap(id=id)['filename'] self.map = self._mapStore.loadMap(self.mapFile) if filename is not '': self.mapFile = filename if self.mapFile is not '' and os.path.exists(MAP_PATH + self.mapFile): self.map = self._mapStore.loadMap(self.mapFile) def runTime(self): if self.startTime > 0: return time.time() - self.startTime else: return self.startTime def __repr__(self): r = "<ClientGame: id=%d, name=%s, numPlayers=%d, "%(self.id, self.name, self.numPlayers) r+= "startTime=%s, turnNumber=%d "%(self.startTime, self.turnNumber) r+= "mapFile=%s>"%self.mapFile return r
class PSGServer(object): ''' The main server that listens for connections and manages active games. This also runs the console which can interact with the server.''' # registeredUsers is a list of User objects of registered users registeredUsers = [] # connectedUsers is a list of User objects of currently connected users connectedUsers = [] # connections is a list of Connection objects connections =[] # Connections that have not responded to their keepalive request. # A dictionary of the form (Connection, pingtime) pingNonResponders = {} # games is a list of active GameStateServer objects games = [] def __init__(self): ''' Initialize the server.''' import __builtin__ __builtin__.LOG = LogConsole() print('Starting PSG Server ...') self._cManager = QueuedConnectionManager() self._cListener = QueuedConnectionListener(self._cManager, 0) self._cReader = QueuedConnectionReader(self._cManager, 0) self._cWriter = ConnectionWriter(self._cManager,0) #TODO - Load user file (DB) self.registeredUsers =[ServerPlayer('chad','password1'), ServerPlayer('josh','password2'), ServerPlayer('james','password3')] # Map store self._mapStore = MapStore() # Open socket self._tcpSocket = self._cManager.openTCPServerRendezvous(PORT,BACKLOG) self._cListener.addConnection(self._tcpSocket) # Setup interfaces self._console = InterfaceConsole(self) # Setup system tasks taskMgr.add(self.__listenTask, 'serverListenTask', -40) taskMgr.add(self.__readTask, 'serverReadTask', -39) taskMgr.doMethodLater(PING_DELAY, self.__pingTask, 'serverPingTask', sort=-41) taskMgr.doMethodLater(1, self.__checkPingRespTask, 'serverCheckPingRespTask', sort=-10) print('Server initialized') def __listenTask(self, Task): ''' This task listens for connections. When a connection is made it adds the connection to the clients list and begins listening to that connection.''' if self._cListener.newConnectionAvailable(): rendezvous = PointerToConnection() netAddress = NetAddress() newConnection = PointerToConnection() if self._cListener.getNewConnection(rendezvous,netAddress,newConnection): newConnection = newConnection.p() if newConnection not in self.connections: self.connections.append(newConnection) self._cReader.addConnection(newConnection) # Begin reading connection self._console.printNotice('Connection from %s'%netAddress.getIpString()) else: self._console.printNotice('%s: already connected'%(newConnection.getAddress().getIpString())) return Task.cont def __readTask(self, Task): ''' This task listens for any messages coming in over any connections. If we get a connection passes it to the datagram handler.''' if self._cReader.dataAvailable(): datagram=NetDatagram() if self._cReader.getData(datagram): data = PyDatagramIterator(datagram) msgID = data.getUint16() else: data = None msgID = MSG_NONE else: datagram = None data = None msgID = MSG_NONE if msgID is not MSG_NONE: self.__handleDatagram(data, msgID, datagram.getConnection()) return Task.cont def __pingTask(self, Task): ''' Ping all clients every PING_DELAY seconds to check for those who have dropped their connection.''' notice = 'Pinging: ' for c in self.connections: # Don't ping connections we're still waiting on if c not in self.pingNonResponders.keys(): notice = '%s%s '%(notice, c.getAddress().getIpString()) self.pingNonResponders[c] = int(time.time()) pkg = NetDatagram() pkg.addUint16(MSG_PING_REQ) self._cWriter.send(pkg, c) LOG.notice(notice) # Add task back into the taskmanager taskMgr.doMethodLater(PING_DELAY, self.__pingTask, 'serverPingTask', sort=-41) def __checkPingRespTask(self, Task): ''' Run through the list of connections that haven't responded to their ping yet and disconnect them if has been more than PING_TIMEOUT seconds.''' notice = 'Cleaning non-responders ' for c in self.pingNonResponders.keys(): notice = '%s%s '%(notice, c.getAddress().getIpString()) now = int(time.time()) pingTime = self.pingNonResponders[c] if ((now - pingTime) > PING_TIMEOUT): #print('disconnecting '), self.__handleDisconnect(None, None, c) LOG.notice(notice) # Add task back into the taskmanager taskMgr.doMethodLater(1, self.__checkPingRespTask, 'serverCheckPingRespTask', sort=-10) def __handleDatagram(self, data, msgID, client): ''' This handles incoming messages. It can run the appropriate handler from the server or pass it to the relevant game to deal with. data (PyDatagramIterator): the list of data sent with this datagram msgID (Int): the message ID client (Connection): the connection that this datagram came from''' self._console.printNotice('%s: Recieved msg: %d'%(client.getAddress().getIpString(),msgID)) # System messages if (msgID == MSG_PING_REQ): self._console.printNotice('Notice: MSG_PING_REQ') self.__handlePingReq(data, msgID, client) elif (msgID == MSG_PING_RES): self._console.printNotice('Notice: MSG_PING_RES') self.__handlePingRes(data, msgID, client) elif (msgID == MSG_DISCONNECT_REQ): self._console.printNotice('Notice: MSG_DISCONNECT_REQ') self.__handleDisconnect(data, msgID, client) # Pre-game server messages elif (msgID == MSG_AUTH_REQ): self._console.printNotice('Notice: MSG_AUTH_REQ') self.__handleAuth(data, msgID, client) elif (msgID == MSG_MAPLIST_REQ): self._console.printNotice('Notice: MSG_MAPLIST_REQ') #self.__handleMapList(data, msgID, client) elif (msgID == MSG_GAMELIST_REQ): self._console.printNotice('Notice: MSG_GAMELIST_REQ') self.__handleGameList(data, msgID, client) elif (msgID == MSG_NEWGAME_REQ): self._console.printNotice('Notice: MSG_NEWGAME_REQ') self.__handleNewGame(data, msgID, client) elif (msgID == MSG_JOINGAME_REQ): self._console.printNotice('Notice: MSG_JOINGAME_REQ') self.__handleJoinGame(data, msgID, client) elif (msgID == MSG_DOWNLOADMAP_REQ): self._console.printNotice('Notice: MSG_DOWNLOADMAP_REQ') self.__handleDownloadMap(data, msgID, client) elif (msgID == MSG_DOWNLOADUPD_REQ): self._console.printNotice('Notice: MSG_DOWNLOADUPD_REQ') self.__handleDownloadUpdate(data, msgID, client) # In-game server messages elif (msgID >= MSG_INGAME): self.__route(data, msgID, client) else: self._console.printNotice('%s: Unkown MSG_ID: %d'%(client.getAddress().getIpString(),msgID)) print(data) def __handlePingReq(self, data, msgID, client): ''' Respond to a ping request. data (PyDatagramIterator): the list of data sent with this datagram msgID (Int): the message ID client (Connection): the connection that this datagram came from''' # Send response pkg = NetDatagram() pkg.addUint16(MSG_PING_RES) self._cWriter.send(pkg, client) self._console.printNotice('%s: Ping request'%(client.getAddress().getIpString())) def __handlePingRes(self, data, msgID, client): ''' Handle an incoming ping response. data (PyDatagramIterator): the list of data sent with this datagram msgID (Int): the message ID client (Connection): the connection that this datagram came from''' self._console.printNotice('%s: Ping response'%(client.getAddress().getIpString())) # Client responded so remove from non-responder list try: del(self.pingNonResponders[client]) except KeyError: self._console.printNotice("%s responded to ping but was not in pingNonResponders"%client.getAddress()) def __handleDisconnect(self, data, msgID, client): ''' Disconnect and send confirmation to the client. data (PyDatagramIterator): the list of data sent with this datagram msgID (Int): the message ID client (Connection): the connection that tendNehis datagram came from''' # Create a response pkg = NetDatagram() pkg.addUint16(MSG_DISCONNECT_RES) self._cWriter.send(pkg, client) # If user has joined a game, remove the player from that game for g in self.games: if g.isPlayerInGame(client): g.removePlayer(client) # If user is logged in disconnect username = '' for u in self.connectedUsers: if (u.connectedClient == client): username = u.username u.disconnect() self.connectedUsers.remove(u) # Delete client from list if client in self.connections: self.connections.remove(client) # Don't worry about pings any more if client in self.pingNonResponders: del(self.pingNonResponders[client]) self._console.printNotice('%s: Disconnected user %s'%(client.getAddress().getIpString(),username)) def __handleAuth(self, data, msgID, client): ''' Try to authorize the connecting user, send the result. data (PyDatagramIterator): the list of data sent with this datagram msgID (Int): the message ID client (Connection): the connection that this datagram came from''' # Unpack message data username = data.getString() password = data.getString() auth = 0 # Look for the username in the list of registered users for u in self.registeredUsers: if u.username == username: if u.connected: auth = 1 self._console.printNotice('%s: User %s already connected'%(client.getAddress().getIpString(),username)) break elif u.password != password: auth = 2 self._console.printNotice('%s: User %s gave invalid password'%(client.getAddress().getIpString(),username)) break else: auth = 3 u.connect(client) self.connectedUsers.append(u) self._console.printNotice('%s: User %s connected with pass %s' %(client.getAddress().getIpString(),username,password)) # Send response pkg = NetDatagram() pkg.addUint16(MSG_AUTH_RES) pkg.addUint32(auth) self._cWriter.send(pkg, client) def __handleMapList(self, data, msgID, client): ''' Assemble a list of available maps and send it to the requesting client. data (PyDatagramIterator): the list of data sent with this datagram msgID (Int): the message ID client (Connection): the connection that this datagram came from''' # Assemble a list with entries in the form (filename,mapname,md5sum) mapFileList = Map.getMapFiles() responseList = [] for f in mapFileList: fh = open(Map.MAP_PATH + f,' rb') mapObj = cPickle.load(fh) responseList.append((mapObj.name, f, Map.getMapMD5(f))) # Send response pkg = NetDatagram() pkg.addUint16(MSG_MAPLIST_RES) pkg.addString('SOT') # Start Of Transmission for i, (n,f,c) in enumerate(responseList): pkg.addString(n) pkg.addString(f) pkg.addString(c) if i < len(responseList)-1: pkg.addString('T') # Still tranmitting pkg.addString('EOT') # End Of Transmission self._cWriter.send(pkg, client) self._console.printNotice('%s: Request for map list.' %(client.getAddress().getIpString())) def __handleGameList(self, data, msgID, client): ''' Assemble a list of active games and send it to the requesting client. data (PyDatagramIterator): the list of data sent with this datagram msgID (Int): the message ID client (Connection): the connection that this datagram came from''' # Send response pkg = NetDatagram() pkg.addUint16(MSG_GAMELIST_RES) if (len(self.games) == 0): pkg.addString('EOT') # Nothing to transmit else: pkg.addString('SOT') # Start Of Transmission for i,g in enumerate(self.games): pkg.addInt32(g.id) pkg.addString(g.name) pkg.addUint32(g.numPlayers) pkg.addString(g.map.name) pkg.addString(g.mapFile) pkg.addUint32(g.startTime) pkg.addUint32(g.turnNumber) if i < len(self.games)-1: pkg.addString('T') # Still tranmitting pkg.addString('EOT') # End Of Transmission self._cWriter.send(pkg, client) self._console.printNotice('%s: Request for game list.' %(client.getAddress().getIpString())) def __handleNewGame(self, data, msgID, client): ''' Create a new game and respond with success or failure. data (PyDatagramIterator): the list of data sent with this datagram msgID (Int): the message ID client (Connection): the connection that tendNehis datagram came from''' # Unpack message data gameName = data.getString() mapID = data.getString() numPlayers = data.getUint32() # If we do not have the map for the requested game tell client print("id=%s"%mapID) if(not self._mapStore.isAvailable(id=mapID)): response = 0 else: # Create the game newGame = GameStateServer(gameName, numPlayers, mapID) if newGame is not None: self.games.append(newGame) response = newGame.id else: response = -1 # Send response pkg = NetDatagram() pkg.addUint16(MSG_NEWGAME_RES) pkg.addInt32(response) self._cWriter.send(pkg, client) self._console.printNotice('%s: Request for new game: %s, %s, %d.' %(client.getAddress().getIpString(),gameName,mapID,numPlayers)) def __handleJoinGame(self, data, msgID, client): ''' Add client to the requested game. data (PyDatagramIterator): the list of data sent with this datagram msgID (Int): the message ID client (Connection): the connection that tendNehis datagram came from''' # Unpack message data id = data.getUint32() resp = 0 # Find the game game = None mapMD5 = "00000000000000000000000000000000" print self.games for g in self.games: if g.id == id: game = g mapMD5 = g.mapID if game == None: LOG.debug('No such game') resp = 0 elif len(game.connections) >= game.numPlayers: LOG.debug('Game full') resp = 1 else: game.addPlayer(client) LOG.debug('Ok, joining game') resp = 2 # Send response pkg = NetDatagram() pkg.addUint16(MSG_JOINGAME_RES) pkg.addUint32(resp) pkg.addString(mapMD5) self._cWriter.send(pkg, client) self._console.printNotice('%s: Request to join game id %d, gave %d.'%(client.getAddress().getIpString(),id,resp)) def __handleDownloadMap(data, msgID, client): ''' Prepare and send requested map to client data (PyDatagramIterator): the list of data sent with this datagram msgID (Int): the message ID client (Connection): the connection that tendNehis datagram came from''' # Unpack message data id = data.getUint32() resp = 0 filename = self._mapStore.getMap(id=id)['filename'] mapString = self._mapStore.loadMapAsString(filename) if (mapString == None): LOG.debug('No such map') resp = 0 mapString = '' else: LOG.debug('Sending map') resp = 1 # Send response pkg = NetDatagram() pkg.addUint16(MSG_JOINGAME_RES) pkg.addUint32(resp) pkg.addString(mapString) self._cWriter.send(pkg, client) def __handleDownloadUpdate(data, msgID, client): pass def __route(self, data, msgID, client): LOG.notice('Routing msg to GameStateServer') def shutdown(self): print('Shutting down server ...') # Send disconnect to all clients pkg = NetDatagram() pkg.addUint16(MSG_DISCONNECT_REQ) for c in self.connections: self._cWriter.send(pkg, c) self._cManager.closeConnection(self._tcpSocket) print('Server done') sys.exit() def disconnect(self, client): ''' Disconnect client''' self.__handleDisconnect(None, None, client)