class HServer: def __init__(self, host, port): self.host = host self.port = port self.ln = Listener(host, port) self.tk = Talker(host, port) thread.start_new_thread(self.ln.listening, (self.recv_data,)) self.client_list = {} def recv_data(self, data): """ Callback function to receive from the server. Note that data is NOT deserialized yet. Args: socket (socket): the socket that is connected to the server. """ self.process_packet(pickle.loads(data)) def send_ack_message(self, msg): """ Inform the client that the request succeeds. Args: msg (Message): the original message. """ logging.info('Sending ACK') ack_msg = pickle.dumps(Message('', Message.ACK, id = msg.id)) self.tk.talk(ack_msg, msg.origin[0], msg.origin[1]) def send_fail_message(self, msg): """ Inform the client that the request fails. Args: msg (Message): the original message. """ fail_msg = pickle.dump(Message('', Message.FAIL, id = msg.id)) self.tk.talk(fail_msg, msg.origin[0], msg.origin[1]) def process_packet(self, data): """ Process a packet after receiving. Args: data (Object): data that has been deserialized by pickle. """ if data.type == Message.JOIN: # update user. username = data.msg if username not in self.client_list: self.client_list[username] = data.origin logging.info('User "%s" joined', username) else: logging.warning('User "%s" is already in the server.', username) elif data.type == Message.MESSAGE: # broadcast the message to all other users. print 'receive new message:"%s"'%data.msg print 'broadcasting...' logging.info('Receive message from (%s, %s) with id %s', data.origin[0] , data.origin[1], data.id) data.type = Message.BROADCAST for username in self.client_list: self.tk.talk(pickle.dumps(data), self.client_list[username][0], self.client_list[username][1]) elif data.type == Message.LEAVE: username = data.msg logging.info('User "%s" is leaving.', username) if username not in self.client_list: return else: self.client_list.pop(username, None) elif data.type == Message.CHANGE_NAME: # We need to check that the new name is not used, and the old name # exists. If one of the condition fails, inform the sender. oldname, newname = data.msg.split(' ')[0], data.msg.split(' ')[1] print "user %s changes the name to %s" %(oldname, newname) logging.info('Change username from %s to %s', oldname, newname) if oldname not in self.client_list or newname in self.client_list: logging.warning('Change username from %s to %s unsuccessfully.' , oldname, newname) self.send_fail_message(data) else: self.client_list[newname] = self.client_list.pop(oldname) self.send_ack_message(data)
class HClient: """ HClient is another alternative to IRCClient. This only connects to HServer. The difference between this and IRCClient is that, for this, user will need to specify the listening port that this program will bind to. Example: c = HClient(host, port) c.connect_server(dhost, dport) c.set_username(user) while True: line = raw_input() c.on_typing(line) """ def __init__(self, host, port, dhost = '', dport = 0): self.host = host self.port = port self.dhost = dhost self.dport = dport self.prev_cmd = '' self.user = '' self.msg_id = 0 self.ack_status = {} self.irc_mode = False self.parser = Parser() self.tk = Talker(self.host, self.port) self.ln = Listener(self.host, self.port) thread.start_new_thread(self.ln.listening, (self.receive_packet,)) if self.dhost and self.dport: self.connect_server(self.dhost, self.dport) def on_typing(self, line): """ Process what user types. Args: line: input from user. """ msg = self.parser.parse(line) if(msg.type == Message.COMMAND): self.dispatch_command(msg) elif msg.type == Message.MESSAGE: self.say(msg) def set_username(self, newname): """ Set nickname """ if not self.dhost or not self.dport: self.user = newname else: self.request_change_name(newname) def request_change_name(self, newname): """ Request to change a new username. This method will BLOCK until it completes either successfully or unsuccessfully. Args: newname (string): new username. """ msg = Message("%s %s"%(self.user, newname), Message.CHANGE_NAME, origin = (self.host, self.port), destination = (self.dhost, self.dport)) self.increment_msg_id(msg) self.ack_status[msg.id] = MessageStatus.PENDING self.tk.talk(pickle.dumps(msg), self.dhost, self.dport) while self.ack_status[msg.id] is MessageStatus.PENDING: pass if self.ack_status[msg.id] is MessageStatus.SUCCESS: self.ack_status.pop(msg.id) return True else: self.ack_status.pop(msg.id) return False def increment_msg_id(self, msg): """ Give each message a unique message ID. Args: msg (Message): the message to assign ID for. """ msg.id = self.msg_id self.msg_id += 1 def connect_server(self, dest_host, dest_port): """ Join a chat server. Args: dest_host (string): address of server. dest_port (int): port of server. """ self.dhost = dest_host self.dport = dest_port if not self.user: self.user = '******' msg = Message(self.user, Message.JOIN, origin = (self.host, self.port), destination =(self.dhost, self.dport)) self.tk.talk(pickle.dumps(msg), self.dhost, self.dport) def leave_server(self): """ Leave a chat server. This function resets the dhost and dport. Args: talker (Talker): the network worker that executes the sending of packet. """ msg = Message(self.user, Message.LEAVE, (self.host, self.port), (self.dhost, self.dport)) self.tk.talk(pickle.dumps(msg), self.dhost, self.dport) self.dhost = '' self.dport = 0 def say(self, msg): """ Broadcast a message. Args: msg (Message): message to broadcast. """ if not self.dhost or not self.dport: print 'You need to connect to a HServer.' msg.origin = (self.host, self.port) msg.destination = (self.dhost, self.dport) msg.author = self.user self.increment_msg_id(msg) self.tk.talk(pickle.dumps(msg), self.dhost, self.dport) def dispatch_command(self, msg): """ Dispatch the command. Called when user type a command. Args: msg (Message): message that contains the command, after parsed. """ if msg.cmd_args[0] == 'JOIN': dest_host = msg.cmd_args[1] dest_port = int(msg.cmd_args[2]) self.connect_server(dest_host, dest_port) elif msg.cmd_args[0] == 'NICK': # this will block until username is changed, or unsuccessfully # reported by the server. if self.dhost and self.dport: status = self.request_change_name(msg.cmd_args[1]) else: status = True if status is True: print '>>> change successfully' self.user = msg.cmd_args[1] else: print '>>> change of username unsuccessfully' elif msg.cmd_args[0] == 'LEAVE': self.leave_server() def process_packet(self, data): """ Process a packet after receiving. Args: data (Object): data that has been deserialized by pickle. """ if data.type is Message.BROADCAST: print "%s: %s" %(data.author, data.msg) elif data.type is Message.ACK: self.ack_status[data.id] = MessageStatus.SUCCESS elif data.type is Message.FAIL: self.ack_status[data.id] = MessageStatus.FAIL def receive_packet(self, data): """ Callback function when listener receives a packet. Args: data (Object): data that has been deserialized by pickle. """ self.process_packet(pickle.loads(data))