4

网页端IM通信技术快速入门:短轮询、长轮询、SSE、WebSocket

 3 years ago
source link: http://www.blogjava.net/jb2011/archive/2021/05/25/435881.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

本文来自“糊糊糊糊糊了”的分享,原题《实时消息推送整理》,有优化和改动。

1、写在前面

对Web端即时通讯技术熟悉的开发者来说,我们回顾网页端IM的底层通信技术,从短轮询、长轮询,到后来的SSE以及WebSocket,使用门槛越来越低(早期的长轮询Comet这类技术实际属于hack手段,使用门槛并不低),技术手段越来越先进,网页端即时通讯技术的体验也因此越来越好。

但上周在编辑《IM扫码登录技术专题》系列文章第3篇的时候忽然想到,之前的这些所谓的网页端即时通讯“老技术”相对于当红的WebSocket,并非毫无用武之地。就拿IM里的扫码登录功能来说,用短轮询技术就非常合适,完全没必要大炮打蚊子上WebSocket。

所以,很多时候没必要盲目追求新技术,相对应用场景来说适合的才是最好的。对于即时通讯网的im和消息推送这类即时通讯技术开发者来说,掌握WebSocket固然很重要,但了解短轮询、长轮询等这些所谓的Web端即时通讯“老技术”仍然大有裨益,这也正是整理分享本文的重要原因。

(本文同步发布于:http://www.52im.net/thread-3555-1-1.html

2、推荐阅读

[1] 新手入门贴:史上最全Web端即时通讯技术原理详解

[2] 详解Web端通信方式的演进:从Ajax、JSONP 到 SSE、Websocket

[3] Web端即时通讯技术盘点:短轮询、Comet、Websocket、SSE

3、正文引言

对于IM/消息推送这类即时通讯系统而言,系统的关键就是“实时通信”能力。

从表面意思上来看,“实时通信”指的是:

  • 1)客户端能随时主动发送数据给服务端;
  • 2)当客户端关注的内容在发生改变时,服务器能够实时地通知客户端。

类比于传统的C/S请求模型,“实时通信”时客户端不需要主观地发送请求去获取自己关心的内容,而是由服务器端进行“推送”。

注意:上面的“推送”二字打了引号,实际上现有的几种技术实现方式中,并不是服务器端真正主动地推送,而是通过一定的手段营造了一种“实时通信”的假象。

就目前现有的几种技术而言,主要有以下几类:

  • 1)客户端轮询:传统意义上的短轮询(Short Polling);
  • 2)服务器端轮询:长轮询(Long Polling);
  • 3)单向服务器推送:Server-Sent Events(SSE);
  • 4)全双工通信:WebSocket。

以下正文将针对这几种技术方案,为你一一解惑。

4、本文配套Demo和代码

为了帮助读者更好的理解本文内容,笔者专门写了一个较完整的Demo,Demo会以一个简易聊天室的例子来分别通过上述的四种技术方式实现(代码存在些许bug,主要是为了做演示用,别介意)。

完整Demo源码打包下载:

(请从同步链接附件中下载:http://www.52im.net/thread-3555-1-1.html

Demo的运行效果(动图):

有兴趣可以自行下载研究学习。

5、理解短轮询(Short Polling)

短轮询的实现原理:

  • 1)客户端向服务器端发送一个请求,服务器返回数据,然后客户端根据服务器端返回的数据进行处理;
  • 2)客户端继续向服务器端发送请求,继续重复以上的步骤,如果不想给服务器端太大的压力,一般情况下会设置一个请求的时间间隔。

逻辑如下图所示:

使用短轮询的优点:基础不需要额外的开发成本,请求数据,解析数据,作出响应,仅此而已,然后不断重复。

缺点也显而易见:

  • 1)不断的发送和关闭请求,对服务器的压力会比较大,因为本身开启Http连接就是一件比较耗资源的事情;
  • 2)轮询的时间间隔不好控制。如果要求的实时性比较高,显然使用短轮询会有明显的短板,如果设置interval的间隔过长,会导致消息延迟,而如果太短,会对服务器产生压力。

短轮询客户的代码实现(片段节选):

var ShortPollingNotification = {

  datasInterval: null,

  subscribe: function() {

    this.datasInterval = setInterval(function() {

      Request.getDatas().then(function(res) {

        window.ChatroomDOM.renderData(res);

    }, TIMEOUT);

    return this.unsubscribe;

  unsubscribe: function() {

    this.datasInterval && clearInterval(this.datasInterval);

PS:完整代码,请见本文“4、本文配套Demo和代码”一节。

对应本文配套Demo的运行效果如下(动图):

下面是对应的请求,注意左下角的请求数量一直在变化:

在上图中,每隔1s就会发送一个请求,看起来效果还不错,但是如果将timeout的值设置成5s,效果将大打折扣。如下图所示。

将timeout值设置成5s时的Demo运行效果(动图):

6、理解长轮询(Long Polling)

6.1 基本原理

长轮询的基本原理:

  • 1)客户端发送一个请求,服务器会hold住这个请求;
  • 2)直到监听的内容有改变,才会返回数据,断开连接(或者在一定的时间内,请求还得不到返回,就会因为超时自动断开连接);
  • 3)客户端继续发送请求,重复以上步骤。

逻辑如下图所示:

长轮询是基于短轮询上的改进版本:主要是减少了客户端发起Http连接的开销,改成了在服务器端主动地去判断所关心的内容是否变化。

所以其实轮询的本质并没有多大变化,变化的点在于:

  • 1)对于内容变化的轮询由客户端改成了服务器端(客户端会在连接中断之后,会再次发送请求,对比短轮询来说,大大减少了发起连接的次数);
  • 2)客户端只会在数据改变时去作相应的改变,对比短轮询来说,并不是全盘接收。

6.2 代码实现

长轮询客户的代码实现(片段节选):

// 客户端

var LongPollingNotification = {

    // ....

    subscribe: function() {

      var that = this;

      // 设置超时时间

      Request.getV2Datas(this.getKey(),{ timeout: 10000 }).then(function(res) {

        var data = res.data;

        window.ChatroomDOM.renderData(res);

        // 成功获取数据后会再次发送请求

        that.subscribe();

      }).catch(function(error) {

        // timeout 之后也会再次发送请求

        that.subscribe();

      return this.unsubscribe;

    // ....

笔者采用的是express,默认不支持hold住请求,因此用了一个express-longpoll的库来实现。

下面是一个原生不用库的实现(这里只是介绍原理),整体的思路是:如果服务器端支持hold住请求的话,那么在一定的时间内会自轮询,然后期间通过比较key值,判断是否返回新数据。

以下是具体思路:

  • 1)客户端第一次会带一个空的key值,这次会立即返回,获取新内容,服务器端将计算出的contentKey返回给客户端;
  • 2)然后客户端发送第二次请求,带上第一次返回的contentKey作为key值,然后进行下一轮的比较;
  • 3)如果两次的key值相同,就会hold请求,进行内部轮询,如果期间有新内容或者客户端timeout,就会断开连接;
  • 4)重复以上步骤。

代码如下:

// 服务器端

