django and comet

by Yan Sheng

一些介绍

原始需求, 想通过某个手段动态更新当前页面上的某些数据, 不通过刷新. 很容易想到的一种实现方式就是ajax不断请求服务器上数据, 若有更新则更改当前页面数据. 但这种方式比较笨. 于是乎, 想到之前听过comet, 想是否可以在这里实现服务器推送数据给客户端.

Orbited

google上乱搜了一通django和comet,找到这个东西, 按其网站上说, 它提供了浏览器的即时通信, 支持多种协议, 如IRC, XMPP, STOMP(ActiveMQ, RabbitMQ). 那简单来说, 它提供了浏览器端的js/html socket, 用于与服务器交互. 而在后台也提供对应的服务. 整个结构可以如下图所示:

这是其中的一种实现方式(Orbited+MorbidQ+STOMP). 简单介绍下的话:

  • 浏览器通过订阅(subscribe)相关url, 而orbited服务器会记录客户端连接情况, 如果有新数据到达时会推送给相应客户端.
  • 而这些新的数据来自web应用框架, 其通过某中方式发送给orbited服务器.

具体使用

那具体来说, 使用django+orbited+morbidQ, 大概介绍下的话:

  • Orbited服务器起到的作用是 记录来自客户端到服务器的请求信息.但不是所有客户端的请求路径都保存,只保存订阅页面的路径开头的请求的那些客户端. Orbited记录这些信息之后,才能再收到到django框架发过来的更新数据之后发送给对应的客户端. 发送更新数据给客户端之后,页面上的js代码接收数据并显示出来.
  • 那orbited的数据来源是从哪里来的呢? 当然是希望直接从web框架过来,但是django没有这方面的功能.至少我不知道.所以中间用了一个代理.也就是relay.py. 他也相当于一个服务器,具体实现成一个RPC,也就是远程过程调用服务, 这里的作用是接收django中发送过来的数据,进行编码后发送给orbited. 这就是web框架通过relay与orbited进行单向通信.
  • 当然,这里利用的是orbited的MorbidQ,基于stomp协议, 还有基于其他协议的,比如xmpp等(ps, gmail是, 哲思也是用xmpp, 估计这个比较好.)

orbited

依次安装, python2.5+, twisted, orbited, stompservice, simplejson, ok之后, orbited有个配置文件, 叫做orbited.cfg,

shengyan@shengyan-desktop:~$ cat /etc/orbited.cfg
# Example Orbited Configuration file

[global]
reactor=select
# reactor=kqueue
# reactor=epoll
#session.ping_interval = 40
session.ping_interval = 300     # 服务器ping客户端时间间隔, 300秒
session.ping_timeout = 30
# once the sockets are open, orbited will drop its privileges to this user.
user=orbited

[listen]
#http://:8000
http://:9000           # 监听http协议的9000端口
stomp://:61613         # 监听stomp协议的61613端口
# uncomment to enable SSL on port 8043 using given .key and .crt files
#https://:8043
#
#[ssl]
#key=orbited.key
#crt=orbited.crt

[static]
prdt=product.htm     # 相当于别名. 无关痛痒的个设置

[access]
#localhost:8000 -> irc.freenode.net:6667
#* -> localhost:4747
* -> localhost:61613    # 访问设置.

[logging]
debug=STDERR,debug.log
info=STDERR,info.log
access=STDERR,info.log
warn=STDERR,error.log
error=STDERR,error.log

#Don't enable debug by default
enabled.default=info,access,warn,error

# Turn debug on for the "Proxy" logger
[loggers]
#Proxy=debug,info,access,warn,error

ok之后, 启动 $ orbited -c /etc/orbited.cfg #服务器启动之后看到的输出信息就是不同客户端链接和断开情况,

relay

ralay是一个网上牛人写的RPCServer, 用户将django传送过来的数据编码, 后转发给orbited.

#!/usr/bin/python
# relay.py
# http://cometdaily.com/2008/10/10/scalable-real-time-web-architecture-part-2-a-live-graph-with-orbited-morbidq-and-jsio/ & http://anirudhsanjeev.org/tutorialhow-to-django-comet-orbited-stomp-morbidq-jsio/

from stompservice import StompClientFactory
from twisted.internet import reactor
from twisted.internet.task import LoopingCall
from random import random
from orbited import json
from SimpleXMLRPCServer import *

from threading import Thread
import stompservice
from settings import RPC_HOSTNAME as HOSTNAME, RPC_PORT as PORT, STOMP_PORT  # 主机名和端口号需要一致

INTERVAL = 1000# in ms

class DataProducer(StompClientFactory):
    def recv_connected(self, msg):

        print 'Connected; producing data'
        self.timer = LoopingCall(self.test_data)
        self.timer.start(INTERVAL/1000.0)

    def send_data(self, channel, data):
        print "Transmitting: %s bytes" % len(data)
        #self.send(channel, data.encode("utf8"))
        self.send(channel, json.encode(data))

    def test_data(self):
        pass

orbited_proxy = DataProducer()

class RPCServer(Thread):
    def __init__(self, orbited):
        self.orbited = orbited
        Thread.__init__(self)
    def run(self):
        class RequestHandler(SimpleXMLRPCRequestHandler):
            rpc_paths = ('/RPC2',)
        #create a server
        server = SimpleXMLRPCServer((HOSTNAME,PORT),
                                    requestHandler = RequestHandler)

        server.register_introspection_functions()
        def transmit_orbited(channel, message):
            """
            @param channel: The stomp channel to send to
            @param message: The message that needs to be transmitted
            """
            self.orbited.send_data(channel, message)
            return ""

        server.register_function(transmit_orbited, 'transmit')
        server.serve_forever()

if __name__ == "__main__":
        rpcthread = RPCServer(orbited_proxy)
        rpcthread.start()

        reactor.connectTCP(HOSTNAME, STOMP_PORT, orbited_proxy)
        reactor.run()


#python relay.py 启动它.

django

django中的view中, 如果某用户提交了数据, 更新数据库,或者其他的, 都可触发更新, 将更新后的数据发送给relay. 这里直接封装数据, 并使用xmlrpclib中的ServerProxy发送.

def _prdt(dnid='--',):
    """
    handle an XMLHttpRequest
    """
    import xmlrpclib
    proxy = xmlrpclib.ServerProxy("http://%s:%s" % (settings.RPC_HOSTNAME, settings.RPC_PORT))
    msg = {}
    msg['dnid'] = dnid
    #...
    proxy.transmit("/topic/prdt", msg)  # 这个路径和js中订阅的路径一致

js

页面中订阅及获取数据

<script src="/site_media/js/jquery-1.2.6.js" type="text/javascript"></script>
<script src="/static/Orbited.js" type="text/javascript"></script>
<script src="/static/protocols/stomp/stomp.js" type="text/javascript"></script>
<script type="text/javascript">
    document.domain = document.domain;
    OrbitedHost = "localhost";  // 主机名
    Orbited.settings.port = 9000;  // Orbited端口
    Orbited.settings.hostname = OrbitedHost;
    TCPSocket = Orbited.TCPSocket;
    StompPort = 61613;    // STOMP端口, 这两个端口设置是和orbited配置中一致
</script>

ok, 启动django服务器. 打开俩个相同页面的话, 在一个页面中更新数据, 另一个页面也会显示出来...

JSON数据读取

js中读取JSON数据,

最后

前两天看到有人提到webqq, 好奇用了下, 的确做得很棒. 一句感叹,,,越来越多的这种类似的应用~~~

PythonJavascript