def handleJoin(self, data): # do we have a game? if self.game: self.logger.warn("already have a game, can not join another") self.send(TcpPacket.ALREADY_HAS_GAME) return # joined game (gameId,) = struct.unpack_from("!I", data, 0) if not self.games.has_key(gameId): self.logger.warn("no such game: %d", gameId) self.send(TcpPacket.INVALID_GAME) return game = self.games[gameId] # game already full? if game.hasStarted(): self.logger.warn("game has already started, can not join") self.send(TcpPacket.GAME_FULL) return # game is ok and it's ours game.player2 = self self.game = game opponent = game.player1 # set up UDP handlers self.udpHandler = UdpHandler(self.statistics, self.logger, self.reactor) opponent.udpHandler = UdpHandler(opponent.statistics, self.logger, self.reactor) # let the UDP handlers know about each other self.udpHandler.opponent = opponent opponent.opponent = self.udpHandler # the opponent is player 1, send its UDP port and our name dataTo1 = struct.pack("!HH%ds" % len(self.name), opponent.udpHandler.getLocalPort(), len(self.name), self.name) opponent.send(TcpPacket.GAME_JOINED, dataTo1) # send the opponent data to us, along with our UDP port dataTo2 = struct.pack( "!HH%ds" % len(opponent.name), self.udpHandler.getLocalPort(), len(opponent.name), opponent.name ) self.send(TcpPacket.GAME_JOINED, dataTo2) # the game is no longer open, tell everyone self.broadcast(TcpPacket.GAME_REMOVED, struct.pack("!I", self.game.id)) self.logger.info("joined game %d with opponent %d", self.game.id, self.game.player1.id)
class Client(Int16StringReceiver): nextId = 0 def __init__(self, clients, games, authManager, reactor): self.id = Client.nextId Client.nextId += 1 self.logger = logging.getLogger("client-%d" % self.id) self.logger.info("client initialized") self.clients = clients self.games = games self.authManager = authManager self.reactor = reactor # not yet logged in self.loggedIn = False self.name = None # no own game yet self.game = None # not ready to start yet self.readyToStart = False # no UDP handler yet connected to the client self.udpHandler = None # statistics self.statistics = Statistics() # set up the handlers self.handlers = { TcpPacket.LOGIN: self.handleLogin, TcpPacket.ANNOUNCE: self.handleAnnounce, TcpPacket.LEAVE_GAME: self.handleLeave, TcpPacket.JOIN_GAME: self.handleJoin, TcpPacket.DATA: self.handleData, TcpPacket.READY_TO_START: self.handleReadyToStart, TcpPacket.GET_RESOURCE_PACKET: self.handleGetResource, TcpPacket.KEEP_ALIVE_PACKET: self.handleKeepAlivePacket, } def connectionMade(self): self.clients[self.id] = self remotePeer = self.transport.getPeer() self.logger.info("client from: %s:%d, clients now: %d" % (remotePeer.host, remotePeer.port, len(self.clients))) self.statistics.connected = datetime.datetime.now() def connectionLost(self, reason): if self.id in self.clients: del self.clients[self.id] self.logger.info("client disconnected, now left %d", len(self.clients)) # do we have a game? if self.game and self.game.hasStarted(): self.logger.debug("writing game statistics") self.game.ended = datetime.datetime.now() self.saveStatistics() self.game = None if self.udpHandler: self.udpHandler.cleanup() def stringReceived(self, string): # get the first byte, the packet type self.logger.debug("stringReceived: bytes: %d", len(string)) (packetType,) = struct.unpack_from("!H", string, 0) self.logger.debug("packet type: %d, name: %s", packetType, TcpPacket.name(packetType)) # find a handler to handle the real packet if not self.handlers.has_key(packetType): self.logger.error("invalid packet type: %d", packetType) self.transport.loseConnection() return # call the handler try: # call the handler, and strip out the first 2 bytes: the packet type self.handlers[packetType](string[2:]) # update statistics self.statistics.tcpBytesReceived += len(string) self.statistics.tcpPacketsReceived += 1 self.statistics.tcpLastR = datetime.datetime.now() except: self.logger.error("stringReceived: failed to execute handler for packet %d", packetType) self.transport.loseConnection() raise def handleLogin(self, data): offset = 0 (protocol, nameLength) = struct.unpack_from("!HH", data, offset) offset += struct.calcsize("!HH") if protocol != definitions.protocolVersion: self.logger.warn("invalid protocol: %d, our: %d", protocol, definitions.protocolVersion) self.send(TcpPacket.INVALID_PROTOCOL) self.transport.loseConnection() return # already logged in? if self.loggedIn: self.logger.warn("already logged in") self.send(TcpPacket.ALREADY_LOGGED_IN) return if nameLength == 0 or nameLength > 100: self.logger.warn("invalid name length: %d", nameLength) self.send(TcpPacket.INVALID_NAME) return # name (self.name, passwordLength) = struct.unpack_from("!%dsH" % nameLength, data, offset) offset += struct.calcsize("!%dsH" % nameLength) # name taken? for player in self.clients.values(): if player != self and player.name == self.name: self.send(TcpPacket.NAME_TAKEN) return # password if passwordLength == 0 or passwordLength > 100: self.logger.warn("invalid password length: %d", passwordLength) self.send(TcpPacket.INVALID_PASSWORD_PACKET) return (password,) = struct.unpack_from("!%ds" % passwordLength, data, offset) if not self.authManager.validatePassword(password): self.logger.warn("invalid password") self.send(TcpPacket.INVALID_PASSWORD_PACKET) return # login ok self.send(TcpPacket.LOGIN_OK) self.loggedIn = True self.logger.info("player %s logged in", self.name) # broadcast the changed player count self.broadcast(TcpPacket.PLAYER_COUNT_PACKET, struct.pack("!H", len(self.clients))) # send all games to this player for gameId, game in self.games.iteritems(): ownerName = game.player1.name data = struct.pack("!IHH%ds" % len(ownerName), gameId, game.scenarioId, len(ownerName), ownerName) self.send(TcpPacket.GAME_ADDED, data) def handleAnnounce(self, data): # do we already have a game? if self.game: self.logger.warn("alread have a game, can not announce another") self.send(TcpPacket.ALREADY_ANNOUNCED) return offset = 0 (scenarioId,) = struct.unpack_from("!H", data, offset) offset += struct.calcsize("!HH") # set up the game self.game = Game(self, scenarioId) self.games[self.game.id] = self.game self.logger.info("announced game %d, scenario: %d", self.game.id, self.game.scenarioId) # send the game to the client self.send(TcpPacket.ANNOUNCE_OK, struct.pack("!I", self.game.id)) # broadcast the added game to all players self.broadcast( TcpPacket.GAME_ADDED, struct.pack("!IHH%ds" % len(self.name), self.game.id, self.game.scenarioId, len(self.name), self.name), ) def handleJoin(self, data): # do we have a game? if self.game: self.logger.warn("already have a game, can not join another") self.send(TcpPacket.ALREADY_HAS_GAME) return # joined game (gameId,) = struct.unpack_from("!I", data, 0) if not self.games.has_key(gameId): self.logger.warn("no such game: %d", gameId) self.send(TcpPacket.INVALID_GAME) return game = self.games[gameId] # game already full? if game.hasStarted(): self.logger.warn("game has already started, can not join") self.send(TcpPacket.GAME_FULL) return # game is ok and it's ours game.player2 = self self.game = game opponent = game.player1 # set up UDP handlers self.udpHandler = UdpHandler(self.statistics, self.logger, self.reactor) opponent.udpHandler = UdpHandler(opponent.statistics, self.logger, self.reactor) # let the UDP handlers know about each other self.udpHandler.opponent = opponent opponent.opponent = self.udpHandler # the opponent is player 1, send its UDP port and our name dataTo1 = struct.pack("!HH%ds" % len(self.name), opponent.udpHandler.getLocalPort(), len(self.name), self.name) opponent.send(TcpPacket.GAME_JOINED, dataTo1) # send the opponent data to us, along with our UDP port dataTo2 = struct.pack( "!HH%ds" % len(opponent.name), self.udpHandler.getLocalPort(), len(opponent.name), opponent.name ) self.send(TcpPacket.GAME_JOINED, dataTo2) # the game is no longer open, tell everyone self.broadcast(TcpPacket.GAME_REMOVED, struct.pack("!I", self.game.id)) self.logger.info("joined game %d with opponent %d", self.game.id, self.game.player1.id) def handleLeave(self, data): # do we have a game? if not self.game: self.logger.warn("no game, nothing to leave") self.send(TcpPacket.NO_GAME) return self.logger.info("leaving game %d, scenario: %d", self.game.id, self.game.scenarioId) self.game.endGame() # has it started? if self.game.hasStarted(): # notify the opponent and clear opponent = self.game.getOpponent() if opponent: opponent.send(TcpPacket.GAME_ENDED) opponent.game = None # notify and clear our game self.send(TcpPacket.GAME_ENDED) del (self.games[self.game.id]) self.game = None else: # not started, so it's still looking for players, broadcast the removal to all players self.broadcast(TcpPacket.GAME_REMOVED, struct.pack("!I", self.game.id)) def handleData(self, data): if self.game == None: self.logger.warn("no game, can not send data") return opponent = self.game.getOpponent() if opponent: self.logger.debug("sending %d bytes to opponent", len(data)) opponent.send(TcpPacket.DATA, data) else: self.logger.warn("no opponent, can not send data") def handleReadyToStart(self, data): if self.game == None: self.logger.warn("no game, can not handle ready to start") self.send(TcpPacket.NO_GAME) return opponent = self.game.getOpponent(self) if opponent == None: self.logger.warn("no opponent, can not handle ready to start") self.send(TcpPacket.INVALID_GAME) return # we're now ready to start self.readyToStart = True # is the opponent also ready to start? if opponent.readyToStart: self.logger.debug("handleReadyToStart: opponent also ready to start, sending start packets") self.udpHandler.sendStartPackets() # the game has started now self.game.started = datetime.datetime.now() def handleGetResource(self, data): offset = 0 (nameLength,) = struct.unpack_from("!H", data, offset) offset += struct.calcsize("!H") if nameLength == 0 or nameLength > 1024: # invalid resource name length self.send(TcpPacket.INVALID_RESOURCE_NAME_PACKET) return # resource name (name,) = struct.unpack_from("!%ds" % nameLength, data, offset) self.logger.debug("sending resource: '%s'", name) parts = resources.loadResource(name, self.logger) if parts == None or len(parts) == 0: # invalid resource self.send(TcpPacket.INVALID_RESOURCE_PACKET) return # resource loaded ok, send off it in parts partIndex = 0 partCount = len(parts) for part in parts: partLength = len(part) self.logger.debug("part %d, length: %d, parts: %d", partIndex, partLength, partCount) self.send( TcpPacket.RESOURCE_PACKET, struct.pack("!H%dsIBB" % nameLength, nameLength, name, len(part), partIndex, partCount), ) partIndex += 1 def handleKeepAlivePacket(self, data): self.logger.debug("TODO: keep alive") def broadcast(self, packetType, data): """Send the packet to all clients.""" dataLength = len(data) self.logger.debug( "broadcasting packet of type: %s, data length: %d to %d players", TcpPacket.name(packetType), dataLength, len(self.clients), ) for clientId, client in self.clients.iteritems(): client.send(packetType, data) def send(self, packetType, data=None): packetLength = 0 if data != None: dataLength = len(data) packetLength = struct.calcsize("!H") + dataLength self.logger.debug("sending packet of type: %s, data length: %d", TcpPacket.name(packetType), dataLength) self.transport.write(struct.pack("!HH", packetLength, packetType)) self.transport.write(data) else: self.logger.debug("sending empty packet of type: %s", TcpPacket.name(packetType)) packetLength = struct.calcsize("!H") self.transport.write(struct.pack("!HH", packetLength, packetType)) self.statistics.lock() self.statistics.tcpPacketsSent += 1 self.statistics.tcpBytesSent += 2 + packetLength self.statistics.tcpLastSent = datetime.datetime.now() self.statistics.release() def saveStatistics(self): if not self.game: self.logger.warning("no game, can not save statistics") return filename = "games/%d.txt" % self.game.id try: file = open(filename, "w") file.write("game %d\n" % self.game.id) file.write("scenario %d\n" % self.game.scenarioId) file.write("created %d\n" % self.game.created.isoformat(" ")) if self.game.started: file.write("started %d\n" % self.game.started.isoformat(" ")) else: file.write("started -\n") if self.game.ended: file.write("ended %d\n" % self.game.ended.isoformat(" ")) else: file.write("ended -\n") # player 1 stats stats1 = self.game.player1.statistics stats2 = self.game.player2.statistics except OSError: self.logger.error("failed to write to statistics file: %s", filename)