Web Electron 平台即时通讯产品的技术选型

news/2024/10/18 16:31:48/

 (点击报名融云 2022 社交泛娱乐出海嘉年华)

8 月底,“IM 进阶实战高手课·第二讲”围绕“Web & Electron 平台即时通讯产品的技术选型”进行了详细拆解。融云讲师巧用比喻等方法,生动而又逻辑清晰地对 IM 场景前端技术方案进行了分析对比,并分享了融云的最佳实践。下期聚焦 IM 全能力 ,就在 9 月 20 日 关注【融云全球互联网通信云】了解更多


IM 常见业务形态及核心功能

即时通讯产品常见的业务形态有以下几种:聊天室、单群聊、超级群、实时通知、在线广播。而底层功能就像基础零件,可以用不同的方法拼接出上层的不同业务形态。

基础功能单元模块,大概就分为三类:

最基础的是连接管理类的需求,这是即时通讯业务的基础。接着是两端基于连接的数据传输,这里我们要关注的是前后端通讯时的数据传输协议,也就是对于数据的序列化和反序列化的过程管理。

最后就是基于既有数据的查询功能,我们主要分享前端的持久化存储技术

即时通讯场景下,对于这三个技术点的一些技术要求各不相同。

  • 连接管理 - 持续、稳定、及时的双向网络连接

  • 数据传输 - 安全、高效、易拓展的前后端数据传输协议

  • 记录查询 - 前端数据持久化存储方案


网络连接方案对比

我们通过五个指标来横向对比连接方案,分别是:连接速度、传输效率、即时性、安全性、兼容性WebSocket 是前端的首选方案,它也是 Web 平台上构建长连接业务的原生技术方案。

因为浏览器安全沙箱的存在,我们不能在 Web 浏览器内直接访问传输层协议,但是 Electron 主进程内是百无禁忌的,我们可以在 Electron 场景下选用 TCP

基于 HTTP 的模拟双工协议解决方案,并不是单纯的 HTTP 协议本身,因为在长连接业务中,短连接特性的 HTTP 协议并不匹配需求场景。

连接速度

连接速度是从发起连接到连接建立的耗时。

TCP 的连接需要进行三次握手,对于发起端是发两次收一次,对于应答端就是发一次收两次。

 

为什么要做三次握手?其实这也是一个很常见的面试题。打个比方来说,两个人要达成一次有效对话,就需要确定两个信息:一是自己的耳朵和嘴巴没有问题;二是对方的耳朵和嘴巴没有问题。只有在这个基础之上,两者之间的对话才是有效的。

三次握手就是要完成这么一个能力确认过程。嘴巴=发送能力,耳朵=接收能力。

当然这个比喻也不严谨,双方语言不通时也沟通不来。这是第二个技术方向数据封装协议要解决的问题,也就是对对端意图理解问题。

WebSocket 连接要建立在 TCP 连接基础之上,因为 WebSocket 是一个应用层协议,也就是运维们经常说的 7 层协议,而 TCP 是一个传输层协议,是 4 层协议。

 

TCP 握手完成之后,通过 HTTP 报文向服务器协议提出升级申请,服务通过 HTTP response 报文来响应申请,至此 WebSocket 协商完成。比 TCP 多了两个动作,所以 WebSocket 的连接速度是慢于 TCP 的。

HTTP 是短连接协议,连接无法稳定保持,每次通讯要重建连接,所以连接速度对它没有意义。

传输效率

我们把传输效率定义为同一块数据在传输过程中产生的流量消耗、时间消耗、算力消耗,以辅助横向对比不同网络协议,消耗越大,效率越低。

这里我们主要看流量消耗和时间消耗,算力消耗在 Web 和 Electron 几乎可以忽略不计。

先看一下 OSI 参考模型,TCP 是 4 层协议,WebSocekt 是 7 层协议,这些其实就是 OSI 参考模型里的层级概念。

 

OSI 参考模型里,数据从两个网络节点之间传递,是一个 U 型传递过程:发送端从上到下传递,每层需要在数据包中增加不同的协议头信息,以确保接收端同层可以解析;接收端从下往上传递,传递过程逐层剥离数据中的头信息,并将数据向上传递。所以数据包从上到下的传递过程中,数据体积是不断加码的。同一段数据,直接通过 TCP 发送,流量消耗低于 WebSocket 协议。

我们看一下通过 WebSocket 协议传递数据的额外流量有哪些。

