tornado长轮询

时间:2021-12-13 21:50:52

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)

界面如下:
tornado长轮询

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