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 457 chat server from your own machine, but that’s not much fun in the long run, is it?) To be able to connect, the user has to know the address of your machine (a machine name such as foo.bar.baz.com or an IP number). In addition, the user must know the port number used by your server. You can set this in your program; in the code in this chapter I use the (rather arbitrary) number 5005. ■Note As mentioned in Chapter 14, certain port numbers are restricted and require administrator privileges. In general, numbers greater than 1023 are okay. To test your server you need a client—the program on the user side of the interaction. A simple program for this sort of thing is telnet (which basically lets you connect to any socket server). In UNIX, you probably have this program available on the command line: $ telnet some.host.name 5005 The preceding command connects to the machine some.host.name on port 5005. To connect to the same machine on which you’re running the telnet command, simply use the machine name localhost. (You might want to supply an escape character through the -e switch to make sure you can quit telnet easily. See the man page for more details.) In Windows, you can use either the standard telnet command (in a DOS window) or a terminal emulator with telnet functionality, such as PuTTY (software and more information available at http://www.chiark.greenend.org.uk/~sgtatham/putty). However, if you are installing new software, you might as well get a client program tailored to chatting. MUD (or MUSH or MOO or some other related acronym) clients are quite suitable for this sort of thing. My client of choice is TinyFugue (software and more information available at http://tf.tcp.com). It is mainly designed for use in UNIX. (There are several clients available for Windows as well; just do a Web search for “mud client” or something similar.) First Implementation Let’s break things down a bit. We need to create two main classes: one representing the chat server and one representing each of the chat sessions (the connected users). The ChatServer Class To create the basic ChatServer, you subclass the dispatcher class from asyncore. The dispatcher is basically just a socket object, but with some extra event handling features, which you’ll be using in a minute. See Listing 24-1 for a basic chat server program (that does very little).
458 CHAPTER 24 ■ PROJECT 5: A VIRTUAL TEA PARTY Listing 24-1. A Minimal Server Program from asyncore import dispatcher import asyncore class ChatServer(dispatcher): pass s = ChatServer() asyncore.loop() If you run this program, nothing happens. To make the server do anything interesting, you should call its create_socket method to create a socket, and its bind and listen methods to bind the socket to a specific port number and to tell it to listen for incoming connections. (That is what servers do, after all.) In addition, you’ll override the handle_accept event handling method to actually do something when the server accepts a client connection. The resulting program is shown in Listing 24-2. Listing 24-2. A Server That Accepts Connections from asyncore import dispatcher import socket, asyncore class ChatServer(dispatcher): def handle_accept(self): conn, addr = self.accept() print 'Connection attempt from', addr[0] s = ChatServer() s.create_socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(('', 5005)) s.listen(5) asyncore.loop() The handle_accept method calls self.accept, which lets the client connect. This returns a connection (a socket that is specific for this client) and an address (information about which machine is connecting). Instead of doing anything useful with this connection, the handle_accept method simply prints that a connection attempt was made. addr[0] is the IP address of the client. The server initialization calls create_socket with two arguments that specify the type of socket you want. You could use different types, but those shown here are what you usually want. The call to the bind method simply binds the server to a specific address (host name and port). The host name is empty (an empty string, essentially meaning “localhost,” or, more technically, “all interfaces on this machine”) and the port number is 5005. The call to listen tells the server to listen for connections; it also specifies a backlog of five connections. The final call to asyncore.loop starts the server’s listening loop as before. This server actually works. Try to run it and then connect to it with your client. The client should immediately be disconnected, and the server should print out the following:
- Page 437 and 438: 406 CHAPTER 20 ■ PROJECT 1: INSTA
- Page 439 and 440: 408 CHAPTER 20 ■ PROJECT 1: INSTA
- Page 442 and 443: CHAPTER 21 ■ ■ ■ Project 2: P
- 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 490 and 491: CHAPTER 24 ■ PROJECT 5: A VIRTUAL
- Page 492 and 493: CHAPTER 24 ■ PROJECT 5: A VIRTUAL
- Page 494 and 495: 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
458 CHAPTER 24 ■ PROJECT 5: A VIRTUAL TEA PARTY<br />
Listing 24-1. A Minimal Server Program<br />
from asyncore import dispatcher<br />
import asyncore<br />
class ChatServer(dispatcher): pass<br />
s = ChatServer()<br />
asyncore.loop()<br />
If you run this program, nothing happens. To make the server do anything interesting, you<br />
should call its create_socket method <strong>to</strong> create a socket, and its bind and listen methods <strong>to</strong><br />
bind the socket <strong>to</strong> a specific port number and <strong>to</strong> tell it <strong>to</strong> listen for incoming connections. (That<br />
is what servers do, after all.) In addition, you’ll override the handle_accept event handling<br />
method <strong>to</strong> actually do something when the server accepts a client connection. The resulting<br />
program is shown in Listing 24-2.<br />
Listing 24-2. A Server That Accepts Connections<br />
from asyncore import dispatcher<br />
import socket, asyncore<br />
class ChatServer(dispatcher):<br />
def handle_accept(self):<br />
conn, addr = self.accept()<br />
print 'Connection attempt from', addr[0]<br />
s = ChatServer()<br />
s.create_socket(socket.AF_INET, socket.SOCK_STREAM)<br />
s.bind(('', 5005))<br />
s.listen(5)<br />
asyncore.loop()<br />
The handle_accept method calls self.accept, which lets the client connect. This returns<br />
a connection (a socket that is specific for this client) and an address (information about which<br />
machine is connecting). Instead of doing anything useful with this connection, the handle_accept<br />
method simply prints that a connection attempt was made. addr[0] is the IP address of the client.<br />
The server initialization calls create_socket with two arguments that specify the type of<br />
socket you want. You could use different types, but those shown here are what you usually<br />
want. The call <strong>to</strong> the bind method simply binds the server <strong>to</strong> a specific address (host name and<br />
port). The host name is empty (an empty string, essentially meaning “localhost,” or, more technically,<br />
“all interfaces on this machine”) and the port number is 5005. The call <strong>to</strong> listen tells<br />
the server <strong>to</strong> listen for connections; it also specifies a backlog of five connections. The final call<br />
<strong>to</strong> asyncore.loop starts the server’s listening loop as before.<br />
This server actually works. Try <strong>to</strong> run it and then connect <strong>to</strong> it with your client. The client<br />
should immediately be disconnected, and the server should print out the following: