def __init__(self, timeout, missed_ping, simulation): self.__client_lock = threading.Lock() self.__timed_out_lock = threading.Lock() self.__clients = {} self.__clientids = {} self.__timed_out = [] self.__ping_pong = {} self.__t = PeriodicTimer(timeout, self.__timeout_check, [self]) ## The number of missed pings allowed self.missed_ping = missed_ping self.__sim = simulation self.__t.daemon = True
class ClientManager(): ## Class constructor # @param self The instance of playernsd::ClientManager. # @param timeout The number of seconds for a timeout. # @param missed_ping The number of missed pings allowed before a disconnect. def __init__(self, timeout, missed_ping, simulation): self.__client_lock = threading.Lock() self.__timed_out_lock = threading.Lock() self.__clients = {} self.__clientids = {} self.__timed_out = [] self.__ping_pong = {} self.__t = PeriodicTimer(timeout, self.__timeout_check, [self]) ## The number of missed pings allowed self.missed_ping = missed_ping self.__sim = simulation self.__t.daemon = True ## Start the timeout poller # @param self The instance of playernsd::ClientManager. def start(self): self.__t.start() ## Stop the timeout poller # @param self The instance of playernsd::ClientManager. def stop(self): self.__t.cancel() ## Add a client that should be polled by the timeout poller # @param self The instance of playernsd::ClientManager. # @param address The address of the client. # @param client The associated playernsd:RemoteClient object. def add_client(self, address, client): with self.__client_lock: self.__clients[address] = RemoteClient(None, address, None, client) self.__ping_pong[address] = 1 ## Register a client with name and protocol version. # @param name The name of the client. # @param address The addres sof the client. def register_client(self, address, name, version): self.__clients[address].name = name self.__clients[address].version = version self.__clientids[name] = self.__clients[address] if self.__sim: self.__sim.new_client(name) ## Check if the address is handled by the client manager. # @param identifier The identifier to refer uniquely to a client. def has_client(self, identifier): if isinstance(identifier, str): return identifier in self.__clientids else: return identifier in self.__clients ## Check if the address is already registered in the client manager. # @param identifier The identifier to refer uniquely to a client. def is_registered(self, identifier): if isinstance(identifier, str): return identifier in self.__clientids else: return identifier in self.__clients and self.__clients[identifier].name != None ## Get a RemoteClient object when identified by address or id. # @param identifier The identifier to refer uniquely to a client. def get_client(self, identifier): if isinstance(identifier, str): return self.__clientids[identifier] else: return self.__clients[identifier] ## Remove a client that is polled by the timeout poller. # @param self The instance of playernsd::ClientManager. # @param address The address of the client. def remove_client(self, address): with self.__client_lock: if self.__clients[address].name in self.__clientids: if self.__sim: self.__sim.remove_client(self.__clients[address].name) del self.__clientids[self.__clients[address].name] del self.__clients[address] ## Get a list of client ids. def get_clientid_list(self): l = [] for v in self.__clients.itervalues(): l.append(v.name) return l ## Indicate that a particular client has replied to a ping. # @param self The instance of playernsd::ClientManager. # @param address The address of the client. def pong(self, address): self.__ping_pong[address] = 0 ## Check if a particular client has timed out. # @param self The instance of playernsd::ClientManager. # @param address The address of the client. # @return Boolean return indicating whether the client has timed out. def is_timed_out(self, address): return address in self.__timed_out or \ self.__ping_pong[address] < -self.missed_ping ## Send a message to a client. # # This is a wrapper function to send a message to a client. # By default, this sends the message to the client that invoked the request, # but with the parameters @a s (the target socket) and # @a ca (the target client address), any client can be messaged. # @param self The playernsd::ClientManager instance. # @param msg The message to be sent. # @param s The socket to send the message to. # @param ca The client address to send the message to. def send(self, msg, s, ca): if VERBOSE > 1: self.log(ca, 'SEND(' + str(len(msg)) + ')', msg) # read the type of message, and see if the message should be # simulated command = msg.split(' ') if simulation and (command[0] == 'msgtext' or command[0] == 'msgbin'): self.__sim.send(command[1], self.__clientids(ca), msg[msg.find('\n')+1:]) else: s.send(msg) ## Get a property from the simulation # # This function requests a value from the simulation. # @param self The playernsd::ClientManager instance. # @param ca The client address that this request comes from. # @param prop The name of the property. def prop_get_sim(self, prop, ca): if self.__sim: cid = self.__clients[ca].name self.__sim.prop_get(cid, prop) else: self.__clients[ca].socket.send('propval ' + prop + ' ' + '\n') ## Set a property in the simulation # # This function sets a value from the simulation. # @param self The playernsd::ClientManager instance. # @param _from The client address that this request comes from. # @param prop The name of the property. # @param val The value of the property. def prop_set_sim(self, prop, val, ca): if self.__sim: cid = self.__clients[ca].name self.__sim.prop_set(cid, prop, val) ## Broadcast a message to all clients. # # This is a wrapper function to broadcast a message to all clients. # @param self The playernsd::ClientManager instance. # @param msg The message to be sent to all clients. def broadcast(self, msg): if simulation: command = msg.split(' ') self.__sim.send(command[1], '__broadcast__', msg[msg.find('\n')+1:]) else: for v in self.__clients.itervalues(): if msg.split(' ')[1] != v.name: self.send(msg, v.socket, v.address) ## Receive a message from a client. # # This is a wrapper function to receive a message from a client and # logs it. # @param self The playernsd::ClientManager instance. # @param s The socket to send the message to. # @param ca The client address to send the message to. # @return The message received from the client. def recv(self, s, ca): data = s.recv(MAX_READ) if VERBOSE > 1: self.log(ca, 'RECV(' + str(len(data)) + ')', data) return data ## Receive a message from the simulation. def recv_sim(self, _from, to, msg): self.__clientids[to].socket.send('msgbin ' + _from + ' ' + str(len(msg)) + '\n' + msg) ## Receive a property value from the simulation. def prop_val_sim(self, _from, prop, val): #if val == "": #self.send('error propnotexist\n') # TODO: Handle empty strings separately? #else: self.__clientids[_from].socket.send('propval ' + prop + ' ' + str(val) + '\n') ## Create a log message. # # This is used internally to log sent and received messages. # This is typically only called when --verbose is passed to the daemon. # @param self The playernsd::ClientManager instance. # @param ca Client address related to log message. # @param tag Tag indicating the log level. # @param msg The message to be logged. def log(self, ca, tag, msg): if len(msg): logmsg = tag + ': ' + msg.encode(sys.stdout.encoding, 'backslashreplace').replace('\n', '\\n') log.debug(self.get_id(ca) + ' ' + logmsg) ## Get id of client or else return '__unregistered' def get_id(self, ca): if ca in self.__clients and self.__clients[ca].name != None: return '[' + str(ca) + ', ' + self.__clients[ca].name + ']' else: return '[' + str(ca) + ', __unregistered]' # Callback function that periodically checks all clients to see whether # they are still responding. # @param args Additional arguments. # @param args Additional keyword arguments. def __timeout_check(self, args, kwargs): with self.__client_lock: for k,v in self.__clients.iteritems(): try: self.send('ping\n', v.socket, k) self.__ping_pong[k] -= 1 if self.__ping_pong[k] < -self.missed_ping: log.warn(str(k) + ' has missed at least ' + str(-self.__ping_pong[k]+1) + ' pings, closing connection') self.send('error missedping\n', v.socket, k) v.socket.shutdown(1) except socket.error, msg: if not self.is_timed_out(k): self.__timed_out.append(k) log.warn('Lost connection to ' + str(k) + ' ' + str(msg))