关于即时通讯:搞懂现代Web端即时通讯技术一文就够WebSocketsocketioSSE

3次阅读

共计 11255 个字符,预计需要花费 29 分钟才能阅读完成。

本文援用自“豆米博客”的《JS 实时通信三把斧》系列文章,有优化和改变。

1、引言

无关 Web 端即时通讯技术的文章我已整顿过很多篇,浏览过的读者可能都很相熟,晚期的 Web 端即时通讯计划,受限于 Web 客户端的技术限度,想实现真正的“即时”通信,难度相当大。

传统的 Web 端即时通讯技术从短轮询到长连询,再到 Comet 技术,在如此原始的 HTML 规范之下,为了实现所谓的“即时”通信,技术上堪称搜索枯肠,极尽所能。

自从 HTML5 规范公布之后,WebSocket 这类技术横空出世,实现 Web 端即时通讯技术的便利性大大提前,以往想都不敢想的真正全双工实时通信,如此早已成为可能。

本文将专门介绍 WebSocket、socket.io、SSE 这几种古代的 Web 端即时通讯技术,从实用场景到技术原理,艰深又不失深度的文字,特地适宜对 Web 端即时通讯技术有肯定理解,且想深刻学习 WebSocket 等古代 Web 端“实时”通信技术,却又不想花工夫去深读干燥的 IETF 技术手册的读者。

学习交换:

  • 即时通讯 / 推送技术开发交换 5 群:215477170 [举荐]
  • 挪动端 IM 开发入门文章:《新手入门一篇就够:从零开发挪动端 IM》
  • 开源 IM 框架源码:https://github.com/JackJiang2…

(本文同步公布于:http://www.52im.net/thread-36…)

2、本文作者

“豆米”:现居杭州,酷爱前端,酷爱互联网,豆米是“洋芋 (土豆 - 豆)”和“米喳(米)”的简称。
作者博客:https://blog.5udou.cn/
作者 Github:https://github.com/linxiaowu66/

3、常识准备

如果你对 Web 端即时通讯技术的前世今生未曾理解,倡议先读以下文章:

《新手入门贴:史上最全 Web 端即时通讯技术原理详解》
《Web 端即时通讯技术盘点:短轮询、Comet、Websocket、SSE》
《详解 Web 端通信形式的演进:从 Ajax、JSONP 到 SSE、Websocket》
《网页端 IM 通信技术疾速入门:短轮询、长轮询、SSE、WebSocket》

如果你对本文将要介绍的技术已有理解,倡议进行专项学习,以便深刻把握:

《Comet 技术详解:基于 HTTP 长连贯的 Web 端实时通信技术》
《SSE 技术详解:一种全新的 HTML5 服务器推送事件技术》
《WebSocket 详解(三):深刻 WebSocket 通信协议细节》
《实践联系实际:从零了解 WebSocket 的通信原理、协定格局、安全性》
《WebSocket 从入门到精通,半小时就够!》

4、WebSocket

在这里不打算具体介绍整个 WebSocket 协定的内容,依据我自己以前协定的学习思路,我挑重点应用问答形式来介绍该协定,这样读起来就不那么干燥。

4.1 根本状况
协定运行在 OSI 的哪层?

应用层,WebSocket 协定是一个独立的基于 TCP 的协定。它与 HTTP 惟一的关系是它的握手是由 HTTP 服务器解释为一个 Upgrade 申请。

协定运行的规范端口号是多少?

默认状况下,WebSocket 协定应用端口 80 用于惯例的 WebSocket 连贯、端口 443 用于 WebSocket 连贯的在传输层平安(TLS)RFC2818 之上的隧道化口。

4.2 协定是如何工作的?
协定的工作流程能够参考下图:

其中帧的一些重要字段须要解释一下:

1)Upgrade:upgrade是 HTTP1.1 中用于定义转换协定的 header 域。它示意,如果服务器反对的话,客户端心愿应用现有的「网络层」曾经建设好的这个「连贯(此处是 TCP 连贯)」,切换到另外一个「应用层」(此处是 WebSocket)协定;
2)Connection:Upgrade固定字段。Connection 还有其余字段,能够本人给本人科普一下;
3)Sec-WebSocket-Key:用来发送给服务器应用(服务器会应用此字段组装成另一个 key 值放在握手返回信息里发送客户端);
4)Sec-WebSocket-Protocol:标识了客户端反对的子协定的列表;
5)Sec-WebSocket-Version:标识了客户端反对的 WS 协定的版本列表,如果服务器不反对这个版本,必须回应本人反对的版本;
6)Origin:作平安应用,避免跨站攻打,浏览器个别会应用这个来标识原始域;
7)Sec-WebSocket-Accept:服务器响应,蕴含 Sec-WebSocket-Key 的签名值,证实它反对申请的协定版本。

对于 Sec-WebSocket-Key 和 Sec-WebSocket-Accept 的计算是这样的:

所有兼容 RFC 6455 的 WebSocket 服务器都应用雷同的算法计算客户端挑战的答案:将 Sec-WebSocket-Key 的内容与规范定义的惟一 GUID 字符 (258EAFA5-E914-47DA-95CA-C5AB0DC85B11) 串拼接起来,计算出 SHA1 散列值,后果是一个 base-64 编码的字符串,把这个字符串发给客户端即可。

用代码就是实现如下:

const key = crypto.createHash('sha1')
      .update(req.headers['sec-websocket-key'] + constants.GUID, 'binary')
      .digest('base64')

至于为什么须要这么一个步骤,能够参考《实践联系实际:从零了解 WebSocket 的通信原理、协定格局、安全性》一文。

援用如下:

Sec-WebSocket-Key/Sec-WebSocket-Accept 在次要作用在于提供根底的防护,缩小歹意连贯、意外连贯。

作用大抵归纳如下:

1)防止服务端收到非法的 websocket 连贯(比方 http 客户端不小心申请连贯 websocket 服务,此时服务端能够间接回绝连贯);
2)确保服务端了解 websocket 连贯。因为 ws 握手阶段采纳的是 http 协定,因而可能 ws 连贯是被一个 http 服务器解决并返回的,此时客户端能够通过 Sec-WebSocket-Key 来确保服务端意识 ws 协定。(并非百分百保险,比方总是存在那么些无聊的 http 服务器,光解决 Sec-WebSocket-Key,但并没有实现 ws 协定。。。);
3)用浏览器里发动 ajax 申请,设置 header 时,Sec-WebSocket-Key 以及其余相干的 header 是被禁止的。这样能够防止客户端发送 ajax 申请时,意外申请协定降级(websocket upgrade);
4)能够避免反向代理(不了解 ws 协定)返回谬误的数据。比方反向代理前后收到两次 ws 连贯的降级申请,反向代理把第一次申请的返回给 cache 住,而后第二次申请到来时间接把 cache 住的申请给返回(无意义的返回);
5)Sec-WebSocket-Key 次要目标并不是确保数据的安全性,因为 Sec-WebSocket-Key、Sec-WebSocket-Accept 的转换计算公式是公开的,而且非常简单,最次要的作用是预防一些常见的意外状况(非故意的)。

强调:Sec-WebSocket-Key/Sec-WebSocket-Accept 的换算,只能带来根本的保障,但连贯是否平安、数据是否平安、客户端 / 服务端是否非法的 ws 客户端、ws 服务端,其实并没有实际性的保障。

4.3 协定传输的帧格局是什么?
帧格局定义的格局如下:

各个字段的解释如下:

1)FIN:1bit,用来表明这是一个音讯的最初的音讯片断,当然第一个音讯片断也可能是最初的一个音讯片断;
2)RSV1,RSV2,RSV3:别离都是 1 位,如果单方之间没有约定自定义协定,那么这几位的值都必须为 0, 否则必须断掉 WebSocket 连贯。在 ws 中就用到了 RSV1 来示意是否消息压缩了的;
3)opcode:4 bit,示意被传输帧的类型:

  • %x0 示意间断音讯片断;
  • %x1 示意文本音讯片断;
  • %x2 表未二进制音讯片断;
  • %x3-7 为未来的非管制音讯片断保留的操作码;
  • %x8 示意连贯敞开;
  • %x9 示意心跳查看的 ping;
  • %xA 示意心跳查看的 pong;
  • %xB-F 为未来的管制音讯片断的保留操作码。
    4)Mask:1 bit。定义传输的数据是否有加掩码, 如果设置为 1, 掩码键必须放在 masking-key 区域,客户端发送给服务端的所有音讯,此位都是 1;
    5)Payload length:传输数据的长度,以字节的模式示意:7 位、7+16 位、或者 7 +64 位。如果这个值以字节示意是 0 -125 这个范畴,那这个值就示意传输数据的长度;如果这个值是 126,则随后的两个字节示意的是一个 16 进制无符号数,用来示意传输数据的长度;如果这个值是 127, 则随后的是 8 个字节示意的一个 64 位无合乎数,这个数用来示意传输数据的长度。多字节长度的数量是以网络字节的程序示意。负载数据的长度为扩大数据及利用数据之和,扩大数据的长度可能为 0, 因此此时负载数据的长度就为利用数据的长度;
    6)Masking-key:0 或 4 个字节,客户端发送给服务端的数据,都是通过内嵌的一个 32 位值作为掩码的;掩码键只有在掩码位设置为 1 的时候存在;
    7)Extension data:x 位,如果客户端与服务端之间没有非凡约定,那么扩大数据的长度始终为 0,任何的扩大都必须指定扩大数据的长度,或者长度的计算形式,以及在握手时如何确定正确的握手形式。如果存在扩大数据,则扩大数据就会包含在负载数据的长度之内;
    8)Application data:y 位,任意的利用数据,放在扩大数据之后,利用数据的长度 = 负载数据的长度 - 扩大数据的长度;
    9)Payload data:(x+y)位,负载数据为扩大数据及利用数据长度之和;

更多细节请参考 RFC6455- 数据帧,这里不作赘述。

针对下面的各个字段的介绍,有一个 Mask 的须要说一下。

掩码键(Masking-key)是由客户端筛选进去的 32 位的随机数。掩码操作不会影响数据载荷的长度。

掩码、反掩码操作都采纳如下算法。

首先,假如:

1)original-octet-i:为原始数据的第 i 字节;
2)transformed-octet-i:为转换后的数据的第 i 字节;
3)j:为 i mod 4 的后果;
4)masking-key-octet-j:为 mask key 第 j 字节。

算法形容为:original-octet-i 与 masking-key-octet-j 异或后,失去 transformed-octet-i。

即:j = i MOD 4 transformed-octet-i = original-octet-i XOR masking-key-octet-j

用代码实现:

const mask = (source, mask, output, offset, length) => {for(vari = 0; i < length; i++) {output[offset + i] = source[i] ^ mask[i & 3];
  }
};

解掩码是反过来的操作:

const unmask = (buffer, mask) => {// Required until [url=https://github.com/nodejs/node/issues/9006]https://github.com/nodejs/node/issues/9006[/url] is resolved.
  const length = buffer.length;
  for(vari = 0; i < length; i++) {buffer[i] ^= mask[i & 3];
  }
};

同样的为什么须要掩码操作,也能够参考之前的那篇文章:《实践联系实际:从零了解 WebSocket 的通信原理、协定格局、安全性》,残缺的我就不列举了。

须要留神的重点,我援用一下:

WebSocket 协定中,数据掩码的作用是加强协定的安全性。但数据掩码并不是为了爱护数据自身,因为算法自身是公开的,运算也不简单。除了加密通道自身,仿佛没有太多无效的爱护通信安全的方法。

那么为什么还要引入掩码计算呢,除了减少计算机器的运算量外仿佛并没有太多的收益(这也是不少同学纳闷的点)。

答案还是两个字:平安。但并不是为了避免数据泄密,而是为了避免晚期版本的协定中存在的代理缓存净化攻打(proxy cache poisoning attacks)等问题。

5、socket.io

5.1 本节引言

介绍完上一节 WebSocket 协定,咱们把眼帘转移到古代 Web 端即时通讯技术的第二个利器:socket.io。

预计有读者就会问,WebSocket 和 socket.io 有啥区别啊?

在理解 socket.io 之前,咱们先聊聊传统 Web 端即时通讯“长连贯”技术的实现背景。

5.2 传统 Web 长连贯的技术实现背景
在事实的 Web 端产品中,并不是所有的 Web 客户端都反对长连贯的,或者换句话说,在 WebSocket 协定进去之前,是三种形式去实现 WebSocket 相似的性能的。

这三种形式是:

1)Flash:应用 Flash 是一种简略的办法。不过很显著的毛病就是 Flash 并不会装置在所有客户端上,比方 iPhone/iPad。
2)Long-Polling:也就是众所周之的“长轮询”,在过来,这是一种无效的技术,但并没有对音讯发送进行优化。尽管我不会把 AJAX 长轮询当做一种 hack 技术,但它的确不是一个最优办法;
3)Comet:在过来,这被称为 Web 端的“服务器推”技术,绝对于传统的 Web 利用,开发 Comet 利用具备肯定的挑战性,具体请见《Comet 技术详解:基于 HTTP 长连贯的 Web 端实时通信技术》。

那么如果单纯地应用 WebSocket 的话,那些不反对的客户端怎么办呢?难道间接放弃掉?

当然不是。Guillermo Rauch 大神写了 socket.io 这个库,对 WebSocket 进行封装,从而让长连贯满足所有的场景,不过当然得配合应用对应的客户端代码。

socket.io 将会应用个性检测的形式来决定以 websocket/ajax 长轮询 /flash 等形式建设连贯。

那么 socket.io 是如何做到这些的呢?

咱们带着以下几个问题去学习:

1)socket.io 到底有什么新个性?
2)socket.io 是怎么实现个性检测的?
3)socket.io 有哪些坑呢?
4)socket.io 的理论利用是怎么的,须要留神些什么?

如果有童鞋对上述问题曾经分明,想必就没有往下读的必要了。

5.3 socket.io 的介绍
通过后面章节,读者们都晓得了 WebSocket 的性能,那么 socket.io 绝对于 WebSocket,在此基础上封装了一些什么新货色呢?

socket.io 其实是有一套封装了 websocket 的协定,叫做 engine.io 协定,在此协定上实现了一套底层双向通信的引擎 Engine.io。

而 socket.io 则是建设在 engine.io 上的一个应用层框架而已。所以咱们钻研的重点便是 engine.io 协定。

在 socket.io 的 README 中提到了其实现的一些新个性(答复了问题一):

1)可靠性:连贯仍然能够建设即便应用环境存在:代理或者负载均衡器 集体防火墙或者反病毒软件;
2)反对主动连贯:除非特地指定,否则一个断开的客户端会始终重连服务器直到服务器复原可用状态;
3)断开连接检测:在 Engine.io 层实现了一个心跳机制,这样容许客户端和服务器晓得什么时候其中的一方不能响应。该性能是通过设置在服务端和客户端的定时器实现的,在连贯握手的时候,服务器会被动告知客户端心跳的间隔时间以及超时工夫;
4)二进制的反对:任何序列化的数据结构都能够用来发送;
5)跨浏览器的反对:该库甚至反对到 IE8;
6)反对复用:为了在应用程序中将创立的关注点隔离开来,Socket.io 容许你创立多个 namespace,这些 namespace 领有独自的通信通道,但将共享雷同的底层连贯;
7)反对 Room:在每一个 namespace 下,你能够定义任意数量的通道,咱们称之为 ” 房间 ”,你能够退出或者来到房间,甚至播送音讯到指定的房间。

留神:Socket.IO 不是 WebSocket 的实现,尽管 Socket.IO 的确在可能的状况下会去应用 WebSocket 作为一个 transport,然而它增加了很多元数据到每一个报文中:报文的类型以及 namespace 和 ack Id。这也是为什么规范 WebSocket 客户端不可能胜利连贯上 Socket.IO 服务器,同样一个 Socket.IO 客户端也连贯不上规范 WebSocket 服务器的起因。

5.4 engine.io 协定介绍
残缺的 engine.io 协定的握手过程如下图:

以后 engine.io 协定的版本是 3,咱们依据上图来大抵介绍一下 engine.io 协定。

5.4.1)engine.io 协定申请字段:

咱们看到的是申请的 url 和 WebSocket 不大一样,解释一下:

1)EIO=3: 示意的是应用的是 Engine.io 协定版本 3;
2)transport=polling/websocket: 示意应用的长连贯形式是轮询还是 WebSocket;
3)t=xxxxx: 代码中应用 yeast 依据工夫戳生成一个惟一的字符串;
4)sid=xxxx: 客户端和服务器建设连贯之后获取到的 session id,客户端拿到之后必须在每次申请中追加这个字段。

除了上述的 3 个字段,协定还形容了上面几个字段:

1)j: 如果 transport 是 polling,然而要求有一个 JSONP 的响应,那么 j 就应该设置为 JSONP 响应的索引值;
2)b64: 如果客户端不反对 XHR,那么客户端应该设置 b64= 1 传给服务器,告知服务器所有的二进制数据应该以 base64 编码后再发送。

另外 engine.io 默认的 path 是 /engine.io,socket.io 在初始化的时候设置为了 /socket.io,所以大家看到的 path 就都是 /socket.io 了:

