猫耳 WebSocket 跨端优化实践

前言

在现代的移动应用程序中,长连接是一种不可或缺的能力,包括但不限于推送、实时通信、信令控制等常见场景。在猫耳FM的直播业务中,我们同样使用了 WebSocket 长连接作为我们实时通信的基础。

在我们推进用户体验优化的工作中,其中用户成功进入直播间的时间是我们优化的一个重点指标,其包含了房间信息接口的调用、长连接的建立、播放器拉流的首帧等。本文主要介绍我们在 WebSocket 长连接跨端统一和体验优化的思路和方案。

这里我们先简单介绍下 WebSocket,以及为什么我们选择了 WebSocket 而不是其他的协议作为我们持续迭代的方向。

WebSocket 是一种在 Web 应用程序和服务器之间建立持久、双向通信连接的通信协议。它允许客户端和服务器之间进行实时数据传输,而无需客户端不断地发起 HTTP 请求,为开发者提供了丰富的实时应用开发可能性。从最早猫耳 2016 年开始调研和迭代直播的业务,WebSocket 已经是一种相当成熟的方案,可以同时在 Web 和移动应用程序中使用。早在 2011 年 12 月 WebSocket 的协议标准被定稿 [RFC 6455](https://datatracker.ietf.org/doc/html/rfc6455),从 2012 年开始 WebSocket 逐渐被各大浏览器支持,包括当时的 Internet Explorer、Safari、Mozilla Firefox 和 Google Chrome 等。同时作为更早期诞生的 Socket.IO 为了兼容更早期的浏览器,在我们的场景下很多做法于这个时间点上就显得相当臃肿和没必要了。当时使用 WebSocket 的一个重点考虑我们还是可以同时在 Web 上直接使用,如果我们在客户端上引入其他的协议,在迭代过程中,我们就不得不考虑同时兼容和支持多个实时通信的通道,为了降低最初直播方案的复杂性,我们选择使用了 WebSocket 协议作为我们直播业务实时通信、信令传递的基础协议。

时间来到 2023 年前后,随着互联网技术的发展,我们当前又有了更多的选择作为实时消息传递的基础消息通道,类似 MQTT、gRPC 等上层协议其实都可以作为一个可靠的传递消息的方式,并且被大量的用户所验证。同时,在 Web 技术上也有类似 SSE、WebRTC、WebTransport 等方案可以作为我们上层消息传输的机制。作为一个普遍的考虑,这里我们的选择一定是一种“高级”协议,如果直接使用 TCP、UDP 等传输层协议,不可避免的我们要考虑更复杂的消息拆分、可靠性保证和安全等问题,这里我们秉持着多端统一、提高研发效率、减少维护成本的考虑,我们最后的选择尽可能还是一种相对成熟和可靠的方案,在这样的前提下,MQTT、gRPC 由于在 Web 上支持不佳或是本身就通过 WebSocket 包装的,我们就暂时不做深入讨论。同时,一些较新的 Web 方案,对比之下类似 SSE 是单向传输、WebRTC 更适合实时音视频通信的场景且不太能被 CDN 加速支持、WebTransport 太过于新,Safari 到现在为止还不支持,同时在消息顺序上需要外部进行额外保证,这样看下来 WebSocket 仍是我们当前最好的选择。

技术方案

在考虑优化之前,由于我们之前在 Android 和 iOS 客户端上也是使用的是 WebSocket (wss://),首先需要明确的就是当前有什么问题。

根据我们的埋点的数据,在我们直连国内 BGP 线路服务器建连的情况下,完成 WebSocket 握手的时长(90 分位,下文同)为 500ms ~ 600ms,DNS + TCP + TLS + HTTP Upgrade,相比于我们一次正常的 https 请求取得响应的时间明显慢了很多,这个也是我们优化的一个最终目标,由于 WebSocket 在 HTTP/1.1 上不可避免的需要重新建立 HTTP 连接并进行一次 Upgrade,目前可以做的优化相对有限,可能在 DNS 和 TLS 过程中可以有一些优化。其实我们早期还尝试通过订阅、取消订阅指令来直接复用同一个连接,但后来为了方便做负载均衡以及高可用性等原因线上已经不再支持这块逻辑。

要做好这次优化,我们这边有几点考虑:

  1. 统一多端代码,在日志中能输出有效和明确的错误信息

  2. 支持自定义 DNS 过程,包括 httpdns、DNS 缓存的策略和 ipv6 的策略等

  3. 支持 http proxy,方便测试调试

  4. 后续迭代方便,可以持续跟进支持 WebSocket 协议标准

在考虑多端统一并且成熟可靠的选择不多,我们主要对比下我们使用过的一些可以跨端的方案,这里主要提使用过的原因是我们能大致理解其核心的逻辑并作出一定的修改:

  • [websocketpp](https://github.com/zaphoyd/websocketpp)一个支持 client/server 的库,我们主要用于 PC 上建立 server 和 Web 进行本地交互

  • [libwebsockets](https://github.com/warmcat/libwebsockets)在用户连麦等场景中曾作为和服务端的进行信令传递的方案

  • [cronet](https://chromium.googlesource.com/chromium/src/+/HEAD/components/cronet/)来自 Chromium 的网络栈,基本上可以认为是 Google Chrome 浏览器中负责网络通信部分的上层封装

    这里特别提到 cronet 的原因是它虽然不直接支持 WebSocket,但是其代码库完整包含了 WebSocket 协议的实现,同时也是目前我们在客户端上已经使用中的网络库的底层实现

由于 websocketpp 已经很久没更新了,我们这里主要对比下我们目前 libwebsockets 和 cronet 实现的方案要考虑的一些问题:

主要代码语言DNS系统 Wi-Fi http proxy 设置接入成本持续迭代
libwebsocketsC目前仅支持简单的策略,需要修改代码进行完整的 DNS 逻辑控制外部控制设置Android 上需要单独实现 JNI 层,支持 Java/Kotlin 中调用。同时自定义 DNS 过程都在持续迭代中,支持较新的标准
cronetC++ / iOS Objective-C / Android Java JNI已修改,并在线上稳定运行
支持国内的各种商用 httpdns 服务、海外 Google DoH 等
内部集成支持需要实现 cronet 适配层代码,代码生成相关接入层代码

通过简单的对比,其实我们更倾向使用 cronet 作为持续后续迭代的方案,不仅更适合我们在移动端中集成,且有足够的经验来优化它。同时,在 Android 上我们将逻辑的代码合入 native 层中,还能进一步优化网络 IO、TLS 等关键性能。

这里特别要提到我们之前使用的 cronet 方案是来自B站移动端基础架构优化过的修改版本,在一些特定的场景给予了我们业务更高的自由度,比如请求优先级等。常见的在 iOS 上原生支持自定义 DNS 过程其实都是一个比较取巧的方案,特别是在处理 https 请求的一些过程上,我们是用了 cronet 之后,才考虑 iOS 上也支持这块。由于这里我们是使用的修改版 cronet 方案,这些问题已经都被优化的足够好了,我们仅需要考虑如何启用其中 WebSocket 的部分并提供给客户端使用。另外需要考虑的一点是 Android 上的 cronet 实际上它的 Java 层的初始化的配置和 native 层中使用的 Cronet C API 接口实际上不能直接兼容,这块其实是在更早猫耳 Android 落地播放器使用 cronet 的作为 http 传输方案的时候已经得到了解决,并用于实验不同协议的播放效果,参见 [猫耳 Android 播放框架开发实践],而我们 iOS 和 PC 上直接使用的就是 Cronet C API 的包装,这样保证了多端全局的配置、埋点信息都是一致的且最大化了网络的性能和端上可观测的能力。

确定了方向,我们其实要做的事情也很简单,因为在 Chromium 中 WebSocket 在某种程度上也是基于 http 已有过程的一种延伸,之前很多已有的优化方案可以直接对 WebSocket 生效,包括自定义 DNS 过程等。

这里我们仅将 `net/websockets` 中相关逻辑对客户端进行了适配:

  • 支持主动发送 ping 包。Web 标准中的 WebSocket 实现实际上没有 ping 的动作,只能被动响应服务端的 ping 包

  • 移除 Origin 的请求头和相关校验。Web 标准中的 WebSocket 相对于普通的 http 请求有一些源站策略的差别,这里我们在客户端和 native 中实际上使用不到

增加 cronet 适配层代码,这里简单贴下 native 接口 idl,Android 中 jni 的适配也是依样画葫芦:

// Counterpart of UrlRequestCallback for websocket.
[Abstract]
interface WebSocketCallback {/*** The message type invoked by {@code OnMessage()}.* 目前只能收到 text 或者 binary 两种. continuation 已被合并处理.*/enum MESSAGE_TYPE {CONTINUATION = 0,TEXT = 1,BINARY = 2,};OnAddChannelResponse(WebSocket request, UrlResponseInfo info, string extensions);OnMessage(WebSocket request, MESSAGE_TYPE type, Buffer buffer);OnDropChannel(WebSocket request, bool was_clean, uint16 code, string reason);OnFailed(WebSocket request, Error error);OnCanceled(WebSocket request);
};/*** Controls an Websocket request.* Initialized by InitWithParams().* Note: All methods must be called on the Executor passed to InitWithParams().*/
interface WebSocket {// see https://www.iana.org/assignments/websocket/websocket.xhtml#opcode.enum OPCODE {CONTINUATION_FRAME = 0,TEXT_FRAME = 1,BINARY_FRAME = 2,CONNECTION_CLOSE_FRAME = 8,PING_FRAME = 9,PONG_FRAME = 10,};[Sync]InitWithParams(Engine engine,string url,WebSocketParams params,WebSocketCallback callback,Executor executor) => (RESULT result);[Sync]Start() => (RESULT result);[Sync]ReadFrames() => (RESULT result);[Sync]SendFrame(bool fin, OPCODE op_code, Buffer buffer) => (RESULT result);[Sync]Send(OPCODE op_code, Buffer buffer) => (RESULT result);[Sync]Close(uint16 code, string reason) => (RESULT result);Cancel();[Sync]IsDone() => (bool done);
};// 请求参数.
struct WebSocketParams {/*** Array of HTTP headers for this request.*/array<HttpHeader> request_headers;
};

其中大部分的通用的参数都可以复用已有的 CronetURLRequest 的部分。

整体的架构和调用时机大致是这样的,关系图中有标星的位置是我们这次进行新加或调整的地方:

图片

调用时机:

图片

同时,我们对 WebSocket 的消息在内部进行了处理,由于协议支持消息是分片传输,为了简化业务上消息处理的过程,将消息做了合并,最终一起回调给上层业务处理。

最后在端上再进行一次封装,已有的重试、心跳等机制这里就不进行讨论了,我们将端上业务中的和 native 代码中 libwebsockets 的 WebSocket 都换成了 cronet 的实现,最终上线后收获了非常可观的收益:

在建连速度方面,Android 优化了 ~150ms,iOS 优化了 ~250ms,其中我们 Android 端上早在之前就已经落地通过 httpdns 的方式(包括 DNS 的缓存策略的优化等)优化 okhttp 建连的过程,iOS 之前的 WebSocket 实现 [SocketRocket](https://github.com/facebookincubator/SocketRocket) 依赖系统的 DNS,建连时长相比于 Android 会慢 50ms+,现在也和普通 HTTP 请求统一了 DNS 的策略,收益会更明显点,符合我们的预期,最终也是补完了我们端上业务中最后一块不支持自定义 DNS 过程的缺口。在失败率方面,也和之前基本上一致,且上报上来的错误信息更具有可读性,后续进行分析和监控都将更加清晰。下图展示了不同版本间客户端 WebSocket 连接失败的错误信息,这里我们从 6.1.0 版本开始切换到 cronet 的实现,可以看到新版本的信息更加直观和明确,极大地方便了研发人员定位具体问题。

图片

随着 WebSocket 的实现切换为 cronet,在我们客户端本身的网络诊断中也可以更加完整和准确反馈实际的 DNS 结果、错误信息和网络质量等信息。

未来展望

前阵子 Node.js 22 的发布,其原生支持的 WebSocket 客户端也旨在提供一个更加标准化的接口和使用的方式,说明了当前 WebSocket 仍能作为一种足够优秀的方案被广大开发者所认可。

值得一提的是,随着互联网技术的发展,WebSocket 也不是一成不变,WebSocket over HTTP/2 [RFC 8441](https://www.rfc-editor.org/rfc/rfc8441)和 WebSocket over HTTP/3 [RFC 9220](https://www.rfc-editor.org/rfc/rfc9220.html) 也被相继定稿,其中 WebSocket over HTTP/2 也开始逐渐被各大浏览器、网关、开源库支持,相对于 HTTP/1.1 中的 Upgrade 机制,在 HTTP/2 和 HTTP/3 中 WebSocket 的握手有了相当程度的简化,换句话说就是可以更快。目前由于支持 WebSocket over HTTP/2 的服务端组件还不够广泛,暂时没有在用户侧进行验证,不过我们接入的 cronet 版本已经支持,仅需要先有一次 HTTP/2 的连接,确认服务端支持后就可以正常启用,在我们的测试环境中也可以观测到更好的效果。除此之外,由于我们使用的修改后的 cronet 也支持配置下发域名通配符等方式强制使用 QUIC 通过 UDP 进行传输而无需与普通 TCP 的 HTTP 请求进行竞速,目前仅用于在播放和下载场景中做一些实验,后续也有望在 WebSocket over HTTP/3 的演进中得到更可观收益。

-End-

作者丨腾袭、三七、nazimai、叔于田、阿司、浩哥

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/842844.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

IC开发——Ubuntu安装VCS2018

1. 简介 VCS是一种常用的Verilog仿真和综合工具&#xff0c;由Synopsys公司开发。它提供了一个完整的设计验证环境&#xff0c;用于验证硬件设计的正确性和性能。以下是VCS工具的一些主要特点和功能&#xff1a; 仿真功能&#xff1a;VCS支持基于事件驱动的数字电路级仿真&am…

【Linux命令】--- Linux下的分卷压缩与解压

在编程的艺术世界里&#xff0c;代码和灵感需要寻找到最佳的交融点&#xff0c;才能打造出令人为之惊叹的作品。而在这座秋知叶i博客的殿堂里&#xff0c;我们将共同追寻这种完美结合&#xff0c;为未来的世界留下属于我们的独特印记。 【Linux命令】--- 多核压缩命令大全&…

markdown画时序图的时候,如何自动显示每一条时序的序号

1: 现象描述 今天画时序图的时候&#xff0c;发现时序上面没有显示序号&#xff0c;看起来不够清晰&#xff0c;只有单纯的说明; 如下图所示 刚测试CSDN的时序图&#xff0c;默认是带序号的&#xff0c;看起来和实际使用的markdown工具有关系&#xff1b; 2&#xff1a;解决办…

XPosed项目的接入、模版制作、改名全过程

XPosed项目的接入、模版制作、改名全过程 写在前面 之前写过这篇Xposed Hook 过登录密码验证配置开发Xposed项目的文章&#xff0c;这次的接入使用的是当前最新版Android Studio&#xff0c;接入稍微有些差别&#xff0c;也记录下。 本篇文章主要是写关于XP项目接入、制作XP模…

两年前的微信聊天记录能恢复吗?正确答案在这里(全)

微信已经成为我们日常沟通中不可或缺的一部分&#xff0c;承载着无数重要的对话和回忆。然而&#xff0c;面对手机更换、系统升级或意外删除等情况&#xff0c;许多人不禁要问&#xff1a;两年前的微信聊天记录能恢复吗&#xff1f;这个问题的答案并不简单&#xff0c;因为能否…

WGCLOUD部署好后,怎么登录WGCLOUD界面

WGCLOUD的server启动完成后&#xff0c;我们在浏览器里输入URL&#xff0c;如下 http://[server主机IP]:9999 注意默认端口就是9999&#xff0c;如果修改过&#xff0c;那么把端口改成自己的实际端口 这样就可以看到登录页面了&#xff0c;默认账号密码是&#xff1a;admin/…

辅助科技照亮道路,携手共促盲文书写技能新飞跃

在这个科技日新月异的时代&#xff0c;创新的力量正以前所未有的方式融入我们的日常生活&#xff0c;特别是对于视觉障碍群体而言&#xff0c;技术的每一次进步都是通往更加独立生活的桥梁。今天&#xff0c;让我们聚焦于一款名为“蝙蝠避障”的辅助软件&#xff0c;它不仅为盲…

探索数字规律与数组操作

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、问题描述与需求概述 二、数字规律的理解 1. 观察数字模式 2. 思考生成方法 三、实现…

Ubuntu24.04安装tabby-terminal-1.0.207并处理依赖

1 下载 tabby-terminal-1.0.207 地址&#xff1a; https://github.com/Eugeny/tabby/releases 点击show all 36 assets 选择 tabby-1.0.207-linux-x64.deb 并下载。 2 依赖下载 gconf2_3.2.6-3ubuntu6_amd64.deb gconf2-common_3.2.6-3ubuntu6_all.deb gconf-service_3.2.6-…

怎么挑选骨传导耳机?精选六大选购技巧教你如何挑选

过去的两年里&#xff0c;骨传导耳机逐渐被大众的所熟知。可能毕竟长时间使用音量过大的传统入耳式耳机&#xff0c;多多少少会对我们的听力健康构成威胁。所以很多人就想找一款不伤耳朵的耳机。然后就了解到了骨传导耳机&#xff0c;所以就会延伸出这些问题——骨传导耳机好用…

vue3学习(二)

前言 上一篇分享了vue的基础指令&#xff0c;这篇记录下vue3的核心内容&#xff0c;也是自己的学习笔记&#xff0c;可能有些核心还不全&#xff0c;大佬请略过。 一、核心内容 分享这个之前&#xff0c;先声明下&#xff0c;我这里是用的脚手架的写法&#xff0c;分享的讲解截…

Springboot项目——网页版本五子棋

网页五子棋&#xff1a;本项目简单实现了网页版本的五子棋对战功能&#xff0c;同时会根据用户的天梯分数来匹配&#xff0c;可供多位用户同时提供对战功能。大致可分为三个模块&#xff0c;用户模块&#xff0c;匹配模块&#xff0c;对战模块&#xff0c;下面重点介绍以下三个…

腾盾科创无人机亮相第二十四届新疆农机博览会引发观展热潮

5月25日&#xff0c;第二十四届新疆农业机械博览会、2024“一带一路”智慧农业大会&#xff08;以下简称新疆农机博览会&#xff09;在新疆国际会展中心开幕。展会现场多种农牧业的新装备、新技术集中亮相&#xff0c;其中首次在新疆农机博览会上亮相的腾盾科创无人机产品引发观…

C语言——基于stm32G030的温湿度传感器项目实验

一、功能要求&#xff1a; 设备自检功能&#xff1a; 设备上电自检&#xff08;检查传感器采集是否正常&#xff0c; DHT11有存在响应&#xff0c; 可以自检使用&#xff0c; &#xff09;自检通过后&#xff0c;由串口打印设备状态信息。 自动控制功能&#xff1a; 进入自动控…

Clickhouse MergeTree 存储引擎架构总结——Clickhouse 架构篇(二)

文章目录 前言MergeTree存储引擎的三大特点MergeTree 的数据组织MergeTree的文件组织数据文件、元数据文件、索引文件和其他文件分区数据库和表 索引与事务数据库存储引擎的对比存储引擎如何影响查询速度MergeTree存储引擎的工作过程 前言 存储引擎是ClickHouse非常重要的一个…

小易大数据:大数据报告查询领域的黑马,这些优势让你无法忽视!

随着大数据技术被运用到各行各业&#xff0c;风控领域也不例外&#xff0c;形成了基于大数据技术的大数据信用&#xff0c;也就是我们常说的大数据报告或者网贷大数据&#xff0c;在众多的查询平台中&#xff0c;小易大数据平台在市面上是比较受欢迎的&#xff0c;那在小易平台…

windows内存管理

一 windows系统的内存管理涉及哪些 1.1 虚拟内存管理机制 windows操作系统使用虚拟内存技术&#xff0c;将磁盘文件&#xff0c;通过映射对象&#xff08;存储在物理内存&#xff09;关联&#xff0c;映射到虚拟内存作为文件试图。即用户操作"虚拟内存中File View Objec…

C-数据结构-树状存储基本概念

‘’’ 树状存储基本概念 深度&#xff08;层数&#xff09; 度&#xff08;子树个数&#xff09; 叶子 孩子 兄弟 堂兄弟 二叉树&#xff1a; 满二叉树&#xff1a; 完全二叉树&#xff1a; 存储&#xff1a;顺序&#xff0c;链式 树的遍历&#xff1a;按层遍历&#xff0…

Kibana(一张图片胜过千万行日志)

Kibana&#xff08;一张图片胜过千万行日志&#xff09; Kibana是一个开源的分析和可视化平台&#xff0c;设计用于和Elasticsearch一起工作。 你用Kibana来搜索&#xff0c;查看&#xff0c;并和存储在Elasticsearch索引中的数据进行交互。 你可以轻松地执行高级数据分析&a…

LangChain 0.2 - 基于 SQL 数据构建问答系统

本文翻译整理自&#xff1a;Build a Question/Answering system over SQL data https://python.langchain.com/v0.2/docs/tutorials/sql_qa/ 文章目录 一、项目说明⚠️ 安全说明⚠️架构 二、设置三、Chains1、将问题转换为 SQL查询2、执行 SQL查询3、回答问题 四、Agents1、S…