# 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]