function Server(srv, opts){if(!(this instanceof Server)) return new Server(srv, opts);
  if('object'== typeof srv && srv instanceof Object && !srv.listen) {
    opts = srv;
    srv = null;
  }

  opts = opts || {};
  this.nsps = {};
  this.parentNsps = new Map();
  this.path(opts.path || '/socket.io');

5.4.2)数据包编码要求:

engine.io 协定的数据包编码有本人的一套格局,在协定介绍上 engine.io-protocol,定义了两种编码类型:packet 和 payload。

一个编码过的 packet 是上面这种格局:
<packettype id>[<data>]

而后协定定义了上面几种 packet type(采纳数字进行标识):

1)0(open): 当开始一个新的 transport 的时候,服务端会发送该类型的 packet;
2)1(close): 申请敞开这个 transport 然而不要本人敞开敞开连贯;
3)2(ping): 由客户端发送的 ping 包,服务端必须回应一个蕴含雷同数据的 pong 包;
4)3(pong): 响应 ping 包,服务端发送;
5)4(message): 理论音讯,在客户端和服务端都能够监听 message 事件获取音讯内容;
6)5(upgrade): 在 engine.io 切换 transport 之前,它会用来测试服务端和客户端是否在该 transport 上通信。如果测试胜利,客户端会发送一个 upgrade 包去让服务器刷新它的缓存并切换到新的 transport;
7)6(noop): 次要用来强制一个轮询循环当收到一个 WebSocket 连贯的时候。

那 payload 也有对应的格局要求:

1)如果当只有发送 string 并且不反对 XHR 的时候,其编码格局是::[:[…]];
2)当不反对 XHR2 并且发送二进制数据, 然而应用 base64 编码字符串的时候,其编码格局是::b[…];
3)当反对 XHR2 的时候,所有的数据都被编码成二进制,格局是:<0 for string data, 1 for binary data>[…];
4)如果发送的内容混杂着 UTF- 8 的字符和二进制数据,字符串的每个字符被写成一个字符编码,用 1 个字节示意。

留神:payload 的编码要求不适用于 WebSocket 的通信。

针对下面的编码要求,咱们轻易举个例子.

之前在第一条 polling 申请的时候,服务端编码发送了这个数据:

97:0{"sid":"Peed250dk55pprwgAAAA","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":60000}2:40

依据下面的常识,咱们晓得第一次服务端会发送一个 open 的数据包。

所以组装进去的 packet 是:
0

而后服务端会告知客户端去尝试降级到 websocket,并且告知对应的 sid。

于是整合后便是:

0{"sid":"Peed250dk55pprwgAAAA","upgrades":"websocket","pingInterval":25000,"pingTimeout":60000}

接着依据 payload 的编码格局,因为是 string,且长度是 97 个字节。

所以是:

97:0{"sid":"Peed250dk55pprwgAAAA","upgrades":"websocket","pingInterval":25000,"pingTimeout":60000}

接着第二局部数据是 message 包类型,并且数据是 0,所以是 40,长度为 2 字节,所以是 2:40,最初就拼成方才大家看到的后果。

留神:

ping/pong 的间隔时间是服务端告知客户端的:”pingInterval”:25000,”pingTimeout”:60000,也就是说心跳工夫默认是 25 秒,并且期待 pong 响应的工夫默认是 60s。

5.5 降级协定的必备过程
协定定义了 transport 降级到 websocket 须要经验一个必须的过程。

如下图:

WebSocket 的测试开始于发送 probe,如果服务器也响应 probe 的话,客户端就必须发送一个 upgrade 包。

为了确保不会丢包,只有在以后 transport 的所有 buffer 被刷新并且 transport 被认为 paused 的时候才能够发送 upgrade 包。服务端收到 upgrade 包的时候,服务端必须假如这是一个新的通道并发送所有已存的缓存到这个通道上

在 Chrome 上的成果如下:

5.6 engine.io 的代码实现
相熟了 engine.io 协定之后,咱们看看代码是怎么实现主流程的。

客户端的 engine.io 的次要实现流程咱们在下面文字介绍了。

联合代码 engine.io,画了这么一个客户端流程图:

服务端的代码和客户端十分类似,其实现流程图如下:

6、SSE

6.1 本节引言

本文前两节剖析了 WebSocket 和 socket.io,当初咱们来看看 SSE。

很多人兴许好奇,有了 WebSocket 这种实时通信,为什么还须要 SSE 呢?

答案其实很简略:那就是 SSE 其实是单向通信,而 WebSocket 是双向通信。

