listing24-6.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. from asyncore import dispatcher
  2. from asynchat import async_chat
  3. import socket, asyncore
  4. PORT = 5005
  5. NAME = 'TestChat'
  6. class EndSession(Exception): pass
  7. class CommandHandler:
  8. """
  9. Simple command handler similar to cmd.Cmd from the standard library.
  10. """
  11. def unknown(self, session, cmd):
  12. 'Respond to an unknown command'
  13. session.push('Unknown command: {}s\r\n'.format(cmd))
  14. def handle(self, session, line):
  15. 'Handle a received line from a given session'
  16. if not line.strip(): return
  17. # Split off the command:
  18. parts = line.split(' ', 1)
  19. cmd = parts[0]
  20. try: line = parts[1].strip()
  21. except IndexError: line = ''
  22. # Try to find a handler:
  23. meth = getattr(self, 'do_' + cmd, None)
  24. try:
  25. # Assume it's callable:
  26. meth(session, line)
  27. except TypeError:
  28. # If it isn't, respond to the unknown command:
  29. self.unknown(session, cmd)
  30. class Room(CommandHandler):
  31. """
  32. A generic environment that may contain one or more users (sessions).
  33. It takes care of basic command handling and broadcasting.
  34. """
  35. def __init__(self, server):
  36. self.server = server
  37. self.sessions = []
  38. def add(self, session):
  39. 'A session (user) has entered the room'
  40. self.sessions.append(session)
  41. def remove(self, session):
  42. 'A session (user) has left the room'
  43. self.sessions.remove(session)
  44. def broadcast(self, line):
  45. 'Send a line to all sessions in the room'
  46. for session in self.sessions:
  47. session.push(line)
  48. def do_logout(self, session, line):
  49. 'Respond to the logout command'
  50. raise EndSession
  51. class LoginRoom(Room):
  52. """
  53. A room meant for a single person who has just connected.
  54. """
  55. def add(self, session):
  56. Room.add(self, session)
  57. # When a user enters, greet him/her:
  58. self.broadcast('Welcome to {}\r\n'.format(self.server.name))
  59. def unknown(self, session, cmd):
  60. # All unknown commands (anything except login or logout)
  61. # results in a prodding:
  62. session.push('Please log in\nUse "login <nick>"\r\n')
  63. def do_login(self, session, line):
  64. name = line.strip()
  65. # Make sure the user has entered a name:
  66. if not name:
  67. session.push('Please enter a name\r\n')
  68. # Make sure that the name isn't in use:
  69. elif name in self.server.users:
  70. session.push('The name "{}" is taken.\r\n'.format(name))
  71. session.push('Please try again.\r\n')
  72. else:
  73. # The name is OK, so it is stored in the session, and
  74. # the user is moved into the main room. session.name = name
  75. session.enter(self.server.main_room)
  76. class ChatRoom(Room):
  77. """
  78. A room meant for multiple users who can chat with the others in the room.
  79. """
  80. def add(self, session):
  81. # Notify everyone that a new user has entered:
  82. self.broadcast(session.name + ' has entered the room.\r\n')
  83. self.server.users[session.name] = session
  84. super().add(session)
  85. def remove(self, session):
  86. Room.remove(self, session)
  87. # Notify everyone that a user has left:
  88. self.broadcast(session.name + ' has left the room.\r\n')
  89. def do_say(self, session, line):
  90. self.broadcast(session.name + ': ' + line + '\r\n')
  91. def do_look(self, session, line):
  92. 'Handles the look command, used to see who is in a room'
  93. session.push('The following are in this room:\r\n')
  94. for other in self.sessions:
  95. session.push(other.name + '\r\n')
  96. def do_who(self, session, line):
  97. 'Handles the who command, used to see who is logged in'
  98. session.push('The following are logged in:\r\n')
  99. for name in self.server.users:
  100. session.push(name + '\r\n')
  101. class LogoutRoom(Room):
  102. """
  103. A simple room for a single user. Its sole purpose is to remove the
  104. user's name from the server.
  105. """
  106. def add(self, session):
  107. # When a session (user) enters the LogoutRoom it is deleted
  108. try: del self.server.users[session.name]
  109. except KeyError: pass
  110. class ChatSession(async_chat):
  111. """
  112. A single session, which takes care of the communication with a single user.
  113. """
  114. def __init__(self, server, sock):
  115. super().__init__(sock)
  116. self.server = server
  117. self.set_terminator("\r\n")
  118. self.data = []
  119. self.name = None
  120. # All sessions begin in a separate LoginRoom:
  121. self.enter(LoginRoom(server))
  122. def enter(self, room):
  123. # Remove self from current room and add self to
  124. # next room...
  125. try: cur = self.room
  126. except AttributeError: pass
  127. else: cur.remove(self)
  128. self.room = room
  129. room.add(self)
  130. def collect_incoming_data(self, data):
  131. self.data.append(data)
  132. def found_terminator(self):
  133. line = ''.join(self.data)
  134. self.data = []
  135. try: self.room.handle(self, line)
  136. except EndSession: self.handle_close()
  137. def handle_close(self):
  138. async_chat.handle_close(self)
  139. self.enter(LogoutRoom(self.server))
  140. class ChatServer(dispatcher):
  141. """
  142. A chat server with a single room.
  143. """
  144. def __init__(self, port, name):
  145. super().__init__()
  146. self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
  147. self.set_reuse_addr()
  148. self.bind(('', port))
  149. self.listen(5)
  150. self.name = name
  151. self.users = {}
  152. self.main_room = ChatRoom(self)
  153. def handle_accept(self):
  154. conn, addr = self.accept()
  155. ChatSession(self, conn)
  156. if __name__ == '__main__':
  157. s = ChatServer(PORT, NAME)
  158. try: asyncore.loop()
  159. except KeyboardInterrupt: print()