首先 WebSocket 数据传递的最小单元是数据帧。一段数据会被分割组装为最少一个数据帧写入到 TCP 的缓冲区,如果数据比较大,就会被分拆为多个数据帧,然后对端接收到之后再进行数据帧还原。

下图的二进制序列结构就是数据帧中的数据组成。

 

HTTP 是文本型协议,没有最小发送单元,或者说 HTTP 的报文数据的最小发送单元,就是 TCP 协议的最小发送单元。它不像 WebSocket 会将数据分装为 N 个数据帧,每个数据帧增加 WebSocket 头信息后再交给 TCP。它不主动分割数据,只在数据首部增加首行和 Headers 信息,并最终把完整报文信息写入 TCP 缓冲区,交由 TCP 去管理传输过程。

那么,HTTP 的传输效率是否就优于 WebSocket 呢?并不一定。

首先,一个 WebSocket 数据帧最多额外增加 2 - 14 个字节的头信息,但是 HTTP 协议本身的首行和 Headers 信息消耗的空间是远大于 14 字节的,这也是因为 HTTP 协议的特性导致的。它是文本型协议,一个字符最少需要一个字节的容量来存储。

其次,HTTP 协议的拓展,会额外增加报文 Headers 信息,这些信息也是字符型的,且是键值对形式。另外,HTTP 本身是短连接,意味着服务在收到请求时首先要确认数据发送者的身份,所以报文数据中不可避免地在每次的请求中携带鉴权信息,但是长连接协议是不需要这些额外开销的。

所以,WebSocket 的总体传输效率是优于 HTTP 的,除非待传输的数据大到了使 WebSocket 数据帧数量的头信息空间总和超过了 HTTP 报文头的程度,但是这种情况一般发生在文件上传等低频场景。大部分业务数据往来中,单次发送的数据都不会很大。

即时性

即时性是数据准备完成,到被写入到 TCP 缓冲区可能经历哪些等待时长。

对于即时通讯场景,数据的上下行是同时在发生的,这也意味着 HTTP 的短特性很吃亏,因为下行数据会受阻,服务器无法通过短连接的 HTTP 协议完成数据的主动推送。

这里我们先普及一下网络协议的一些基础概念,因为它跟我们要去比较的即时性是息息相关。

第一类概念,是对于连接持续性的描述。

长连接,通俗点讲就是连接在建立后是持续存在的,双端可以通过已存在的连接互发数据,只有当一端主动终止连接,连接才会被关闭。TCP 和 WebSocket 都属于长连接协议。(关于长连接的更多分享,点此了解)

短连接,是说当我需要与对端通信时建立连接,通信完毕后立即关闭连接。HTTP 就是一个短连接协议。发起请求的时候建立连接,收到响应之后连接就会关闭。当然,也可以利用 KeepAlive 去保持 TCP 连接复用,不过它还是不能保证连接不能被关闭。因为连接的持续有效,长连接的即时性是优于短连接的。因为它避免了数据发送时要建立连接的等待过程。

第二类概念,是对于字节流数据流向控制的定义。

全双工协议,是说字节流可以在连接中双向自由流动,因为这种自由流动,所以这类协议的即时性是最好的,也通常是长连接协议。只要缓冲区够大,就基本没有等待过程。TCP、WebSocket 都是属于全双工协议。

半双工协议,是说字节流可以在两个方向上流动,但同一时刻只能存在一个方向上的流动数据。半双工协议就像一条路上只有一个车道,对向有来车时,本方向的车就不能进车道,否则路就堵死了。HTTP 协议就是一个典型的半双工协议,它实际上是允许数据双向流动的,但是它的响应必须在请求数据接收完成之后,同一时刻不存在双向流动的字节流。半双工协议的即时性要低于全双工,因为它有对连接的使用等待过程,当有对向的数据流时,数据要延迟发送。

单工协议,就是数据只能单向流动,比如 HTTP 协议中的 SSE 功能。因为单工协议不能独立完成双向数据流动,不符合即时通讯的需求,所以我们就不考虑了。

归类来看,WebScoekt 和 TCP 的即时性基本属于同一级别的,HTTP 则比他们要弱

当然,HTTP 单独拎出来一个请求是不能和长连接协议比的,我们还要看一下通过 HTTP 协议的并发多连接请求能不能弥补它自身的不足。这里,我们再深入分析一下基于 HTTP 协议的长连接模拟方案。

我们先想一下,基于 HTTP 协议构建的解决方案,要解决的核心问题是什么?

第一点,客户端发送数据时,等待连接建立造成的发送延迟。因为 HTTP 的短连接性质,所以在上行数据传输过程中,需要等 TCP 连接建立才能发送 HTTP 报文。针对这一点,就 HTTP 协议来说,目前是没有解决方案的,HTTP 的 KeepAlive 特性可以缓解,但不能彻底解决。

第二点,服务器数据无法主动推送到端造成的下行数据延迟。这也是 HTTP 的短连接特性造成的。当服务器有下行数据时,并没有一个持续的有效连接能够让它把数据推下去,所以只能等待客户端来主动建立连接,顺道把下行数据带下去。

我们要介绍的方案就是围绕解决第二点展开的。市面上比较流行的前端基于 HTTP 协议构建的解决方案,主要有三种。

Comet 部分缓解了下行延迟问题。

HTTP + SSE 方案与 Comet 类似,只是把下行通道从 HTTP 请求变成了 SSE 实现。

SSE 的特性是长连接、单工协议,它的整体效果优于 Comet,因为没有额外的连接等待时间。它作为下行数据的通道,单工协议也完全符合要求。可以说 HTTP + SSE 的下行即时性基本是与 WebSocekt 等同的,基本解决了下行延迟问题。

Long-Pulling,定时向服务器去发送请求,以此来把下行数据带回来,基本属于常规操作,两个核心问题,基本一个也没有解决。

总结而言,HTTP + SSE > Comet > Long-Pulling

这个结论有一个前提,是抛开了兼容性的。

SSE 方案虽好,但仅限于浏览器,如果我们想把 JS 代码复用到其他环境比如小程序,该方案就无法实现了。目前各小程序 Runtime 对于 SSE 的支持几乎是 0。

安全性

我们看一下 OSI 参考模型,着重看一下应用层和传输层之间的部分,这里是 SSL/TLS 所处的位置,也就是我们常说的 HTTPS 中的那个 S。

 

OSI 模型中定义中,会话层负责两端的会话维持、身份鉴别等,表示层负责对数据的加解密。在此之上,应用层协议的安全性是等同的,HTTPS 和 WSS 协议就是安全版的 HTTP 和 WebSocket 协议。

TCP 是比 SSL/TLS 更底层的协议,所以直接经由 TCP 发送的数据是可以有更多的安全选择的,TLS 只是备选项之一。

使用 HTTPS 或 WSS 协议时,由 Runtime 提供 TLS/SSL 支持,开发者无需关注数据传输过程中的安全问题。使用 TCP 协议时,需要由开发者自行保证数据传输过程中的安全性(对接 TLS/SSL 或其他自定义安全方案)。

 


数据传输协议方案 & 前端持久化存储

数据传输协议方案

我们通过信息密度、拓展性、安全性、多端一致、兼容性五个指标来做对比数据传输协议。

除了我们并不推荐的自研方案,常用的数据传输协议有两种:Protocol Buffer(PB),TLV 格式二进制数据;JSON,纯文本键值对数据。

 

五个指标对比来看,信息密度指 A 向 B 传达信息需要消耗的流量,密度越高,消耗得越少,PB 的信息密度比 JSON 高

传输结构上,PB 是一个 TLV 格式二进制数据序列,JSON 是纯字符串系列键值对,JSON 描述数据结构要远大于 PB 描述数据结构。

安全性与可读性相反,我们要读一个二进制的 PB 数据,需要知道它序列化过程中的 PB 数据定义的结构。我们常说的 PB 文件定义的可读性文件,这是前后端的一个约定,基于这个文件我们才能去序列化和反序列化这个二进制数据。而 JSON 是纯字符串,可读性良好,可以比较直观去了解数据里的信息。

兼容性方面,JSON 有很多原生语言库可选,PB 的兼容性在前端来看也就是 JS 对 ArrayBuffer 的支持,目前也都是支持的

拓展性上,双方是等同的,PB 有一个优点,因为他传输的数据不包含键信息,所以两端的键信息可以不同。JSON 的传输信息包含键信息,意味着键信息是不可以随意变更的。

多端一致与兼容性一致,即时通讯场景涉及很多平台,多端就要去对数据做多端传输,数据的序列化和反序列化的实现的过程要保持一致,PB 跟 JSON 这方面都比较好,JSON 是原生支持的,PB 可以使用 Google 提供的相应三方库。

总之,PB 比 JSON 更优。

前端持久化存储对比

最后是前端的可持久化存储技术:LocalStorage、IndexDB 和 Sqlite。持久化存储本身可选方案不多,需要考虑容量、兼容性、数据一致性等方面。

  • LocalStorage,最常用的方案,容量比较低

  • IndexDB,浏览器器上唯一可用的持久化大容量存储方案

  • Sqlite - Electron Only,常用前端数据库

  • Sqlite - WebAssembly,研发成本比较高

除了 LocalStorage,其他三种容量相当。

数据一致性而言,除了 Electron 主进程内使用 Sqlite,其他三种方案都不太好处理数据竞争问题,很难保证数据一致性。在 Electron 平台下,把数据库操作放到主进程去完成,当渲染进程需要操控本地数据库时,依赖 IPC 去跟主进程通信,由主进程去处理真实的数据库事务。主进程是唯一一个数据库的访问点,因此可以妥善完成对数据竞争和数据一致性的保障。


融云的落地实践分享

经过三大项的方案对比,融云在落地的时候按照前面所分享的原则,在可用的方案下尽可能选择最优解,比如,Electron 平台上最优解就是 TCP+Sqlite ,数据封装用 PB。

Web 平台上没有 TCP+Sqlite 可用,就用 WebSocket 作为解决方案,把 HTTP 作为次优解做相应的技术落地。

小程序上,PB、WebSocket 都用不了,则采用 HTTP 方案备选,选型用的是 Comet。

这个降级过程对于集成的开发者来说是无感的,但在业务落地过程中是需要关注的,比如消息查询,有没有数据库就是两种应用体验。


http://www.ppmy.cn/news/246797.html

相关文章

鲍尔默:Skype将被整合进商业通讯产品Lync

来自国外媒体的报道,微软CEO史蒂夫•鲍尔默(Steve Ballmer)在今天的微软全球合作伙伴大会(Microsoft Partner Conference)上宣布,微软会将Skype整合到到商业通讯产品Lync中。鲍尔默表示Skype是连接Lync和外部世界的桥梁…

py飞机大战

飞机大战 学习来自:B站“麦叔编程” ​ --兴趣使人快乐! 源码 # coding:utf-8 打飞机ybt import pygame import random import mathfrom time import sleep # 初始化,必选项 pygame.init()#设置窗口大小 win pygame.display.set_mode((800…

Python-飞机大战(二)

本文主要介绍飞机大战的思路,代码会在文章末尾给出。 分为两个文件:plane_main和plane_spirite。 此游戏不是很难。在设计父类时,我将父类定义为一个抽象类,抽象方法是update(self)。 图片是在B站上黑马教程下面的链接中找到的。 …

【Python 飞机大战】

#背景,音乐等位置用a,b,c,d 表示,直接修改abcd对应的文件位置即可使用 # 与敌机相撞后会扣分,但未编写复活,或无敌,在扣分判定范围内持续扣分 #路径为绝对路径可自行更改为相对路径 …

飞机大战(Java)

飞机大战 游戏规则:游戏可以产生小的敌机、大的敌机、小蜜蜂,这三类都是随机概率出现的,游戏打开的时候,鼠标单击,游戏开始, 自动发射子弹,英雄机跟随鼠标移动,当鼠标移到窗口外时,游戏暂停,当鼠标又移回时,游戏继续,子弹打中敌机和小蜜 蜂,当生命降到0时,消失,敌机 撞击到英雄…

Python飞机大战(完整版)

简介:一共分为2个py文件,分别是主类、和精灵类 飞机大战图片地址:链接: https://pan.baidu.com/s/18T6n9JFIDxBqYX9CnHi7ZQ 密码: tqbr 注释:项目启动后如果报libpng warning: iCCP: known incorrect sRGB profile无须处理&#…

Python升级版飞机大战

Python升级版飞机大战,程序运行截图: 敌方共有大中小3款飞机,分为高中低三种速度; 子弹的射程并非全屏,而大概是屏幕长度的80%;消灭小飞机需要1发子弹,中飞机需要8发,大飞机需要20发子弹;每消灭一架小飞机得1000分,中飞机6000分,大飞机10000…

飞机大战一触即发

1:飞机的移动,发射子弹,手雷,生命值,生命条\n\n2:敌飞机有3种形态(小,中,大)不同的飞机大小不一样,生命值不一样,爆炸动画也不一样\n\…