from asyncore import dispatcher from asynchat import async_chat import socket, asyncore PORT = 5005 NAME = 'TestChat' class EndSession(Exception): pass class CommandHandler: """ Simple command handler similar to cmd.Cmd from the standard library. """ def unknown(self, session, cmd): 'Respond to an unknown command' session.push('Unknown command: {}s\r\n'.format(cmd)) def handle(self, session, line): 'Handle a received line from a given session' if not line.strip(): return # Split off the command: parts = line.split(' ', 1) cmd = parts[0] try: line = parts[1].strip() except IndexError: line = '' # Try to find a handler: meth = getattr(self, 'do_' + cmd, None) try: # Assume it's callable: meth(session, line) except TypeError: # If it isn't, respond to the unknown command: self.unknown(session, cmd) class Room(CommandHandler): """ A generic environment that may contain one or more users (sessions). It takes care of basic command handling and broadcasting. """ def __init__(self, server): self.server = server self.sessions = [] def add(self, session): 'A session (user) has entered the room' self.sessions.append(session) def remove(self, session): 'A session (user) has left the room' self.sessions.remove(session) def broadcast(self, line): 'Send a line to all sessions in the room' for session in self.sessions: session.push(line) def do_logout(self, session, line): 'Respond to the logout command' raise EndSession class LoginRoom(Room): """ A room meant for a single person who has just connected. """ def add(self, session): Room.add(self, session) # When a user enters, greet him/her: self.broadcast('Welcome to {}\r\n'.format(self.server.name)) def unknown(self, session, cmd): # All unknown commands (anything except login or logout) # results in a prodding: session.push('Please log in\nUse "login "\r\n') def do_login(self, session, line): name = line.strip() # Make sure the user has entered a name: if not name: session.push('Please enter a name\r\n') # Make sure that the name isn't in use: elif name in self.server.users: session.push('The name "{}" is taken.\r\n'.format(name)) session.push('Please try again.\r\n') else: # The name is OK, so it is stored in the session, and # the user is moved into the main room. session.name = name session.enter(self.server.main_room) class ChatRoom(Room): """ A room meant for multiple users who can chat with the others in the room. """ def add(self, session): # Notify everyone that a new user has entered: self.broadcast(session.name + ' has entered the room.\r\n') self.server.users[session.name] = session super().add(session) def remove(self, session): Room.remove(self, session) # Notify everyone that a user has left: self.broadcast(session.name + ' has left the room.\r\n') def do_say(self, session, line): self.broadcast(session.name + ': ' + line + '\r\n') def do_look(self, session, line): 'Handles the look command, used to see who is in a room' session.push('The following are in this room:\r\n') for other in self.sessions: session.push(other.name + '\r\n') def do_who(self, session, line): 'Handles the who command, used to see who is logged in' session.push('The following are logged in:\r\n') for name in self.server.users: session.push(name + '\r\n') class LogoutRoom(Room): """ A simple room for a single user. Its sole purpose is to remove the user's name from the server. """ def add(self, session): # When a session (user) enters the LogoutRoom it is deleted try: del self.server.users[session.name] except KeyError: pass class ChatSession(async_chat): """ A single session, which takes care of the communication with a single user. """ def __init__(self, server, sock): super().__init__(sock) self.server = server self.set_terminator("\r\n") self.data = [] self.name = None # All sessions begin in a separate LoginRoom: self.enter(LoginRoom(server)) def enter(self, room): # Remove self from current room and add self to # next room... try: cur = self.room except AttributeError: pass else: cur.remove(self) self.room = room room.add(self) def collect_incoming_data(self, data): self.data.append(data) def found_terminator(self): line = ''.join(self.data) self.data = [] try: self.room.handle(self, line) except EndSession: self.handle_close() def handle_close(self): async_chat.handle_close(self) self.enter(LogoutRoom(self.server)) class ChatServer(dispatcher): """ A chat server with a single room. """ def __init__(self, port, name): super().__init__() self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.set_reuse_addr() self.bind(('', port)) self.listen(5) self.name = name self.users = {} self.main_room = ChatRoom(self) def handle_accept(self): conn, addr = self.accept() ChatSession(self, conn) if __name__ == '__main__': s = ChatServer(PORT, NAME) try: asyncore.loop() except KeyboardInterrupt: print()