A simple 94 lines of code "chat" server with various questions and tips for beginner pythoneers.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | #!/usr/bin/env python
# A simple 94(*) lines of code "chat" server. Creates a server on port 4000.
# Users telnet to your machine, at port 4000, and can chat with each other.
# Use "quit" to disconnect yourself, and "shutdown" to shut down the server.
#
# Adapted from J. Strout version at
# http://www.strout.net/info/coding/python/tidbits.html#server
# Modified by me (Steve <lonetwin@yahoo.com>) as a follow-up assignment for
# a basic python lecture I conducted.
#
# Instructions: I have put various comments in the code.
# Some comments are preceded with the marker [Tip]. These comments
# explain some coding-style rules that might commonly be seen in python code.
# Some comments are preceded with the marker [n], where 'n' is a number.
# These comments have questions that have to be answered by mail to me.
# Please write the question number before the answer. To find the answers to
# these questions you may have to read the python documentation or search the
# web. In any case, feel free to contact me if you find any that are hard to
# answer.
# The comments at the bottom of the file are enhancements to this
# program. You are expected to write these on your own. You are free to use
# any references, including any code that you might find on the net for this
# purpose, with the only condition being that you should credit the source
# when you hand in the assignment.
#
from socket import * # [1] What does this statement do ?
import time # [2] How does this statement differ from the previous?
# [Tip] Read Chap. 6 of the official Python Tutorial
# define global variables
HOST = '' # [Tip] In the socket module, an empty string for the
# hostname by default maps to 'localhost'
PORT = 4000 # [3] Can I use 40 here instead ? why ?
endl = "\r\n" # [4] What is this character sequence and why has
# it been defined separately ?
userlist = [] # list of connected users
# some constants used to flag the state of user
kAskName, kWaitName, kOK = 0, 1, 2
# [5] What is the scope of all the variables defined above ?
# [Tip] Read section 9.2 of the official Python Tutorial
class User:
"""
class to store info about connected users
"""
def __init__(self, conn, addr):
self.conn, self.addr = conn, addr
self.name, self.step = "", kAskName
def poll(self):
if self.step == kAskName: self.AskName()
# [Tip] blocks with single statements need not be written on an
# indented line.
def AskName(self):
self.conn.send("Name? ")
self.step = kWaitName
def HandleMsg(self, msg):
print "Handling message: %s" % msg
global userlist # [6] What is the purpose of this statement ?
# if waiting for name, record it
if self.step == kWaitName:
# try to trap initial garbage sent by some telnet programs ...
if len(msg) < 2 or msg == "#": return
print "Setting name to: %s" % msg
self.name, self.step = msg, kOK
self.SendMsg("Hello, %s" % self.name)
broadcast("%s has connected." % self.name)
return
if msg == "quit": # check for commands
broadcast("%s has quit." % self.name)
self.conn.close()
userlist.remove(self)
else: # otherwise, broadcast msg
broadcast("%s: %s" % (self.name, msg))
def SendMsg(self, msg):
self.conn.send(msg + endl)
def pollNewConn(s):
"""
Routine to check for incoming connections. Accepts a socket object 's' as
argument and returns a User object 'user'
"""
try:
conn, addr = s.accept() # [Tip] Read the socket HOWTO
except:
return None
print "Connection from ", addr
conn.setblocking(0) # [7] What is the effect of this call ?
user = User(conn, addr)
return user
def broadcast(msg): # [8] Does this belong to the User class ?? support your
# answer with sufficient reasoning.
"""
Routine to broadcast a message to all connected users. Takes the argument
message 'msg', in the form '<sender>: <message>'
"""
map(lambda u: u.SendMsg(msg), \
filter(lambda u: u.name != msg.split(':')[0], \
userlist))
# [9] Convert the above map+lambda+filter into a readable loop+conditional
# Main Program
def main():
# set up the server
s = socket(AF_INET, SOCK_STREAM)
s.bind((HOST, PORT))
s.setblocking(0)
s.listen(1)
print "Waiting for connection(s)..."
# loop until done, handling connections and incoming messages
done = 0 # set to 1 to shut this down
while not done:
try:
time.sleep(1) # sleep to reduce processor usage
u = pollNewConn(s) # check for incoming connections
if u:
userlist.append(u)
print "%d connection(s)" % len(userlist)
for u in userlist: # check all connected users
u.poll()
try:
data = u.conn.recv(1024)
data = filter(lambda x: x >= ' ' and x <= 'z', data)
# [10] Rewrite the above statement as a List Comprehension.
data = data.strip()
if data:
print "From %s: [%s]" % (u.name, data)
u.HandleMsg(data)
if data == "shutdown": done=1
except:
pass # [11] What exception are we ignoring ? and why ?
except KeyboardInterrupt:
done=1
else: # [12] What does this statement imply ?
print "Shutting down ..." # [Tip] Read section 4.4 of the official
s.shutdown(2) # Python Tutorial
s.close()
# We are done, close all connections
for u in userlist:
u.conn.close()
if __name__ == '__main__': # [Tip] Read about the if __name__ == '__main__'
main() # trick in chap. 2 at diveintopython.org
# Rewrite the server program to do the following things:
# a) Log it's status/action messages (*not* the actual chat msgs) to a log
# file as well as print it out to the console.
# b) Enhance the User class to recognize more commands besides 'quit'.
# [Tip] We can implement this using a 'dispatcher' for example, I should be
# able to do something like this in the HandleMsg method:
# if msg in commands:
# cmd = getattr(self, command)
# cmd()
# c) Design and implement a client for this server.
# d) Take care of the problem in question no. [11]
# [Tip] Read section 5 of the Python Socket Programming HOWTO.
# e) Create a chat client/server package
# [Tip] Read 6.4 of the Official Python Tutorial
# (*) 94 == `egrep -v "(^[ ]*#)|(^[ ]*$)" chat_server.py | wc -l`
# vim: set tw=80 et ai sts=4 sw=4 ts=8:
|
Sometime back I conducted short (two 2 hr. sessions) python introduction course for some new-to-python-but-old-to-programming youngsters. At the end of the course I wanted to give them an assignment which was was simple enough for beginner pythoneers without being too trivial. While searching the net for something like this I stumbled across the python titbits page (http://www.strout.net/python/tidbits.html) where I found the chat_server.py script, which fit the bill perfectly and also inspired me to take a different approach to creating programming assignments.
Since I have always believed, reading code is as important as writing code, I modified the script and added various questions and requests for alternative approaches to solving problems in code that has been written and designed by someone else without having to do a total rewrite.
This turned out to be an instant success, to say the least. The students took up the thing as a challenge and best of all had fun doing their assignment. I'm sharing it here just in case somebody is looking for something like this, as I was ...also for the book ;-).