This recipe demos how to write a simple command line chat server & client using multiplexing using select.select. The server uses select call to multiplex multiple clients and the client uses it to multiplex command line & socket I/O.
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 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 | # First the server
#!/usr/bin/env python
#!/usr/bin/env python
"""
A basic, multiclient 'chat server' using Python's select module
with interrupt handling.
Entering any line of input at the terminal will exit the server.
"""
import select
import socket
import sys
import signal
from communication import send, receive
BUFSIZ = 1024
class ChatServer(object):
""" Simple chat server using select """
def __init__(self, port=3490, backlog=5):
self.clients = 0
# Client map
self.clientmap = {}
# Output socket list
self.outputs = []
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.server.bind(('',port))
print 'Listening to port',port,'...'
self.server.listen(backlog)
# Trap keyboard interrupts
signal.signal(signal.SIGINT, self.sighandler)
def sighandler(self, signum, frame):
# Close the server
print 'Shutting down server...'
# Close existing client sockets
for o in self.outputs:
o.close()
self.server.close()
def getname(self, client):
# Return the printable name of the
# client, given its socket...
info = self.clientmap[client]
host, name = info[0][0], info[1]
return '@'.join((name, host))
def serve(self):
inputs = [self.server,sys.stdin]
self.outputs = []
running = 1
while running:
try:
inputready,outputready,exceptready = select.select(inputs, self.outputs, [])
except select.error, e:
break
except socket.error, e:
break
for s in inputready:
if s == self.server:
# handle the server socket
client, address = self.server.accept()
print 'chatserver: got connection %d from %s' % (client.fileno(), address)
# Read the login name
cname = receive(client).split('NAME: ')[1]
# Compute client name and send back
self.clients += 1
send(client, 'CLIENT: ' + str(address[0]))
inputs.append(client)
self.clientmap[client] = (address, cname)
# Send joining information to other clients
msg = '\n(Connected: New client (%d) from %s)' % (self.clients, self.getname(client))
for o in self.outputs:
# o.send(msg)
send(o, msg)
self.outputs.append(client)
elif s == sys.stdin:
# handle standard input
junk = sys.stdin.readline()
running = 0
else:
# handle all other sockets
try:
# data = s.recv(BUFSIZ)
data = receive(s)
if data:
# Send as new client's message...
msg = '\n#[' + self.getname(s) + ']>> ' + data
# Send data to all except ourselves
for o in self.outputs:
if o != s:
# o.send(msg)
send(o, msg)
else:
print 'chatserver: %d hung up' % s.fileno()
self.clients -= 1
s.close()
inputs.remove(s)
self.outputs.remove(s)
# Send client leaving information to others
msg = '\n(Hung up: Client from %s)' % self.getname(s)
for o in self.outputs:
# o.send(msg)
send(o, msg)
except socket.error, e:
# Remove
inputs.remove(s)
self.outputs.remove(s)
self.server.close()
if __name__ == "__main__":
ChatServer().serve()
#############################################################################
# The chat client
#############################################################################
#! /usr/bin/env python
"""
Simple chat client for the chat server. Defines
a simple protocol to be used with chatserver.
"""
import socket
import sys
import select
from communication import send, receive
BUFSIZ = 1024
class ChatClient(object):
""" A simple command line chat client using select """
def __init__(self, name, host='127.0.0.1', port=3490):
self.name = name
# Quit flag
self.flag = False
self.port = int(port)
self.host = host
# Initial prompt
self.prompt='[' + '@'.join((name, socket.gethostname().split('.')[0])) + ']> '
# Connect to server at port
try:
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.connect((host, self.port))
print 'Connected to chat server@%d' % self.port
# Send my name...
send(self.sock,'NAME: ' + self.name)
data = receive(self.sock)
# Contains client address, set it
addr = data.split('CLIENT: ')[1]
self.prompt = '[' + '@'.join((self.name, addr)) + ']> '
except socket.error, e:
print 'Could not connect to chat server @%d' % self.port
sys.exit(1)
def cmdloop(self):
while not self.flag:
try:
sys.stdout.write(self.prompt)
sys.stdout.flush()
# Wait for input from stdin & socket
inputready, outputready,exceptrdy = select.select([0, self.sock], [],[])
for i in inputready:
if i == 0:
data = sys.stdin.readline().strip()
if data: send(self.sock, data)
elif i == self.sock:
data = receive(self.sock)
if not data:
print 'Shutting down.'
self.flag = True
break
else:
sys.stdout.write(data + '\n')
sys.stdout.flush()
except KeyboardInterrupt:
print 'Interrupted.'
self.sock.close()
break
if __name__ == "__main__":
import sys
if len(sys.argv)<3:
sys.exit('Usage: %s chatid host portno' % sys.argv[0])
client = ChatClient(sys.argv[1],sys.argv[2], int(sys.argv[3]))
client.cmdloop()
###############################################################################
# The communication module (communication.py)
###############################################################################
import cPickle
import socket
import struct
marshall = cPickle.dumps
unmarshall = cPickle.loads
def send(channel, *args):
buf = marshall(args)
value = socket.htonl(len(buf))
size = struct.pack("L",value)
channel.send(size)
channel.send(buf)
def receive(channel):
size = struct.calcsize("L")
size = channel.recv(size)
try:
size = socket.ntohl(struct.unpack("L", size)[0])
except struct.error, e:
return ''
buf = ""
while len(buf) < size:
buf = channel.recv(size - len(buf))
return unmarshall(buf)[0]
|
This demonstrates the power of select.select call which can be used as a very efficient I/O multiplexer for any I/O stream such as sockets, file streams etc.
Here is a sample session with the chat server/client.
Starting the server
$ python chatserver.py &
Starting the client
[anand@localhost python]$ python chatclient.py anand 3490 Connected to chat server@3490 [anand@127.0.0.1]> (Connected: New client (2) from appy@127.0.0.1) [anand@127.0.0.1]> hi appy [anand@127.0.0.1]> (Hung up: Client from appy@127.0.0.1) [anand@127.0.0.1]> ^C [anand@127.0.0.1]> Interrupted. [anand@localhost python]$
asyncore & asynchat modules provide wrappers for event handling using select/poll in Python. Twistedmatrix provides an advanced reactor pattern using select.
[28/09/07] - Changed to work across machines by using network byte addressing. The communication module has been borrowed from recipe #457669.
One problem using socket.ntohl. There is one horrible problem using socket.ntohl:
Just try to send a string with at least 230 chars. It's a bit weird the fact that sending something with 530 chars will work fine.
How to resolve the CPU problem? when i started the server and connected as a client, CPU almost 60%...
Please help
very thanks for this code >>> but there is a problem i really faced for along time.
Traceback (most recent call last): File "C:/Users/TOSHIBA/Desktop/v.py", line 17, in <module> from cmmunication import send, receive ImportError: No module named cmmunication
how can i solve this would somebody help me
i received this error
Traceback (most recent call last): File "C:\Users\TOSHIBA\Desktop\recipe-531824-1.py", line 17, in <module> from communication import send, receive ImportError: No module named communication
how can i solve this is this code work in windows and symbian mobiles ?? if not how can i chang it?? thanks
Well... Program is intersting, i have been trying to understand network programming for a bit. since I am teaching my self with tutorials and things like analyzing premade code, this is helpful. I do keep getting an error however at line 54 of the server module:
54>>> inputready,outputready,exceptready = select.select(inputs, self.outputs, []) except select.error, e: break except socket.error, e: break
the error is:
File "C:/Users/Owner/Desktop/server.py", line 54, in serve inputready,outputready,exceptready = select.select(inputs, self.outputs, []) TypeError: argument must be an int, or have a fileno() method.
I am not sure where the select module is and it would probably be helpful if i could see the code for the select module, but i cant.
One of the arguments is obviosly not an integer when it is put into the function, but i am not sure how to fix this. My first attemp (which was probably pointless) i put int(*) around the arguments, and tested it on one or the other.
Each time it came out saying the argument for int must be a string or a number. I am not sure what either of the arguments in line 54.
If any one could help me with this it would be greatly appreciated
@Myles:
Select.select expects a file-like handle, including sockets or integers representing file descriptors. The error you got, "argument must be an int, or have a fileno() method", means that one of the inputs you provided did not implement a file-like interface (usually because it is an undefined variable).
Is self.server a socket? Did you make sure it is defined?
See the select module documentation for more information.
The select.select on sys.stdin will work only on POSIX systems (Linux/Unix etc). It won't work on Windows. So this code cannot execute on windows as it is. Perhaps there is a way around it, but I haven't tried it yet.
One problem using socket.ntohl. There is one horrible problem using socket.ntohl:
Just try to send a string with at least 230 chars. It's a bit weird the fact that sending something with 530 chars will work fine....
Thinking about this particular issue... Is it possible to actually skip the socket.htonl call by just pushing that responsibility onto struct.pack('>L', actual_lenght)? I mean, by getting the integer as Big-Endian... the '>' before the 'L' or alternatively an '!'. As described in http://docs.python.org/release/2.6.6/library/struct.html#struct-alignment
Would it help?
wonderful recipe but it has a serious bug with respect to multi-packet messages; it locks up if the message is bigger than the MTU. Fix:
--- recipe-531824-1.py.orig 2011-03-18 14:04:04.087992789 -0700 +++ recipe-531824-1.py 2011-03-18 14:04:30.481874871 -0700 @@ -246,6 +246,6 @@ buf = ""
- buf = channel.recv(size - len(buf)) + buf += channel.recv(size - len(buf))
let me try that patch one more time; I'm unfamiliar with the 'Markdown syntax' for this discussion board:
i have a problem with it
from communication import send, receive ImportError: No module named 'communication'
how can i solve it in python3?
shahab. The code expecting communication to be in a separate module or file. Just create a new file names "communication.py" and place everything below and including ############################################################################### # The communication module (communication.py) ############################################################################### in the new file.