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