Web实时消息推送技术总结

通过轮询、长轮询、iframe流和WebSocket等多种方式实现网页消息的实时推送功能。

前言

  消息推送(Push),是指从服务端实时发送信息到客户端,最早诞生于 Email 中,用于提醒新的消息,想必大家都不陌生。
  随着互联网技术的发展,很多网站和移动端应用开始追求用户体验,所以消息推送技术得到了更广泛的应用,常见的有:新闻客户端的热点新闻推荐,IM 工具的聊天消息提醒,电商产品促销信息,企业应用的通知和审批流程等等。
  通过一些学习和了解,下面我主要总结下几种常用的网页消息推送技术。
  针对这几个技术实现,我写过几个很简单的Demo,见 https://github.com/winyuan/py_websocket.git

一、双向通信

  HTTP 协议有一个特点:被动性。
  何为被动性呢,其实就是,服务端不能主动联系客户端,只能由客户端发起。
  举例来说,我们想获得某个数据,就得是客户端(如浏览器)向服务器发出请求,服务器返回查询结果。HTTP 协议做不到服务器主动向客户端推送信息,比如收到新邮件的提示。这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。
  在WebSocket协议之前,有三种实现双向通信的方式:轮询(polling)、长轮询(long-polling)和iframe流(streaming)。

1. 轮询(polling)

  轮询的原理非常简单,让浏览器隔个几秒就发送一次请求,询问服务器是否有新信息。
  用大白话举例就是:
  while True:
    客户端:妹子,请你吃饭有空吗?(Request)
    服务端:没有!(Response)
    客户端:妹子,请你吃饭有空吗?(Request)
    服务端:没有。。(Response)
    客户端:妹子,请你吃饭有空吗?(Request)
    服务端:你好烦啊,没有啊。。(Response)
    客户端:妹子,请你吃饭有空吗?(Request)
    服务端:好啦好啦,有啦。(Response)
    客户端:妹子,请你吃饭有空吗?(Request)
    服务端:。。。。。没。。。。没。。。没有(Response)
polling.jpg
  可以看到,使用轮询的方式,客户端和服务器之间会一直进行连接,每隔一段时间就询问一次。其缺点也很明显:连接数会很多,一个接受,一个发送。而且每次发送请求都会有Http的Header,会很耗流量,也会消耗CPU的利用率。

  • 优点:实现简单,无需做过多的更改
  • 缺点:轮询的间隔过长,会导致用户不能及时接收到更新的数据;轮询的间隔过短,会导致查询请求过多,增加服务器端的负担

2. 长轮询(long-polling)

  长轮询其实原理跟轮询差不多,都是采用轮询的方式,不过采取的是阻塞模型(一直打电话,没收到就不挂电话),也就是说,客户端发起连接后,如果没消息,就一直不返回Response给客户端。直到有消息才返回,返回完之后,客户端再次建立连接,周而复始。
  还是用大白话举例:
  while True:
    客户端:妹子,请你吃饭有空吗?没有的话就等有了再返回给我吧!(Request)
    服务端:(额。。现在好忙,先不回复他,电话先不挂。)
    服务端:现在有空了。(Response)
    客户端:妹子,请你吃饭有空吗?没有的话就等有了再返回给我吧!(Request)
    服务端:(额。。现在好忙,先不回复他,电话先不挂。)
    服务端:现在有空了。(Response)
long_polling.jpg
  本质上,长轮询是对轮询的改进版,客户端发送HTTP给服务器之后,看有没有新消息,如果没有新消息,就一直等待。当有新消息的时候,才会返回给客户端。在某种程度上减小了网络带宽和CPU利用率等问题。由于http数据包的头部数据量往往很大(通常有400多个字节),但是真正被服务器需要的数据却很少(有时只有10个字节左右),这样的数据包在网络上周期性的传输,难免对网络带宽是一种浪费。

  • 优点:比 polling 做了优化,有较好的时效性
  • 缺点:保持连接会消耗资源;服务器没有返回有效数据,程序超时。

