python 2.6 windows 7
I am trying to put together a simple tutorial on how to write collaborative multitasking programs. As an example application, I wrote a chat server with a python asyncore backend. I think this will be a valuable resource for the community. However, I have not yet got it to work, so this post.
The structure is as follows. The ChatServer instance is running on the remote computer. The socket is listening on REMOTE_PORT. When it detects an incoming connection, it generates an instance of ChatHandler to mediate in connection with that connection. Now who is this connection? On the user's local machine, we run an instance of ChatDaemon. This guy is listening to LOCAL_PORT. When you connect to it like this
import socket s = socket.socket() s.connect(('localhost',LOCAL_PORT))
it detects a connection and spawns two things: LocalListener and Connection. The connection connects to the server, answering our question from above. LocalListener just waits for user input. If you send data
s.send("Hello, world!")
LocalListener selects it and passes it to Connection, which then sends it to ChatServer. The server then places the data in each ChatHandler buffer to send to all connected clients. When Connection receives this data, it passes it to the daemon, which prints it on the screen.
(The Daemon level seems too complicated, but without it you need to do other complicated things to prevent hot loops in the asyncore select () loop, while maintaining a delay for the user who sends the data low. I donβt want to go that route.)
The problem is that a connection to Daemon is not created . My specific steps
In one python session
d = ChatDaemon('localhost') d.start()
When I do this, I see the message "Linking chat with" localhost: 7668 "as expected.
In another python session
import socket s = socket.socket() s.connect(('localhost',7668))
When I do this, I do not see the printed line "Received a new local connection".
I edited my etc / hosts file to match "localhost" with 127.0.0.1, and I installed the Microsoft Loopback adapter.
EDIT: I found and fixed the problem. Now the code below may be acceptable as a very simple chat implementation using asyncore.
Here is the source
import asyncore import socket import sys LOCAL_HOST = 'localhost' LOCAL_PORT = 7668 REMOTE_HOST = 'localhost' REMOTE_PORT = 7667 class LocalListener(asyncore.dispatcher): """Receive data from user, putting into cxn buffer""" def __init__(self, sock, cxn): self.cxn = cxn asyncore.dispatcher.__init__(self, sock) def writable(self): return False def readable(self): return True def handle_read(self): data = self.recv(4096) if data: self.cxn.buf = self.cxn.buf + data class Connection(asyncore.dispatcher): """Mediates between user and server""" def __init__(self, host, port, master): asyncore.dispatcher.__init__(self) self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.connect((host,port)) self.buf="" def writable(self): return len(self.buf) > 0 def readable(self): return True def handle_read(self): data = self.recv(4096) if data: self.master.newMessage(data) def handle_write(self): sent = self.send(self.buf) self.buf = self.buf[sent:] class ChatDaemon(asyncore.dispatcher): """Listen for local connections and dispatch in/out data""" ADDRESS_FAMILY = socket.AF_INET SOCKET_TYPE = socket.SOCK_STREAM def __init__(self, remoteHost, remotePort=REMOTE_PORT, localHost=LOCAL_HOST, localPort=LOCAL_PORT): self.remoteHost = remoteHost self.remotePort = remotePort self.localHost = localHost self.localPort = localPort self.buffer = "" asyncore.dispatcher.__init__(self) def writable(self): return False def readable(self): return True def newMessage(self, data): print data def start(self): """Listen for user connection on local port""" self.create_socket(self.ADDRESS_FAMILY, self.SOCKET_TYPE) print("Chat deamon binding to '%s': %s"%(self.localHost,self.localPort)) self.bind((self.localHost,self.localPort)) self.listen(1) asyncore.loop() def handle_accept(self): """Spawn local reader and remote reader/writer""" print "Got new local connection" (connSock, localAddress) = self.accept() print("New connection address is %s"%localAddress)