123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192 |
- 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 <nick>"\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()
|