3. iframe流(streaming)

  iframe流的服务器开销很大,而且IE、Chrome等浏览器一直会处于loading状态。这里先大致看下实现原理。
iframe_streaming.jpg
  iframe流方式是在页面中插入一个隐藏的iframe,利用其src属性在服务器和客户端之间创建一条长连接,服务器向iframe传输数据(通常是HTML,内有负责插入信息的javascript),来实时更新页面。

  • 优点:消息能够实时到达;浏览器兼容好
  • 缺点:服务器维护一个长连接会增加开销;IE、chrome、Firefox会显示加载没有完成,图标会不停旋转。

  从上面很容易看出来,不管怎么样,这几种都是非常消耗资源的。
  轮询(polling)需要服务器有很快的处理速度和资源。
  长轮询(long-polling)需要有很高的并发。
  所以它们都有可能发生这种情况:
  while True:
    客户端:妹子,请你吃饭有空吗?(Request)
    服务端:您好,您所拨打的用户正在通话中,请稍后再拨。(503 Server Unavailable)
    客户端:妹子,请你吃饭有空吗?(Request)
    服务端:您好,您所拨打的用户正在通话中,请稍后再拨。(503 Server Unavailable)
    服务端忙的要死:(我要更多的资源,资源。。。)

二、WebSocket

  通过上面这个例子,我们可以看出,这些都不是最好的方式,需要很多资源。
  轮询需要更快的速度,长轮询需要更多的“手机”,iframe流我没有实际用过,但显然也不好。它们都会导致“手机”的需求越来越高。
  于是,我们再次总结下 HTTP 的两个短板:

  • 被动性
      服务端不能主动联系客户端,只能由客户端发起。
  • 无状态协议
      对于事务处理没有记忆能力。通俗的说就是,服务器因为每天要接待太多客户了,是个健忘鬼,你一挂电话,她就把你的东西全忘光了,把你的东西全丢掉了。你第二次还得再告诉服务器一遍。

  所以在这种情况下出现了,WebSocket出现了。
  他解决了HTTP的这几个难题。
  首先,被动性,当服务器完成协议升级后(HTTP→WebSocket),服务端就可以主动推送信息给客户端啦。所以上面的情景可以做如下修改:
  客户端:我要建立WebSocket协议,需要的服务:chat,WebSocket协议版本:17(HTTP Request)
  服务端:ok,确认,已升级为WebSocket协议(HTTP Protocols Switched)
  客户端:你有事的时候记得要找我噢!
  服务端:ok,有事的时候会找你的。
  服务端:我想吃炸鸡了。
  服务端:我想喝奶茶了。
  服务端:我不开心了。
  服务端:balabalabalabala。。。

  就变成了这样,只需要经过一次HTTP请求,就可以做到源源不断的信息传送了。
  这样的协议解决了上面同步有延迟,而且还非常消耗资源的这种情况。那么为什么他会解决服务器上消耗资源的问题呢?
  下面开始举个啰嗦的例子:
  一般生产环境我们所用的程序是要经过两层代理的,即HTTP协议在Nginx等服务器的解析下,然后再传送给相应的Handler(Python等)来处理。
  简单地说,我们有一个非常快速的接线员(Nginx),他负责把问题转交给相应的客服(Handler)。本身接线员基本上速度是足够的,但是每次都卡在客服(Handler)了,老有客服处理速度太慢,导致客服不够。
  WebSocket就解决了这样一个难题,建立后,可以直接跟接线员建立持久连接,有信息的时候客服想办法通知接线员,然后接线员在统一转交给客户。
  这样就可以解决客服处理速度过慢的问题了。
  同时,在传统的方式上,要不断的建立,关闭HTTP协议,由于HTTP是非状态性的,每次都要重新传输identity info(鉴别信息),来告诉服务端你是谁。
  虽然接线员很快速,但是每次都要听这么一堆,效率也会有所下降的,同时还得不断把这些信息转交给客服,不但浪费客服的处理时间,而且还会在网路传输中消耗过多的流量/时间。
  但是WebSocket只需要一次HTTP握手,所以说整个通讯过程是建立在一次连接/状态中,也就避免了HTTP的非状态性,服务端会一直知道你的信息,直到你关闭请求,这样就解决了接线员要反复解析HTTP协议,还要查看identity info的信息。
  同时由客户主动询问,转换为服务器(推送)有信息的时候就发送(当然客户端还是等主动发送信息过来的。。),没有信息的时候就交给接线员(Nginx),不需要占用本身速度就慢的客服(Handler)了。