router.get('/v2/datas', function(req, res) {

  const key = _.get(req.query, 'key', '');

  let contentKey = chatRoom.getContentKey();

  while(key === contentKey) {

    sleep.sleep(5);

    contentKey = chatRoom.getContentKey();

  const connectors = chatRoom.getConnectors();

  const messages = chatRoom.getMessages();

  res.json({

    code: 200,

    data: { connectors: connectors, messages: messages, key: contentKey },

以下是用 express-longpoll的实现片段:

// mini-chatroom/public/javascripts/server/longPolling.js

function pushDataToClient(key, longpoll) {

  var contentKey = chatRoom.getContentKey();

  if(key !== contentKey) {

    var connectors = chatRoom.getConnectors();

    var messages = chatRoom.getMessages();

    long poll.publish(

      '/v2/datas',

        code: 200,

        data: {connectors: connectors, messages: messages, key: contentKey},

long poll.create("/v2/datas", function(req, res, next) {

  key = _.get(req.query, 'key', '');

  pushDataToClient(key, longpoll);

  next();

intervalId = setInterval(function() {

  pushDataToClient(key, longpoll);

}, LONG_POLLING_TIMEOUT);

PS:完整代码,请见本文“4、本文配套Demo和代码”一节。

为了方便演示,我将客户端发起请求的timeout改成了4s,注意观察下面的截图:

可以看到,断开连接的两种方式,要么是超时,要么是请求有数据返回。

6.3 基于iframe的长轮询模式

这是长轮询技术的另一个种实现方案。

该方案的具体的原理为:

  • 1)在页面中嵌入一个iframe,地址指向轮询的服务器地址,然后在父页面中放置一个执行函数,比如execute(data);
  • 2)当服务器有内容改变时,会向iframe发送一个脚本<script>parent.execute(JSON.stringify(data))</script>;
  • 3)通过发送的脚本,主动执行父页面中的方法,达到推送的效果。

因不篇幅原因,在此不作深入介绍,有兴趣的同学可以详读《新手入门贴:史上最全Web端即时通讯技术原理详解》一文中的“3.3.2 基于iframe的数据流”一节。

7、什么是Server-Sent Events(SSE)

7.1 基本介绍

从纯技术的角度讲:上两节介绍的短轮询和长轮询技术,服务器端是无法主动给客户端推送消息的,都是客户端主动去请求服务器端获取最新的数据。

本节要介绍的SSE是一种可以主动从服务端推送消息的技术。

SSE的本质其实就是一个HTTP的长连接,只不过它给客户端发送的不是一次性的数据包,而是一个stream流,格式为text/event-stream。所以客户端不会关闭连接,会一直等着服务器发过来的新的数据流,视频播放就是这样的例子。

简单来说,SSE就是:

  • 1)SSE 使用 HTTP 协议,现有的服务器软件都支持。WebSocket 是一个独立协议。
  • 2)SSE 属于轻量级,使用简单;WebSocket 协议相对复杂。
  • 3)SSE 默认支持断线重连,WebSocket 需要自己实现。
  • 4)SSE 一般只用来传送文本,二进制数据需要编码后传送,WebSocket 默认支持传送二进制数据。
  • 5)SSE 支持自定义发送的消息类型。

SSE的技术原理如下图所示:

SSE基本的使用方法,可以参看 SSE 的API文档,地址是:https://developer.mozilla.org/en ... _server-sent_events

目前除了IE以及低版本的浏览器不支持,绝大多数的现代浏览器都支持SSE:

(上图来自:https://caniuse.com/?search=Server-Sent-Events

7.2 代码实现

// 客户端

var SSENotification = {

  source: null,

  subscribe: function() {

    if('EventSource'inwindow) {

      this.source = newEventSource('/sse');

      this.source.addEventListener('message', function(res) {

        const d = res.data;

        window.ChatroomDOM.renderData(JSON.parse(d));

    return this.unsubscribe;

  unsubscribe: function() {

    this.source && this.source.close();

// 服务器端

router.get('/sse', function(req, res) {

  const connectors = chatRoom.getConnectors();

  const messages = chatRoom.getMessages();

  const response = { code: 200, data: { connectors: connectors, messages: messages } };

  res.writeHead(200, {

    "Content-Type":"text/event-stream",

    "Cache-Control":"no-cache",

    "Connection":"keep-alive",

    "Access-Control-Allow-Origin": '*',

  res.write("retry: 10000\n");

  res.write("data: "+ JSON.stringify(response) + "\n\n");

  var unsubscribe = Event.subscribe(function() {

    const connectors = chatRoom.getConnectors();

    const messages = chatRoom.getMessages();

    const response = { code: 200, data: { connectors: connectors, messages: messages } };

    res.write("data: "+ JSON.stringify(response) + "\n\n");

  req.connection.addListener("close", function() {

    unsubscribe();

  }, false);

下面是控制台的情况,注意观察响应类型:

详情中注意查看请求类型,以及EventStream消息类型:

PS:有关SSE更详尽的资料就不在这里展开了,有兴趣的同学可以详读《SSE技术详解:一种全新的HTML5服务器推送事件技术》、《使用WebSocket和SSE技术实现Web端消息推送》。

8、什么是WebSocket

8.1 基本介绍

PS:本小节内容引用自《Web端即时通讯实践干货:如何让WebSocket断网重连更快速?》一文的“3、快速了解WebSocket”。

WebSocket诞生于2008年,在2011年成为国际标准,现在所有的浏览器都已支持(详见《新手快速入门:WebSocket简明教程》)。它是一种全新的应用层协议,是专门为web客户端和服务端设计的真正的全双工通信协议,可以类比HTTP协议来了解websocket协议。

(图片引用自《WebSocket详解(四):刨根问底HTTP与WebSocket的关系(上篇)》)

它们的不同点:

  • 1)HTTP的协议标识符是http,WebSocket的是ws;
  • 2)HTTP请求只能由客户端发起,服务器无法主动向客户端推送消息,而WebSocket可以;
  • 3)HTTP请求有同源限制,不同源之间通信需要跨域,而WebSocket没有同源限制。

它们的相同点:

  • 1)都是应用层的通信协议;
  • 2)默认端口一样,都是80或443;
  • 3)都可以用于浏览器和服务器间的通信;
  • 4)都基于TCP协议。

两者和TCP的关系图:

(图片引用自《新手快速入门:WebSocket简明教程》)

有关Http和WebSocket的关系,可以详读:

WebSocket详解(四):刨根问底HTTP与WebSocket的关系(上篇)

WebSocket详解(五):刨根问底HTTP与WebSocket的关系(下篇)

有关WebSocket和Socket的关系,可以详读:WebSocket详解(六):刨根问底WebSocket与Socket的关系》.

8.2 技术特征

WebSocket技术特征总结下就是:

  • 1)可双向通信,设计的目的主要是为了减少传统轮询时http连接数量的开销;
  • 2)建立在TCP协议之上,握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器;
  • 3)与HTTP兼容性良好,同样可以使用80和443端口;
  • 4)没有同源限制,客户端可以与任意服务器通信;
  • 5)可以发送文本,也可以发送二进制数据;
  • 6)协议标识符是ws(如果加密,则为wss),服务器网址就是 URL.

WebSocket的技术原理如下图所示:

关于WebSocket API方面的知识,这里不再作讲解,可以自己查阅:https://developer.mozilla.org/en-US/docs/Web/API/WebSocket

8.3 浏览器兼容性

WebSocket兼容性良好,基本支持所有现代浏览器。

(上图来自:https://caniuse.com/mdn-api_websocket

8.4 代码实现

笔者这里采用的是socket.io,是基于WebSocket的封装,提供了客户端以及服务器端的支持。

// 客户端

var WebsocketNotification = {

  // ...

  subscribe: function(args) {

    var connector = args[1];

    this.socket = io();

    this.socket.emit('register', connector);

    this.socket.on('register done', function() {

      window.ChatroomDOM.renderAfterRegister();

    this.socket.on('data', function(res) {

      window.ChatroomDOM.renderData(res);

    this.socket.on('disconnect', function() {

      window.ChatroomDOM.renderAfterLogout();

  // ...

// 服务器端

var io = socketIo(httpServer);

io.on('connection', (socket) => {

  socket.on('register', function(connector) {

    chatRoom.onConnect(connector);

    io.emit('register done');

    var data = chatRoom.getDatas();

    io.emit('data', { data });

  socket.on('chat', function(message) {

    chatRoom.receive(message);

    var data = chatRoom.getDatas();

    io.emit('data', { data });

PS:完整代码,请见本文“4、本文配套Demo和代码”一节。

响应格式如下:

8.5 深入学习

随着HTML5的普及率越来越高,WebSocket的应用也越来越普及,关于WebSocket的学习资料网上很容易找到,限于篇幅本文就不深入展开这个话题。

如果想进一步深入学习WebSocket的方方面面,以下文章值得一读:

新手快速入门:WebSocket简明教程

WebSocket详解(一):初步认识WebSocket技术

WebSocket详解(二):技术原理、代码演示和应用案例

WebSocket详解(三):深入WebSocket通信协议细节

WebSocket详解(四):刨根问底HTTP与WebSocket的关系(上篇)

WebSocket详解(五):刨根问底HTTP与WebSocket的关系(下篇)

WebSocket详解(六):刨根问底WebSocket与Socket的关系

理论联系实际:从零理解WebSocket的通信原理、协议格式、安全性

微信小程序中如何使用WebSocket实现长连接(含完整源码)

八问WebSocket协议:为你快速解答WebSocket热门疑问

Web端即时通讯实践干货:如何让你的WebSocket断网重连更快速?

WebSocket从入门到精通,半小时就够!

WebSocket硬核入门:200行代码,教你徒手撸一个WebSocket服务器

长连接网关技术专题(四):爱奇艺WebSocket实时推送网关技术实践

9、本文小结

短轮询、长轮询实现成本相对比较简单,适用于一些实时性要求不高的消息推送,在实时性要求高的场景下,会存在延迟以及会给服务器带来更大的压力。

SSE只能是服务器端推送消息,因此对于不需要双向通信的项目比较适用。

WebSocket目前而言实现成本相对较低,适合于双工通信,对于多人在线,要求实时性较高的项目比较实用。

本文已同步发布于“即时通讯技术圈”公众号。

▲ 本文在公众号上的链接是:点此进入。同步发布链接是:http://www.52im.net/thread-3555-1-1.html


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK