蟒蛇 2.6 视窗 7
我正在尝试编写一个尽可能简单的关于如何编写协作多任务程序的教程。作为一个示例应用程序,我使用 python 的 asyncore 后端编写了一个聊天服务器。我认为这将是社区的宝贵资源。但是,我还没有让它工作,因此这篇文章。
结构如下。ChatServer 的一个实例在远程计算机上运行。它的套接字在 REMOTE_PORT 上侦听。当它检测到一个传入连接时,它会生成一个 ChatHandler 实例来协调与该连接的通信。现在,这个联系是谁?在用户的本地机器上,我们运行 ChatDaemon 的一个实例。这家伙在 LOCAL_PORT 上收听。当你像这样连接到他
import socket
s = socket.socket()
s.connect(('localhost',LOCAL_PORT))
他检测到连接并产生两个东西,一个 LocalListener 和一个 Connection。Connection 连接到服务器,从上面回答我们的问题。LocalListener 只是等待来自用户的数据。如果您发送数据
s.send("Hello, world!")
LocalListener 将其拾取,并将其提供给 Connection,然后将其发送到 ChatServer。然后服务器将数据放入每个 ChatHandler 的缓冲区中,以发送给所有连接的客户端。当 Connection 接收到该数据时,它会将其传递给守护程序,后者将其打印到屏幕上。
(守护程序层似乎过于复杂,但如果没有它,您必须做其他复杂的事情来防止 asyncore 的 select() 循环中的热循环,同时保持用户发送数据的低延迟。我不想走那条路。)
问题是与守护程序的连接似乎没有建立。我的确切步骤是
在一个 python 会话中
d = ChatDaemon('localhost')
d.start()
当我这样做时,我看到消息“聊天守护程序绑定到 'localhost:7668' 符合预期。
在另一个 python 会话中
import socket
s = socket.socket()
s.connect(('localhost',7668))
当我这样做时,我没有看到打印的“得到新的本地连接”行。
我编辑了我的 etc/hosts 文件以将“localhost”映射到 127.0.0.1,并安装了 Microsoft Loopback 适配器。
编辑:我发现并解决了这个问题。下面的代码现在应该可以作为使用 asyncore 的非常简单的聊天实现来接受了。
这里是源
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's 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)
#Make a server connection
cxn = Connection(self.remoteHost, self.remotePort, self)
#Connect to local user
LocalListener(connSock, cxn)
### SERVER ###
class ChatHandler(asyncore.dispatcher):
def __init__(self, sock, map, server):
self.server = server
self.buffer = ''
asyncore.dispatcher.__init__(self, sock, map)
def writable(self):
return len(self.buffer) > 0
def readable(self):
return True
def handle_read(self):
"""Notify server of any new incoming data"""
data = self.recv(4096)
if data:
self.server.newMessage(data, self)
def handle_write(self):
"""send some amount of buffer"""
sent = self.send(self.buffer)
self.buffer = self.buffer[sent:]
class ChatServer(asyncore.dispatcher):
"""Receive and forward chat messages
When a new connection is made we spawn a dispatcher for that
connection.
"""
ADDRESS_FAMILY = socket.AF_INET
SOCKET_TYPE = socket.SOCK_STREAM
def __init__(self, host=REMOTE_HOST, port=REMOTE_PORT):
self.map = {}
self.address = (host,port)
self.clients = []
asyncore.dispatcher.__init__(self, map=self.map)
def serve(self):
"""Bind to socket and start asynchronous loop"""
self.create_socket(self.ADDRESS_FAMILY, self.SOCKET_TYPE)
self.bind(self.address)
self.listen(1)
asyncore.loop(map=self.map)
def writable(self):
return False
def readable(self):
return True
def newMessage(self, data, fromWho):
"""Put data in all clients' buffers"""
for client in self.clients:
client.buf = client.buf + data
def handle_accept(self):
"""Deal with newly accepted connection"""
print 'got new connection'
(connSock, clientAddress) = self.accept()
self.clients.append(ChatHandler(connSock, self.map, self))