为什么WebSocket需要前端心跳检测,有没有原生的检测机制?

这篇具有很好参考价值的文章主要介绍了为什么WebSocket需要前端心跳检测,有没有原生的检测机制?。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

本文代码 github、gitee、npm

在web应用中,WebSocket是很常用的技术。通过浏览器的WebSocket构造函数就可以建立一个WebSocket连接。但当需要应用在具体项目中时,几乎都会进行心跳检测。

设置心跳检测,一是让通讯双方确认对方依旧活跃,二是浏览器端及时检测当前网络线路可用性,保证消息推送的及时性。

你可能会想,WebSocket那么简陋的吗,居然不能自己判断连接状态?在了解前先来回顾一下计算机网络知识。

相关的网络知识

TCP/IP协议族四层结构:

  • 应用层:决定了向用户提供应用服务时通信的活动。HTTP、FTP、WebSocket都是应用层协议

  • (TCP)传输控制层:控制网络中两台主机的数据传输:将应用层数据(有必要时对应用层报文分段,例如一个完整的HTTP报文进行分段)发送到目标主机的特定端口的应用程序。给每个数据标记源端口、目标端口、分段后的序号。

  • (IP)网络层:将IP地址映射为目标主机的MAC地址,然后将TCP数据包(有必要时对数据分片)加入源IP、目标IP等信息后经过链路层扔到网络上让其找到目标主机。

  • 链路层:为IP网络层进行发送、接收数据报。将二进制数据包与在网线传输的网络电信号进行相互转换。

TCP是可靠的连接,握手建立连接后,发送方每发送一个TCP报文(对应用层报文分段后形成多个TCP报文),都会期望对方在指定时间里返回已收到的确认消息,如果超时没有回应,会重复发送,确保所有TCP报文可以到达对方,被对方按顺序拼接成应用层需要的完整报文。

WebSocket协议支持在TCP 上层引入 TLS 层,建立加密通信。

WebSocket与HTTP的异同:

  • WebSocket和HTTP一样是应用层协议,在传输层使用了TCP协议,都是可靠的连接。WebSocket在建立连接时,可以使用已有的HTTP的GET请求进行握手:客户端在请求头中将WebSocket协议版本等信息发生到服务器,服务器同意的话,会响应一个101的状态码。就是说一次HTTP请求和响应,即可轻松转换协议到WebSocket。

  • WebSocket可以互相发起请求。当有新消息时,服务器主动通知客户端,无需客户端主动向服务器询问。客户端也可以向后端发送消息。而HTTP中请求只能由客户端发起。

  • WebSocket是HTML5的内容,HTTP则是超文本传输协议,比HTML5诞生更早。

  • 在应用层,WebSocket的每个报文(在WebSocket中叫数据帧)会比HTTP报文(必须包含请求行、请求头、请求数据)更轻量。

    • WebSocket每个数据帧只有固定的头信息,不会有cookie等或者自定义的头信息。建立通讯后是一对一的,不需要携带验证信息。使用HTTP握手时,的握手请求会自动携带cookie。
    • WebSocket在应用层就会将大的数据进行分拆,而HTTP不会。

WebSocket与与WebRTC的异同:

  • WebRTC是一种通讯技术,由谷歌发起,被广大浏览器实现。用来建立浏览器和浏览器间的通讯,如视频通话等。而WebSocket是一种经过抽象的协议,可以实现为通讯技术。用来建立浏览器和服务器间的通讯。

协议中的心跳检测机制

从网上检索的答案,WebSocket大概有两种从协议角度出发的,检测对方存活的方式:

  1. WebSocket只是一个应用层协议规范,其传输层是TCP,而TCP为长连接提供KeepAlive机制,可以定时发送心跳报文确认对方的存活,但一般是服务器端使用。因为是TCP传输控制层的机制,具体的实现要看操作系统,也就是说应用层接收到的连接状态是操作系统通知的,不同操作系统的资源调度是不一样的,例如何时发送探测报文(不包含有效数据的TCP报文)检测对方的存活,频率是多久,在不同的系统配置下存在差异。可能是2小时进行一次心跳检测,或许更短。如果连续没有收到对方的应答包,才会通知应用层已经断开连接。这就带来了不确定性。同时也意味着其它依赖该机制的应用层协议也会被影响。也就是说要利用这个过程进行检测,客户端要修改操作系统的TCP配置才行,在浏览器环境显然不行。参考1、参考2

  2. WebSocket协议也有自身的保活机制,但需要通讯双方的实现。WebSocket通讯的数据帧会有一个4位的OPCODE,标记当前传输的数据帧类型,例如:0x8表示关闭帧、0x9表示ping帧、0xA表示pong帧、0x1普通文本数据帧等。www.rfc-editor.org

    • 关闭数据帧,在任意一方要关闭通道时,发送给对方。例如浏览器的WebSocket实例调用close时,就会发送一个OPCODE为连接关闭的数据帧给服务器端,服务器端接收到后同样需要返回一个关闭数据帧,然后关闭底层的TCP连接。
    • ping数据帧,用于发送方询问对方是否存活,也就是心跳检测包。目前只有后端可以控制ping数据帧的发送。但浏览器端的WebSocket实例上没有对应的api可用。
    • pong数据帧,当WebSocket通讯一方接收到对方发送的ping数据帧后,需要及时回复一个内容一致,且OPCODE标记为pong的数据帧,告诉对方我还在。但目前回复pong是浏览器的自动行为,意味着不同浏览器会有差异。而且在js中没有相关api可以控制。

综上所述,探测对方存活的方式都是服务器主动进行心跳检测。浏览器并没有提供相关能力。为了能够在浏览器端实时探测后端的存活,或者说连接依旧可用,只能自己实现心跳检测。

浏览器端心跳检测的必要性

首先我们先了解一下,目前的浏览器端的WebSocket何时会自动关闭WebSocket,并触发close事件呢?

  • 握手时的WebSocket地址不可用。
  • 其它未知错误。
  • 正常连接状态下,接收到服务器端的关闭帧就会触发关闭回调。

也就是说建立正常连接后,中途浏览器端断网了,或者服务器没有发送关闭帧就关了连接,总之就是在连接无法再使用的情况下,浏览器没有接收到关闭帧,浏览器则会长时间保持连接状态。此时业务代码不去主动探测的话,是无法感知的。

另外通讯双方保持连接意味着需要长时间占用对方的资源。对于服务器端来说资源是非常宝贵的。长时间不活跃的连接,可能会被服务器应用层框架"优化"释放掉。

前端实现心跳检测

实例化一个WebSocket:

function connectWS() {
    const WS = new WebSocket("ws://127.0.0.1:7070/ws/?name=greaclar");
    // WebSocket实例上的事件

    // 当连接成功打开
    WS.addEventListener('open', () => {
        console.log('ws连接成功');
    });
    // 监听后端的推送消息
    WS.addEventListener('message', (event) => {
        console.log('ws收到消息', event.data);
    });
    // 监听后端的关闭消息,如果发送意外错误,这里也会触发
    WS.addEventListener('close', () => {
        console.log('ws连接关闭');
    });
    // 监听WS的意外错误消息
    WS.addEventListener('error', (error) => {
        console.log('ws出错', error);
    });
    return WS;
}

let WS = connectWS();

心跳检测需要用到的实例方法:

// 发送消息,用来发送心跳包
WS.send('hello'); 
// 关闭连接,当发送心跳包不响应,需要重连时,最好先关闭
WS.close();

定义发送心跳包的逻辑:

准备

  • 申请一个变量heartbeatStatus,记录当前心跳检测状态,有三个状态:等待中,已收到应答、超时。
  • 监听WS实例的message事件,监听到就将heartbeatStatus改为:已收到应答。
  • 监听WS实例的open事件,打开后启动心跳检测。