1. WebSocket的概念

  啰嗦了一大段话后,重新梳理下WebSocket的概念。
  WebSocket是HTML5出的东西(协议),也就是说HTTP协议没有变化,或者说没关系,但HTTP是不支持持久连接的(长连接,循环连接的不算)。
  首先HTTP有 1.1 和 1.0 之说,也就是所谓的keep-alive,把多个HTTP请求合并为一个,但是WebSocket其实是一个新协议,跟HTTP协议基本没有关系,只是为了兼容现有浏览器的握手规范而已,也就是说它是HTTP协议上的一种补充,可以理解为:WebSocket和HTTP之间有交集,但是并不是全部。
  另外HTML5是指的一系列新的API,或者说新规范,新技术。HTML协议本身只有1.0和1.1,而且跟HTML本身没有直接关系。。通俗来说,你可以用HTTP协议传输非Html数据,再简单来说,层级不一样。
  关于WebSocket更进一步的内容,我会再另外整理,篇幅限制,这里就不多写了。

2. WebSocket的特点

  • 支持双向通信,实时性更强
  • 可以发送文本,也可以发送二进制数据
  • 减少通信量:只要建立起WebSocket连接,就希望一直保持连接状态。和HTTP相比,不但每次连接时的总开销减少,而且由于WebSocket的首部信息很小,通信量也相应减少了
    websocket.jpg
      相对于传统的HTTP每次请求-应答都需要客户端与服务端建立连接的模式,WebSocket是类似Socket的TCP长连接的通讯模式,一旦WebSocket连接建立后,后续数据都以帧序列的形式传输。在客户端断开WebSocket连接或Server端断掉连接前,不需要客户端和服务端重新发起连接请求。
      在海量并发和客户端与服务器交互负载流量大的情况下,极大的节省了网络带宽资源的消耗,有明显的性能优势,且客户端发送和接受消息是在同一个持久连接上发起,实时性优势明显。

三、Web实时消息推送技术的比较

方式 类型 技术实现 优点 缺点 试用场景
轮询(polling) client→server 客户端循环请求 1.实现简单 2.支持跨域 1.浪费带宽和服务器资源 2.一次请求信息大半是无用(完整http头信息) 3.有延迟 4.大部分无效请求 适于小型应用
长轮询(long-polling) client→server 服务器hold住连接,一直到有数据或者超时才返回,减少重复请求次数 1.实现简单 2.不会频繁发请求 3.节省流量 4.延迟低 1.服务器hold住连接,会消耗资源 2.一次请求信息大半是无用 WebQQ、Hi网页版、Facebook IM
iframe流 client→server 在页面里嵌入一个隐蔵iframe,将这个iframe的src属性设为对一个长连接的请求,服务器端就能源源不断地往客户端输入数据。 1.数据实时送达 2.不发无用请求,一次链接,多次“推送” 1.服务器增加开销 2.无法准确知道连接状态 3.IE、Chrome等一直会处于loading状态 Gmail聊天
WebSocket client⇌server new WebSocket() 1.支持双向通信,实时性更强 2.可发送二进制文件 3.减少通信量 1.浏览器支持程度不一致 2.不支持断开重连 网络游戏、银行交互和支付

  综上所述:Websocket协议不仅解决了HTTP协议中服务端的被动性,即通信只能由客户端发起,也解决了数据同步有延迟的问题,同时还带来了明显的性能优势,所以WebSocket是Web实时消息推送技术的比较理想的方案,但如果要兼容低版本浏览器,可以考虑用轮询来实现。

  以上几种技术的代码实现,我写过几个很简单的Demo,见 https://github.com/winyuan/py_websocket.git


  目录