ページ

2010年12月22日

WebSocketのサーバ間でメッセージをリレーさせるサンプル

どうもです。なぜかPythonのアドベントカレンダーに参加することになっていましたおおたにです。お題が、PythonのWebフレームワークです。えっ、Webフレームワーク?僕はそんなにWebフレームワーク詳しくないし・・・。数年前ならまだしも、最近は便利に使えれば後はそれほど気にしなくなっていました。でも、Webフレームワーク・・・。ということで、WebSocketとFlaskを絡めてサンプルコードを書いたら、どこにもimport flaskってやっていない罠が・・・。ということで、前置き(言い訳)が長くなりました、WebSocketのサーバを複数立てたときに、サーバ間のデータのやりとりをどうするのかというお話です。

まず、WebSocketはHTML5関連で脚光を浴びている機能なので、知っている人も多いでしょう。簡単にいってしまえば、最初にHTTPプロトコルを偽装して、その後データのだだ漏れができるものです。Chromeでは去年サポートされ、Safariも今の最新版でサポートされています。iOS4.2.1でもサポートされているのでiPhoneからでも使えます。IE9では、「何それ?」の悲しい状態ですが、プロトコルの不安定さから仕方ないですね。Firefoxは4からサポートすると期待させておきながら、こんな残念な記事が今朝聞こえてきました。

WebSocketでデータの送受信をすると、Cometなどのロングポーリングよりはインフラに優しい作りになります。でも、ロングポーリングでも同じですが、アプリケーションサーバを複数立てたとき、アプリケーションサーバ間でデータをやりとりする必要がでてきます。リアルタイム性が大きなメリットなので、データベースとかは介せません。そこで有力なのがXMPPだと信じていますが、Google Waveが消えたのと、ブログの一つのエントリじゃ収まらないので、XMPPは忘れます。自前で作るとしたらハブサーバを作って、アプリケーションサーバとハブサーバの間でデータの垂れ流し(リレー)をさせるのが良さそうです。そのときのプロトコルもWebSocketで中身はJSONとか、MessagePackとかもいいかな、と思ったのですが、サンプルコードはライブラリを調べる時間も含めて30分以内に作らないといけないというローカルルールにより、行ベースで自分でソケットを開きます。



で、今回のサンプルは全部geventベースで書いています。非同期処理が同期処理のように書けるのがすごいメリットです。WSGIサーバにもなるので、FlaskでもDjangoでも使えます。で、なぜ、WebSocketを非同期にこだわるかはむか〜し、どこかで書いた気がするので省略。でも、今回のサンプルはある程度ブロックするので効率は悪いです。サンプルコードはここにあります。

まず、ハブサーバのコードです。

from gevent.server import StreamServer

class BCServer:
    def __init__(self):
        self.clients = []

    def received(self, socket, addr):
        fileobj = socket.makefile()
        self.clients.append(fileobj)
        try:
            while True:
                line = fileobj.readline()
                if not line:
                    break
                if line.startswith("quit:"):
                    break
                if line.startswith("msg:"):
                    msg = line.split(":", 1)[1]
                    for fo in self.clients:
                        try:
                            fo.write(msg)
                            fo.flush()
                        except Exception, e:
                            print p
        except Exception, e:
            print e
        finally:
            self.clients.remove(fileobj)


if __name__ == "__main__":
    bcs = BCServer()
    server = StreamServer(("0.0.0.0", 3000), bcs.received)
    server.serve_forever()



geventのsocketが標準ライブラリのsocketと全く同じようにあつかえます。メッセージを垂れ流しているだけなので、単純です。Webアプリケーションの方は、以前のコードとほとんど同じなので、ポイントだけ。全部読みたければ、これ

chat_clients = set()

def read_server(fileobj):
    while True:
        data = fileobj.readline()
        if not data:
            print "end read_server"
            break
        for client in chat_clients:
            client.send(data)

def handle_chat(ws):
    chat_clients.add(ws)
    while True:
        message = ws.wait()
        if message is None:
            break
        fileobj.write("msg:" + message + "\n")
        fileobj.flush()

    chat_clients.remove(ws)


fileobjはハブサーバに接続した後にsocket.makefileしたものです。これだけでメッセージをリレーしてくれます。えっ?信じられないって?それじゃ、動いている画像です。

2 件のコメント:

Takao さんのコメント...

> そこで有力なのがXMPPだと信じていますが、

ぜひAMQPで作ってください。

liris さんのコメント...

うっ、AMQPは忘れていました。