Python 2.6 Windows 7
協調的なマルチタスク プログラムの書き方について、できるだけ簡単なチュートリアルをまとめようとしています。サンプル アプリケーションとして、Python の非同期コア バックエンドを使用してチャット サーバーを作成しました。これはコミュニティにとって貴重なリソースになると思います。ただし、まだ機能していないため、この投稿を行います。
構造は次のとおりです。ChatServer のインスタンスは、リモート コンピューターで実行されます。ソケットは REMOTE_PORT をリッスンします。着信接続を検出すると、ChatHandler のインスタンスを生成して、その接続との通信を仲介します。さて、そのつながりは誰ですか?ユーザーのローカル マシンで、ChatDaemon のインスタンスを実行します。この男は LOCAL_PORT でリッスンします。このように彼に接続すると
import socket
s = socket.socket()
s.connect(('localhost',LOCAL_PORT))
彼は接続を検出し、LocalListener と Connection の 2 つを生成します。Connection はサーバーに接続し、上記の質問に答えます。LocalListener は、ユーザーからデータが来るのを待つだけです。データを送る場合
s.send("Hello, world!")
LocalListener がそれをピックアップして Connection に渡し、Connection がそれを ChatServer に送信します。次に、サーバーはデータを各 ChatHandler のバッファーに入れ、接続されているすべてのクライアントに送信します。Connection がそのデータを受け取ると、それを Daemon に渡します。Daemon はそれを画面に出力します。
(デーモン層は非常に複雑に見えますが、それがなければ、ユーザーがデータを送信するためのレイテンシーを低く保ちながら、asyncore の select() ループでのホット ループを防ぐために、他の複雑なことを行う必要があります。私はその道をたどりたくありません。)
問題は、デーモンへの接続が確立されていないように見えることです。私の正確な手順は
1 つの 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))