理论知识
尝试解决服务器推送的问题, web都是拉取数据:浏览器发出请求到服务器,服务器会产生并且发送回响应 但是,如果有需要将数据主动从服务器推送到浏览器该怎么办呢?
长轮询long-polling
解决的方法:浏览器发出ajax请求到服务器,要求更新,但是这个常用的浏览器和服务器之间的推送方法,有一个问题: 如果服务器没有什么要发送,它会保持连接打开,直到为用户提供一些数据,客户端收到响应后,会发出另外的一个请求,获得更多的数据 上面的这种技术被称为long-polling,长轮询
显然,这种方法不太高效,大多数情况下,信噪比是非常高的(无用的数据比有用的数据),因为这样更多的时间将花在处理http请求(比如解析和验证报头),而不是实际数据发送到客户端 不幸的是,这种长轮询的方法是目前将数据推送到客户端最适合的方式
基于HTTP/1.1情况好转了一点,TCP连接可以使用 Keep-Alive头,默认情况下, 连接在请求发起后将保持打开状态。 此功能使长轮询延迟得到了降低,这样就没有必要为每个轮询请求重新打开TCP连接 HTTP/1.1还引入了 块传输协议 。 它允许讲响应分解为成更小的数据块, 并将它们立即发送到客户端,而不是一直等到完成HTTP请求 不幸的是,有些不兼容这个功能的代理服务器还是试图在转发之前缓存整个响应,所以客户端将不会收到任何数据,直到代理认为http请求已经完成 虽然看起来,web还是能正常工作-因为客户端最终还是会得到来自服务器的响应,但是它打破了为实时而设计的块传输协议的整体思路
其他方法
2006年9月,opera为它的浏览器实现了试验性的服务事件发送功能,虽然sse和块传输协议很相似,但是还是不同的协议,而且有更好的客户端api 2009年4月23日,SSE得到WHATWG批准,得到几乎所有的现代的桌面浏览器(Internet Explorer的除外)的支持 还有其他的技术,比如 forever-iframe, 这是两种可以为Internet Explorer版本低于8做跨域推送的技术之一(另一个是 jsonp, 以及 HTMLFILE 等 总之,所有这些基于HTTP的折中方案都可以叫做 Comet
方法利弊
long-polling,长轮询是昂贵的,但是兼容性很好 块传输协议效率更高,但有可能不是所有的客户端都能正常工作,并且如果没有某种形式的探测都无法发现问题的存在 sse也不错,但不是所有的浏览器都支持,比较好的是有办法在建立连接前,就知道它是否支持
这些方法都有一个问题,它们都只提供一种方式将数据从服务器推送到客户端,而不是建立双向通信,客户端每次想发送一些数据的时候,将不得不使用ajax请求到服务器,这样会增加延迟,并且服务器也会产生额外的负载
邂逅websockets
虽然websockets不是什么新技术,但是经历了几个不兼容的迭代后该规范终于通过了,rfc编号rfc-6455 简而言之,基于websocket的服务器和客户端之间建立的是基于tcp的双向连接,连接的建立使用兼容http的握手协议,加上额外的websocket相关的头,并具有额外的协议层次划分,所以它不仅仅是一个从浏览器中打开原始的tcp连接
websocket协议的最大问题是:浏览器的支持,防火墙,代理服务器和防病毒应用的支持,企业防火墙和代理服务器通常因各种原因阻止的WebSocket连接。 有些代理服务器不能处理WebSocket在端口80上连接 - 他们认为这是一般的HTTP请求,并尝试缓存它。 有HTTP扫描组件的杀毒软件也不允许WebSocket连接 无论如何,websocket来建立的客户端和服务器之间的双向通信是最好的方式,但是不能单一的用来解决推送问题
用例
如果您的应用程序大多是从服务器推送数据,基于HTTP的传输会工作得很好。 但是,如果浏览器支持WebSocket的传输并且WebSocket的连接是可以建立的,它将是更好的选择 总而言之, 最好的办法是:尝试打开的WebSocket连接, 如果失败 - 尝试回退到基于HTTP传输。 当然也有可以”升级”连接 - 首先使用长轮询(long-polling), 然后尝试建立WebSocket的连接。 如果成功,就切换到WebSocket的连接。 虽然这种做法可能会降低初始连接的时间, 需要注意服务器端实现, 以避免但在两者连接之间切换时发生任何的跳变情况(race conditions)
polyfill库
为所有已知的浏览器提供变通方案,搞定代理和防火墙的问题,尤其是从头开始处理这些问题,是非常困难的,幸运的是,已经有人提出尽可能稳定的解决方案 有一些 polyfill库,像sockjs 库 , socket.io 库 , faye和其他一些框架,实现了基于各种不同的传输实现上的类WebSocket的 API 虽然他们所提供的服务器和客户端API不尽相同,但他们有着共同的理念: 在给定的情况下用最好的传输方案,并且提供一致的服务器端API 例如,如果浏览器支持WebSocket协议,polyfill将尝试建立WebSocket连接。 如果失败了,他们将下降回到下一个最好的传输协议。 Engine.IO使用稍微不同的方法 - 他先建立长轮询连接(long-polling),并尝试在后台升级到WebSocket 在任何情况下 - 这些库将尝试建立双向连接到服务器上使用最可靠的传输。 不幸的是,在使用Socket.IO 0.8.x的时候有较差的体验。 我一般在我自己的项目中使用 sockjs-tornado, 即使我自己写了 TornadIO2。 Socket.IO早期的server实现是基于 Tornado]的
服务器端
基于wsgi的服务器不能被用于创建实时应用, 因为wsgi协议是同步的,wsgi服务器一次只能处理一个请求
回顾长轮询long-polling传输
客户端打开http连接到服务器,获得更多的数据
无可用的数据,服务器保持连接打开并等待数据发送
因为服务器无法处理任何其他请求,一切都将被阻塞
伪代码表示:
def handle_request(request):
data = get_more_data(request)
return send_response(data)
如果get_more_data阻塞了,那整个服务器就会被阻塞,不能处理请求了
当然,可以每个请求创建线程,但这非常低效。
网友评论