检测

  • 启动一个定时器A。

  • 定时器A执行,1.修改当前状态heartbeatStatus为等待中;2.发送心跳包;3.启动一个定时器B。

    • 发送心跳包后,后端需要立刻推送一个内容一样的心跳应答包给前端,触发前端WS实例的message事件,继而将heartbeatStatus改为已收到应答。
  • 定时器B执行,检测当前heartbeatStatus状态:

    • 如果是已收到应答,证明定时器A执行后,服务器可以及时响应数据。继续启动定时器A,然后不断循环。

    • 如果是等待中,证明连接出现问题了,走关闭或者检测流程。

let WS = connectWS();
let heartbeatStatus = 'waiting';

WS.addEventListener('open', () => {
    // 启动成功后开启心跳检测
    startHeartbeat()
})

WS.addEventListener('message', (event) => {
    const { data } = event;
    console.log('心跳应答了,要把状态改为已收到应答', data);
    if (data === '"heartbeat"') {
        heartbeatStatus = 'received';
    }
})

function startHeartbeat() {
    setTimeout(() => {
        // 将状态改为等待应答,并发送心跳包
        heartbeatStatus = 'waiting';
        WS.send('heartbeat');
        // 启动定时任务来检测刚才服务器有没有应答
        waitHeartbeat();
    }, 1500)
}

function waitHeartbeat() {
    setTimeout(() => {
        console.log('检测服务器有没有应答过心跳包,当前状态', heartbeatStatus);
        if (heartbeatStatus === 'waiting') {
            // 心跳应答超时
            WS.close();
        } else {
            // 启动下一轮心跳检测
            startHeartbeat();
        }
    }, 1500)
}

优化心跳检测

心跳检测异常,但close事件没有触发,大概率是双方之间的网络线路不佳,如果立马进行重连,会挤兑更多的网络资源,重连的失败概率更大,也可能阻塞用户的其它操作。

但也不排除确实是连接的问题,如服务器宕机、意外重启,同时没有告知浏览器需要把旧连接关闭。

所以一发生心跳不应答,个人推荐的做法是,发生延迟后,提醒用户网络异常正在修复中,让用户有个心理准备。然后多发一两个心跳包,连续不应答再提示用户掉线了,是否重连。如果中途正常了,就不需要重连,用户体验更好,对服务器的压力也更小。

// 以上代码需要修改的地方

// 添加一个变量来记录连续不应答次数
let retryCount = 0WS.addEventListener('message', (event) => {
    const { data } = event;
    console.log('心跳应答了,要把状态改为已收到应答', data);
    if (data === '"heartbeat"') {
        // 复位连续不应答次数
        retryCount = 0;
        heartbeatStatus = 'received';
    }
})

// 在等待应答的函数中添加重试的逻辑
function waitHeartbeat() {
    setTimeout(() => {
        // 心跳应答正常,启动下一轮心跳检测
        if (heartbeatStatus === 'received') {
            return startHeartbeat();
        }
        // 更新超时次数
        retryCount ++;
        // 心跳应答超时,但没有连续超过三次
        if (retryCount < 3) {
            alert('ws线路异常,正在检测中。')
            return startHeartbeat();
        }
        
        // 超时次数超过三次
        WS.close();
    }, 1500)
}

最后,为了方便大家共同进步,本文已经把相关的逻辑封装为一个类,并且在npm中可下载玩一下,也已经开源到github上。文章来源地址https://www.toymoban.com/news/detail-780375.html

