浅析现代web端im即时通讯开发技术

wecloud1314 wecloud1314     2022-11-29     600

关键词:

传统的Web端即时通讯技术从短轮询到长连询,再到Comet技术,在如此原始的HTML标准之下,为了实现所谓的“即时”通信,技术上可谓绞尽脑汁,极尽所能。

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

 

在这里不打算详细介绍整个WebSocket协议的内容,根据我本人以前协议的学习思路,我挑重点使用问答方式来介绍该协议,这样读起来就不那么枯燥。

协议运行在OSI的哪层?

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

协议运行的标准端口号是多少?

默认情况下,WebSocket协议使用端口80用于常规的WebSocket连接、端口443用于WebSocket连接的在传输层安全(TLS)RFC2818之上的隧道化口。

协议是如何工作的?

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

    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')

引用如下:

    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服务端,其实并没有实际性的保证。即时通讯开发

 

协议传输的帧格式是什么?

各个字段的解释如下:

    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)等问题。

传统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服务器的原因。

浅析即时通讯移动端im开发中登录请求的优化

到底是“登陆”还是“登录”?这是很多处女坐开发者纠结的问题,不过它不是本文本讨伦的内容。本文将针对移动端IM的登陆功能给出相应的优化建议。源起在移动IM模块中,最核心最复杂的模块莫过于登录模块。... 查看详情

浅析im即时通讯开发移动端dns域名劫持问题

对于互联网,域名是访问的第一跳,而这一跳很多时候会“失足”(尤其是移动端网络),导致访问错误内容、失败连接等,让用户在互联网上畅游的爽快瞬间消失。而对于这关键的第一跳,包括鹅厂... 查看详情

浅析im即时通讯开发加白名单

IM在Android上的保活问题经常在即时通讯网的论坛和技术群里被讨论,自从Android8.0后系统大大降低了后台运行应用的保活容忍度,保活从黑科技横行的时代进入了技术蛮荒阶段,真要实现保活,技术难度越来越大。不过话说回来... 查看详情

浅析即时通讯开发前置httpsso单点登陆接口的原理

一个安全的信息系统,合法身份检查是必须环节。尤其IM这种以“人”为中心的社交体系,身份认证更是必不可少。一些PC时代小型IM系统中,身份认证可能直接做到长连接中(也就是整个IM系统都是以长连接为中... 查看详情

浅析im即时通讯开发出现上网卡顿?网络掉线?

特别推荐即时通讯开发者来阅读,因为针对移动弱网的问题,确实可以找到很多有价值的答案。作为即时通讯(IM、消息推送等应用场景)相关技术的开发者人员来说,似乎了解跨专业的通信技术(这是大... 查看详情

浅析im即时通讯开发加白名单

IM在Android上的保活问题经常在即时通讯网的论坛和技术群里被讨论,自从Android8.0后系统大大降低了后台运行应用的保活容忍度,保活从黑科技横行的时代进入了技术蛮荒阶段,真要实现保活,技术难度越来越大。... 查看详情

浅析百万消息量小规模im即时通讯系统技术

现如今的互联网产品中,即时通讯技术已经不仅限于传统IM聊天工具本身,它早已通过有形或无形的方式嵌入到了各种形式的互联网应用当中。IM技术(或者说即时通讯技术)对于很多开发者来说,确实是必不... 查看详情

浅析百万消息量小规模im即时通讯系统技术

现如今的互联网产品中,即时通讯技术已经不仅限于传统IM聊天工具本身,它早已通过有形或无形的方式嵌入到了各种形式的互联网应用当中。IM技术(或者说即时通讯技术)对于很多开发者来说,确实是必不... 查看详情

即时通讯技术文集(第13期):web端即时通讯技术精华合集[共15篇]

...13 期。[- 1 -] 新手入门贴:史上最全Web端即时通讯技术原理详解[链接] http://www.52im.net/thread-338-1-1.html[摘要] 本文的目的就是要详细探讨这些技术并分析其原理和过程。[- 2 -] Web端即时通讯技术盘... 查看详情

浅析怎么开发分布式im即时通讯系统

本文不会给出一套通用的IM方案,也不会评判某种架构的好坏,而是讨论设计IM系统的常见难题跟业界的解决方案。因为也没有所谓的通用IM架构方案,不同的解决方案都各有其优缺点,只有最满足业务的系统才是... 查看详情

浅析怎么开发分布式im即时通讯系统

本文不会给出一套通用的IM方案,也不会评判某种架构的好坏,而是讨论设计IM系统的常见难题跟业界的解决方案。因为也没有所谓的通用IM架构方案,不同的解决方案都各有其优缺点,只有最满足业务的系统才是... 查看详情

浅析大型im即时通讯系统开发难度

本文从一个简单的系统例子开始,从单机架构、主从副本、同城灾备、同城双活,再到异地双活、异地多活,由浅入深、循序渐进地讲解了大型分布式系统异地多活容灾架构的技术原理和基本的实现思路,非常适... 查看详情

搭建即时通讯web端高性能分布式im聊天服务器

Pomelo是来自网易公司的基于Node.js的高性能、分布式游戏服务器框架。它包括基础的开发框架和相关的扩展组件(库和工具包),可以帮助你省去游戏开发枯燥中的重复劳动和底层逻辑的开发。Pomelo不但适用于游戏服... 查看详情

web端即时通讯技术原理分享

web端的IM应用,由于浏览器的兼容性以及其固有的“客户端请求服务器处理并响应”的通信模型,造成了要在浏览器中实现一个兼容性较好的IM应用,其通信过程必然是诸多技术的组合,本文的目的就是要详细探讨... 查看详情

浅析im即时通讯开发之扫码登录二维码

二维码技术使用起来很简单,本系列的前三篇文章也专门针对IM扫码登录这个功能做了详细的分享,但本着学习技术不留死角的习惯,我认为有必要单独学习一下到底什么是二维码(说不定哪天被个刚入行的程序... 查看详情

网页端im即时通讯技术入门之短轮询长轮询ssewebsocket

对Web端即时通讯技术熟悉的开发者来说,我们回顾网页端IM的底层通信技术,从短轮询、长轮询,到后来的SSE以及WebSocket,使用门槛越来越低(早期的长轮询Comet这类技术实际属于hack手段,使用门槛并不低&... 查看详情

im即时通讯开发:为什么要用https?浅析短连接的安全性

对于IM开发者来说,IM里最常用的通信技术就是Socket长连接和HTTP短连接(通常一个主流im会是这两种通信手段的结合)。从通信安全的角度来说,Socket长连接的安全性,就是基于SSL/TLS加密的TCP协议来实现的࿱... 查看详情

浅析im即时通讯开发中tcp协议层keepalive保活机制

对于IM这种应用而言,应用层的网络保活的最直接办法就是心跳机制,比如主流的IM里有微信、QQ、钉钉、易信等等,可能代码实现细节有所差异,但理论上无一例外都是这样实现。(PS:没错,当初微... 查看详情