tornado长轮询


1.什么是长轮询
顾名思义,长轮询就是不停循环请求服务器,获取最新信息。
长轮询分为两类:
1)浏览器以固定时间间隔向服务器发送请求
缺点是轮询频率要足够快,但又不能太频繁,否则当成百上千个客户端不断请求,会使web服务器面临极大压力
2)服务器推送
浏览器和服务器之间保持请求的连接,当服务器数据更新时,向浏览器响应新数据,然后关闭连接,浏览器接收到响应,重新发送请求,服务器保持请求状态,如此循环。
优点是极大减少了web服务器的负载,即时响应,用户体验佳。相对于方法1,客户端制造大量的短而频繁的请求(以及每次处理http头部产生的开销),服务器只有当其接受一个初始请求和再次发送响应时处理连接,大部分时间没有新的数据,连接也不会消耗任何处理器资源。

2.长轮询使用示例
以下示例中,用户可添加P商品(数量为10个)到购物车,当用户添加商品到购物车,或删除购物车的时候,其他用户可以适时看到P商品数量的变化。

1)用户访问主页
显示库存数量,添加/删除购物车操作

class DetailHandler(tornado.web.RequestHandler):
    def get(self):
        self.post()
    def post(self):
        #商品条码
        session=uuid.uuid1()
        #查询现有库存
        count=self.application.shoppingCart.getInventoryCount()
        #显示
        self.render("index.html",session=session,count=count)

界面如下:

2)主页长轮询商品当前库存

class StatusHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    def get(self):
        #注册添加/删除购物车后的回调函数
        self.application.shoppingCart.register(self.on_message)
    def on_message(self,count):
        print str(count)
        self.write('{"inventorycount":"%s"}'%count)
        self.finish()

@tornado.web.asynchronous装饰器表示请求为异步IO类型,服务端没有主动调用finish()方法时,请求连接会一直保持。

register方法注册了在用户添加/删除购物车后,需要调用的回调函数
在用户添加/删除购物车后,服务器会调用所有连接中的请求注册的回调函数,将库存数量响应给所有连接请求,然后关闭每个连接,请求结束。
此时,客户端会循环发起请求,建立连接,当库存变化时,服务器推送新的数据到客户端,结束连接。如此循环

3)添加/删除购物车操作

class CartHandler(tornado.web.RequestHandler):
    def get(self):
        self.post()
    def post(self):
        action=self.get_argument('action')
        session=self.get_argument('session')
        if not session:
            self.set_status(400)
            return
        if action=='add':
            #添加到购物车
            self.application.shoppingCart.moveItemToCart(session)
        elif action=='remove':
            #删除购物车
            self.application.shoppingCart.removeItemFromCart(session)
        else:
            self.set_status(400)

4)具体看一看添加和删除操作系统处理流程

class ShoppingCart(object):
    totalInventory=10
    callbacks=[]
    carts={}

    def register(self,callback):
        self.callbacks.append(callback)

    def moveItemToCart(self,session):
        if session in self.carts:
            return
        self.carts[session]=True
        self.notifyCallbacks()

    def removeItemFromCart(self,session):
        if session not in self.carts:
            return
        del(self.carts[session])
        self.notifyCallbacks()

    def notifyCallbacks(self):
        for c in self.callbacks:
            print "**********"
            self.callbackHelper(c)
        self.callbacks=[]

    def callbackHelper(self,callback):
        callback(self.getInventoryCount())

    def getInventoryCount(self):
        return self.totalInventory-len(self.carts)

moveItemToCart方法,添加操作时,将商品唯一标识码放入json串,然后将新库存作为参数,调用所有请求的回调,响应各个请求,并关闭连接。

removeItemFromCart方法,删除操作时,将商品唯一标识码清除,同样调用各回调,通知客户端。

5)客户端长轮询代码如下

$(document).ready(function() {
    document.session = $('#session').val();

    setTimeout(requestInventory, 100);

    $('#add-button').click(function(event) {
        jQuery.ajax({
            url: 'http://localhost:9999/cart',
            type: 'POST',
            data: {
                session: document.session,
                action: 'add'
            },
            dataType: 'json',
            beforeSend: function(xhr, settings) {
                $(event.target).attr('disabled', 'disabled');
            },
            success: function(data, status, xhr) {
                $('#add-to-cart').hide();
                $('#remove-from-cart').show();
                $(event.target).removeAttr('disabled');
            }
        });
    });

    $('#remove-button').click(function(event) {
        jQuery.ajax({
            url: 'http://localhost:9999/cart',
            type: 'POST',
            data: {
                session: document.session,
                action: 'remove'
            },
            dataType: 'json',
            beforeSend: function(xhr, settings) {
                $(event.target).attr('disabled', 'disabled');
            },
            success: function(data, status, xhr) {
                $('#remove-from-cart').hide();
                $('#add-to-cart').show();
                $(event.target).removeAttr('disabled');
            }
        });
    });
});

function requestInventory() {
    jQuery.getJSON('http://localhost/status', {session: document.session},
        function(data, status, xhr) {
            $('#count').html(data.inventorycount);
            setTimeout(requestInventory, 0);
        }
    );
}

6)运行结果
打开多个客户端,当做添加/删除操作时,可以观察到库存数量会实时变动。

3.长轮询的缺陷
上面有提到过长轮询的优势,经过上面的示例,我们可以明白长轮询存在的一些缺陷。
1)所有客户端请求同时关闭,同时打开,在库存变化的时候,服务器会受到猛烈的攻击
2)长轮询保持了连接请求,很多浏览器限制了对于服务器的并发请求数量,所以一直占用连接,会导致其他的请求如下载受到限制。
3)浏览器请求超时是由浏览器控制的。

 参考资料:http://docs.pythontab.com/tornado/introduction-to-tornado/ch5.html

原文地址:https://www.cnblogs.com/shijingjing07/p/6558217.html