到了这里,关于为什么WebSocket需要前端心跳检测,有没有原生的检测机制?的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 为什么需要超时控制

    本文将介绍为什么需要超时控制,然后详细介绍Go语言中实现超时控制的方法。其中,我们将讨论 time 包和 context 包实现超时控制的具体方式,并说明两者的适用场景,以便在程序中以更合适的方式来实现超时控制,提高程序的稳定性和可靠性。 超时控制可以帮助我们避免程

    2024年02月03日
    浏览(57)
  • 为什么需要单元测试?

    为什么需要单元测试? 从产品角度而言,常规的功能测试、系统测试都是站在产品局部或全局功能进行测试,能够很好地与用户的需要相结合,但是缺乏了对产品研发细节(特别是代码细节的理解)。 从测试人员角度而言,功能测试和系统测试以及其他性能测试等等对测试

    2024年02月12日
    浏览(69)
  • 为什么需要uboot?

    bootROM: 一种固化在芯片内部的只读存储器(ROM),用于启动和初始化系统。BootROM 中通常包含了一些预先编写好的代码,用于完成系统启动前的基本初始化和配置, 例如初始化时钟、GPIO控制器、中断控制器、存储设备(SD卡、NAND Flash、SPicy Flash)等硬件资源, 检测启动设备

    2023年04月23日
    浏览(63)
  • uniapp前端+python后端=微信小程序支付到底怎么开发???国内的资料为什么没一篇能讲清楚,简简单单的只需要3步就可以了-V2版本

    真的,在接到这个任务的时候,本以为很简单,不就是普通的浏览器复制粘贴,最不济找下gpt给生成一下,但是到实际开发就不同了,不是后端出问题就是前端,搜资料,上百度上google,基本每一个人讲的都不一样,不是这问题就是那问题,特别是微信官方,自己接口的逻辑

    2024年01月22日
    浏览(54)
  • 为什么需要对相机标定?

    以下内容来自系统教程如何搞定单目/鱼眼/双目/阵列 相机标定? 点击领取相机标定资料和代码 为什么需要对相机标定? 我们所处的世界是三维的,而相机拍摄的照片却是二维的,丢失了其中距离/深度的信息。从数学上可以简单理解为,相机本身类似一个映射函数,其将输

    2024年02月06日
    浏览(55)
  • 为什么自动驾驶需要5G?

    什么叫自动驾驶? 自动驾驶分为6个等级: Level 0: 人工驾驶,无驾驶辅助系统,仅提醒。 Level 1: 辅助人工驾驶,可实现单一的车速或转向控制自动化,仍由人工驾驶(如定速巡航、ACC)。 Level 2: 部分自动驾驶,可实现车速和转向控制自动化,驾驶员必须始终保持监控(

    2024年02月08日
    浏览(65)
  • 为什么CPU需要时钟

    为什么CPU需要时钟这样一个概念? 什么是时钟脉冲,CPU为什么需要时钟,时钟信号是怎么产生的? 上面这个图的方波就是一个脉冲,类比于人类的脉搏跳动。一个脉冲称之为CPU的一个 时钟信号 ,或者 时钟脉冲 。一个脉冲周期就叫CPU时钟周期,一个时钟周期内时钟信号震荡一

    2023年04月11日
    浏览(53)
  • 爬虫时为什么需要代理?

    我们都知道爬虫时是需要代理地址介入的。使用代理可以隐藏你的真实IP地址,防止被网站封禁或限制访问。此外,代理还可以帮助你绕过地理限制,访问被封锁的网站或服务。但是请注意,使用代理也可能会带来一些风险,例如代理服务器可能会记录你的访问数据,或者代

    2024年02月06日
    浏览(55)
  • 爬虫为什么需要ip

    爬虫需要使用爬虫ip主要是为了解决以下问题: 1、反爬虫机制:许多网站会设置反爬虫机制来防止爬虫程序的访问,例如限制IP地址的访问频率、检测访问来源等。使用爬虫ip可以绕过这些限制,使得爬虫程序更难被检测到。 2、访问限制:有些网站可能会对某些地区的IP地址

    2024年02月02日
    浏览(59)
  • 什么是分库分表?为什么需要分表?什么时候分库分表

    不急于上手实战  ShardingSphere  框架,先来复习下分库分表的基础概念,技术名词大多晦涩难懂,不要死记硬背理解最重要,当你捅破那层窗户纸,发现其实它也就那么回事。 分库分表是在海量数据下,由于单库、表数据量过大,导致数据库性能持续下降的问题,演变出的技

    2023年04月26日
    浏览(146)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包