Welcome, guest | Sign In | My Account | Store | Cart

A simple 94 lines of code "chat" server with various questions and tips for beginner pythoneers.

Python, 174 lines
  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 ;-).