比方:在股票行情、新闻推送的这种只须要服务器发送音讯给客户端场景中,应用 SSE 可能更加适合。

另外:SSE 是应用 HTTP 传输的,这意味着咱们不须要一个非凡的协定或者额定的实现就能够应用。而 WebSocket 要求全双工连贯和一个新的 WebSocket 服务器去解决。加上 SSE 在设计的时候就有一些 WebSocket 没有的个性,比方主动重连贯、event IDs、以及发送随机事件的能力,所以各有各的专长,咱们须要依据理论利用场景,去抉择不同的利用计划。

6.2 SSE 介绍
SSE 的简略模型是:一个客户端去从服务器端订阅一条“流”,之后服务端能够发送音讯给客户端直到服务端或者客户端敞开该“流”,所以 SSE 全称叫“server-sent-event”。

相比以前的轮询,SSE 能够为 B2C 带来更高的效率。

有一张图片画出了二者的区别:

6.3 SSE 数据帧的格局
SSE 必须编码成 utf- 8 的格局,音讯的每个字段应用 ”\n” 来做宰割,并且须要上面 4 个标准定义好的字段。

这 4 个字段是:

1)Event: 事件类型;
2)Data: 发送的数据;
3)ID: 每一条事件流的 ID;
4)Retry:告知浏览器在所有的连贯失落之后从新开启新的连贯期待的工夫,在主动从新连贯的过程中,之前收到的最初一个事件流 ID 会被发送到服务端。

下图是通过 wireshark 抓包失去的数据包的原始格局:

6.4 SSE 通信过程
SSE 的通信过程比较简单,底层的一些实现都被浏览器给封装好了,包含数据的解决。

大抵流程如下:

在浏览器中截图如下:

携带的数据是 JSON 格局的,浏览器都帮你整合成为一个 Object:

在 wireshark 中,其通信流程如下。

发送申请:

失去响应:

在开始推送信息流之前,服务器还会发送一个客户端会疏忽掉的包,这个具体起因不分明:

断开连接后的重传:

6.5 SSE 的简略应用示例
浏览器端的应用:
const es = new EventSource('/sse')

服务端的应用:

const sseStream = new SseStream(req)
sseStream.pipe(res)
sseStream.write({
  id: sendCount,
  event: 'server-time',
  retry: 20000, // 通知客户端, 如果断开连接后,20 秒后再重试连贯
  data: {ts: newDate().toTimeString(), count: sendCount++}
})

更多 API 应用和 demo 介绍别离参考:SSE API、demo 代码。

6.6 兼容性及毛病
兼容性:

▲ 上图来自 https://caniuse.com/?search=S…

毛病:

1)因为是服务器 -> 客户端的,所以它不能解决客户端申请流;
2)因为是明确指定用于传输 UTF- 8 数据的,所以对于传输二进制流是低效率的,即便你转为 base64 的话,反而减少带宽的负载,得失相当。

7、参考资料

[1] WebSocket API 文档
[2] SSE API 文档
[3] 新手入门贴:史上最全 Web 端即时通讯技术原理详解
[4] Web 端即时通讯技术盘点:短轮询、Comet、Websocket、SSE
[5] SSE 技术详解:一种全新的 HTML5 服务器推送事件技术
[6] Comet 技术详解:基于 HTTP 长连贯的 Web 端实时通信技术
[7] 老手疾速入门:WebSocket 扼要教程
[8] WebSocket 详解(三):深刻 WebSocket 通信协议细节
[9] WebSocket 详解(四):刨根问底 HTTP 与 WebSocket 的关系(上篇)
[10] WebSocket 详解(五):刨根问底 HTTP 与 WebSocket 的关系(下篇)
[11] 应用 WebSocket 和 SSE 技术实现 Web 端音讯推送
[12] 详解 Web 端通信形式的演进:从 Ajax、JSONP 到 SSE、Websocket
[13] MobileIMSDK-Web 的网络层框架为何应用的是 Socket.io 而不是 Netty?
[14] 实践联系实际:从零了解 WebSocket 的通信原理、协定格局、安全性
[15] WebSocket 从入门到精通,半小时就够!
[16] WebSocket 硬核入门:200 行代码,教你徒手撸一个 WebSocket 服务器
[17] 网页端 IM 通信技术疾速入门:短轮询、长轮询、SSE、WebSocket

本文已同步公布于“即时通讯技术圈”公众号。
同步公布链接是:http://www.52im.net/thread-36…

正文完
 0