Beginning Python - From Novice to Professional
Beginning Python - From Novice to Professional Beginning Python - From Novice to Professional
CHAPTER 24 ■ PROJECT 5: A VIRTUAL TEA PARTY 463 self.set_reuse_addr() self.bind(('', port)) self.listen(5) self.name = name self.sessions = [] def disconnect(self, session): self.sessions.remove(session) def broadcast(self, line): for session in self.sessions: session.push(line + '\r\n') def handle_accept(self): conn, addr = self.accept() self.sessions.append(ChatSession(self, conn)) if __name__ == '__main__': s = ChatServer(PORT, NAME) try: asyncore.loop() except KeyboardInterrupt: print Second Implementation The first prototype may be a fully functioning chat server, but its functionality is quite limited. The most obvious limitation is that you can’t discern who is saying what. Also, it does not interpret commands (such as say or logout), which the original specification requires. So, you need to add support for identity (one unique name per user) and command interpretation, and you must make the behavior of each session depend on the state it’s in (just connected, logged in, and so on)—all of this in a manner that lends itself easily to extension. Basic Command Interpretation I’ll show you how to model the command interpretation on the Cmd class of the cmd module in the standard library. (Unfortunately, you can’t use this class directly because it can only be used with sys.stdin and sys.stdout, and you’re working with several streams.) What you need is a function or method that can handle a single line of text (as typed by the user). It should split off the first word (the command) and call an appropriate method based on it. For example, the line say Hello, world! might result in the call do_say('Hello, world!')
464 CHAPTER 24 ■ PROJECT 5: A VIRTUAL TEA PARTY possibly with the session itself as an added argument (so do_say would know who did the talking). Here is a simple implementation, with an added method to express that a command is unknown: class CommandHandler: """ Simple command handler similar to cmd.Cmd from the standard library. """ def unknown(self, session, cmd): session.push('Unknown command: %s\r\n' % cmd) def handle(self, session, line): if not line.strip(): return parts = line.split(' ', 1) cmd = parts[0] try: line = parts[1].strip() except IndexError: line = '' meth = getattr(self, 'do_'+cmd, None) if callable(meth): meth(session, line) else: self.unknown(session, cmd) The use of getattr in this class is similar to that in Chapter 20. With the basic command handling out of the way, you need to define some actual commands—and which commands are available (and what they do) should depend on the current state of the session. How do you represent that state? Rooms Each state can be represented by a custom command handler. This is easily combined with the standard notion of chat rooms (or locations in a MUD). Each room is a CommandHandler with its own specialized commands. In addition, it should keep track of which users (sessions) are currently inside it. Here is a generic superclass for all your rooms: class EndSession(Exception): pass class Room(CommandHandler): """ A generic environment which may contain one or more users (sessions). It takes care of basic command handling and broadcasting. """
- Page 444 and 445: CHAPTER 21 ■ PROJECT 2: PAINTING
- Page 446 and 447: CHAPTER 21 ■ PROJECT 2: PAINTING
- Page 448 and 449: CHAPTER 21 ■ PROJECT 2: PAINTING
- Page 450 and 451: CHAPTER 21 ■ PROJECT 2: PAINTING
- Page 452 and 453: CHAPTER 22 ■ ■ ■ Project 3: X
- Page 454 and 455: CHAPTER 22 ■ PROJECT 3: XML FOR A
- Page 456 and 457: CHAPTER 22 ■ PROJECT 3: XML FOR A
- Page 458 and 459: CHAPTER 22 ■ PROJECT 3: XML FOR A
- Page 460 and 461: CHAPTER 22 ■ PROJECT 3: XML FOR A
- Page 462 and 463: CHAPTER 22 ■ PROJECT 3: XML FOR A
- Page 464 and 465: CHAPTER 22 ■ PROJECT 3: XML FOR A
- Page 466 and 467: CHAPTER 22 ■ PROJECT 3: XML FOR A
- Page 468: CHAPTER 22 ■ PROJECT 3: XML FOR A
- Page 471 and 472: 440 CHAPTER 23 ■ PROJECT 4: IN TH
- Page 473 and 474: 442 CHAPTER 23 ■ PROJECT 4: IN TH
- Page 475 and 476: 444 CHAPTER 23 ■ PROJECT 4: IN TH
- Page 477 and 478: 446 CHAPTER 23 ■ PROJECT 4: IN TH
- Page 479 and 480: 448 CHAPTER 23 ■ PROJECT 4: IN TH
- Page 481 and 482: 450 CHAPTER 23 ■ PROJECT 4: IN TH
- Page 483 and 484: 452 CHAPTER 23 ■ PROJECT 4: IN TH
- Page 486 and 487: CHAPTER 24 ■ ■ ■ Project 5: A
- Page 488 and 489: CHAPTER 24 ■ PROJECT 5: A VIRTUAL
- Page 490 and 491: CHAPTER 24 ■ PROJECT 5: A VIRTUAL
- Page 492 and 493: CHAPTER 24 ■ PROJECT 5: A VIRTUAL
- Page 496 and 497: CHAPTER 24 ■ PROJECT 5: A VIRTUAL
- Page 498 and 499: CHAPTER 24 ■ PROJECT 5: A VIRTUAL
- Page 500 and 501: CHAPTER 24 ■ PROJECT 5: A VIRTUAL
- Page 502 and 503: CHAPTER 24 ■ PROJECT 5: A VIRTUAL
- Page 504 and 505: CHAPTER 25 ■ ■ ■ Project 6: R
- Page 506 and 507: CHAPTER 25 ■ PROJECT 6: REMOTE ED
- Page 508 and 509: CHAPTER 25 ■ PROJECT 6: REMOTE ED
- Page 510 and 511: CHAPTER 25 ■ PROJECT 6: REMOTE ED
- Page 512: CHAPTER 25 ■ PROJECT 6: REMOTE ED
- Page 515 and 516: 484 CHAPTER 26 ■ PROJECT 7: YOUR
- Page 517 and 518: 486 CHAPTER 26 ■ PROJECT 7: YOUR
- Page 519 and 520: 488 CHAPTER 26 ■ PROJECT 7: YOUR
- Page 521 and 522: 490 CHAPTER 26 ■ PROJECT 7: YOUR
- Page 523 and 524: 492 CHAPTER 26 ■ PROJECT 7: YOUR
- Page 525 and 526: 494 CHAPTER 26 ■ PROJECT 7: YOUR
- Page 527 and 528: 496 CHAPTER 26 ■ PROJECT 7: YOUR
- Page 529 and 530: 498 CHAPTER 26 ■ PROJECT 7: YOUR
- Page 531 and 532: 500 CHAPTER 27 ■ PROJECT 8: FILE
- Page 533 and 534: 502 CHAPTER 27 ■ PROJECT 8: FILE
- Page 535 and 536: 504 CHAPTER 27 ■ PROJECT 8: FILE
- Page 537 and 538: 506 CHAPTER 27 ■ PROJECT 8: FILE
- Page 539 and 540: 508 CHAPTER 27 ■ PROJECT 8: FILE
- Page 541 and 542: 510 CHAPTER 27 ■ PROJECT 8: FILE
- Page 543 and 544: 512 CHAPTER 27 ■ PROJECT 8: FILE
464 CHAPTER 24 ■ PROJECT 5: A VIRTUAL TEA PARTY<br />
possibly with the session itself as an added argument (so do_say would know who did the<br />
talking). Here is a simple implementation, with an added method <strong>to</strong> express that a command<br />
is unknown:<br />
class CommandHandler:<br />
"""<br />
Simple command handler similar <strong>to</strong> cmd.Cmd from the standard<br />
library.<br />
"""<br />
def unknown(self, session, cmd):<br />
session.push('Unknown command: %s\r\n' % cmd)<br />
def handle(self, session, line):<br />
if not line.strip(): return<br />
parts = line.split(' ', 1)<br />
cmd = parts[0]<br />
try: line = parts[1].strip()<br />
except IndexError: line = ''<br />
meth = getattr(self, 'do_'+cmd, None)<br />
if callable(meth):<br />
meth(session, line)<br />
else:<br />
self.unknown(session, cmd)<br />
The use of getattr in this class is similar <strong>to</strong> that in Chapter 20.<br />
With the basic command handling out of the way, you need <strong>to</strong> define some actual<br />
commands—and which commands are available (and what they do) should depend on the<br />
current state of the session. How do you represent that state?<br />
Rooms<br />
Each state can be represented by a cus<strong>to</strong>m command handler. This is easily combined with the<br />
standard notion of chat rooms (or locations in a MUD). Each room is a CommandHandler with its<br />
own specialized commands. In addition, it should keep track of which users (sessions) are<br />
currently inside it. Here is a generic superclass for all your rooms:<br />
class EndSession(Exception): pass<br />
class Room(CommandHandler):<br />
"""<br />
A generic environment which may contain one or more users<br />
(sessions). It takes care of basic command handling and<br />
broadcasting.<br />
"""