websocket 发送ping_那些很重要,但是不常用的技术,websocket

1. 为什么会有websocket

2. websocket协议格式

3. 协议具体实现

一、为什么需要 WebSocket?

初次接触 WebSocket 的人,都会问同样的问题:我们已经有了 HTTP 协议,为什么还需要另一个协议?它能带来什么好处?

答案很简单,因为 HTTP 协议有一个缺陷:通信只能由客户端发起

举例来说,我们想了解今天的天气,只能是客户端向服务器发出请求,服务器返回查询结果。HTTP协议做不到服务器主动向客户端推送信息。

这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。我们只能使用"轮询":每隔一段时候,就发出一个询问,了解服务器有没有新的信息。最典型的场景就是聊天室。

轮询的效率低,非常浪费资源(因为必须不停连接,或者HTTP连接始终打开)。因此,工程师们一直在思考,有没有更好的方法。WebSocket就是这样发明的

WebSocket协议在2008年诞生,2011年成为国际标准。所有浏览器都已经支持了。

它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。

其他特点包括:

1)建立在 TCP 协议之上,服务器端的实现比较容易。

(2)与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。

(3)数据格式比较轻量,性能开销小,通信高效。

(4)可以发送文本,也可以发送二进制数据。

(5)没有同源限制,客户端可以与任意服务器通信。

(6)协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。

websocket协议格式

Browser已经支持http协议,为什么还要开发一种新的WebSocket协议呢?我们知道http协议是一种单向的网络协议,在建立连接后,它只允许Browser/UA(UserAgent)向WebServer发出请求资源后,WebServer才能返回相应的数据。而WebServer不能主动的推送数据给Browser/UA,当初这么设计http协议也是有原因的,假设WebServer能主动的推送数据给Browser/UA,那Browser/UA就太容易受到攻击,一些广告商也会主动的把一些广告信息在不经意间强行的传输给客户端,这不能不说是一个灾难。那么单向的http协议给现在的网站或Web应用程序开发带来了哪些问题呢?

一条连接上只可以发送一个请求
请求只能从客户端开始。客户端不可以接收除了响应以外的指令。
请求 / 响应首部未经过压缩就直接进行传输。首部的信息越多,那么延迟就越大。
发送冗长的首部。每次互相发送相同的首部造成的浪费越多
可以任意选择数据压缩格式。非强制压缩发送
ajax轮询

ajax(异步的javascript与xml技术)是一种有效利用javascript和dom的操作,以达到局部web页面的提花和加载的异步通信手段。和以前的同步通信相比,他只更新一部分页面,相应中传输饿数据量会因此的减少。

ajax轮询的原理是,让浏览器每隔一段时间就发送一次请求,询问服务器是否有新消息。
而利用ajax实时的从服务器获取内容,有可能导致大量的请求产生。


长轮询

原理和ajax轮询差不多,都是采用轮询的方式,不过采用的是阻塞模型。也就是说,当客户端发起连接后,如果服务器端内容没有更新,将响应至于挂起状态,一直不回复response给客户端,知道有内容更新,再返回响应。

虽然可以做到实时更新,但是为了保留响应,一次连接饿持续时间也变长了。期间,为了维持连接会消费更多的资源。

从上面两种方式中,其实可以看出是再不断的建立http连接,然后等待服务器处理,可以体现出了http的特点:被动性,即:请求只能由客户端发起。服务器端不能主动联系客户端。
不管怎么样,上面这两种都是非常消耗资源的。
ajax轮询 需要服务器有很快的处理速度和资源。(速度)
长轮询 需要有很高的并发,也就是说同时接待客户的能力。(场地大小)

除了以上这些,HTTP还是一个无状态协议。
通俗的说就是,服务器因为每天要接待太多浏览器了,是个健忘鬼,你一断连接,他就把你的东西全忘光了,把你的东西全丢掉了。你第二次还得再告诉服务器一遍。

WebSocket

WebSocket其实是HTTP协议上的一种补充,他们有交集但并不是全部。


一旦web服务器和客户端建立起websocket协议的通信连接,之后所有的通信都依靠这个专用连接进行。只需要经过一次HTTP请求,就可以做到源源不断的信息传送了。


websocket是基于HTTP协议的,或者说借用了http的协议来完成一部分握手。为了实现websocket通信,在http建立连接后,还需要进行一次“握手”的步骤。

握手 · 请求

GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13 Origin: http://example.com
为了实现websocket通信,需要用到http的Upgrade首部字段,告知服务器通信协议已发生改变:我要发起的是websocket协议。以达到握手的目的。
Sec-WebSocket-Key字段记录着握手必不可少的键值,用于验证服务器是否支持websocket通信。
Sec-WebSocket-Protocol字段记录的是所需要使用的协议。

握手 · 响应

HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk= Sec-WebSocket-Protocol: chat
对于客户端的请求,服务器返回状态码 101 Switching Protocols的响应。
返回Upgrate告诉客户端即将升级的协议是Websocket协议。
Sec-WebSocket-Accept字段值是由握手请求中的Sec-WebSocket-Key字段值加密过后生成的。
Sec-WebSocket-Protocol 则是表明最总使用的协议。

到这里,http已经完成所有他的工作了,接下来通信时不再使用HTTP的数据帧,而是使用websocket独立的数据帧。


因此,websocket协议具有以下的特点:

推送功能
支持服务器端向客户端推送功能。服务器可以直接发送数据而不用等待客户端的请求。
减少通信量
只要建立起websocket连接,就一直保持连接,在此期间可以源源不断的传送消息,直到关闭请求。也就避免了HTTP的非状态性。
减少资源消耗
那么为什么他会解决服务器上消耗资源的问题呢?
其实我们所用的程序是要经过两层代理的,即HTTP协议在Nginx等服务器的解析下,然后再传送给相应的Handler(PHP等)来处理。
简单地说,我们有一个非常快速的接线员(Nginx),他负责把问题转交给相应的客服(Handler)。本身接线员基本上速度是足够的,但是每次都卡在客服(Handler)了,老有客服处理速度太慢。导致客服不够。Websocket就解决了这样一个难题,建立后,可以直接跟接线员建立持久连接,有信息的时候客服想办法通知接线员,然后接线员在统一转交给客户。这样就可以解决客服处理速度过慢的问题了。
协议具体实现

前提:

本人最近做的项目,服务器端用的是C++写的,而与客户端交互用的是websocket,服务器端要想正常的使用数据,必须要对websocket协议进行解析。

WebSocket数据格式


FIN:表示这个数据是不是接收完毕,为1表示收到的数据是完整的,占1bit
RSV1~3:用于扩展,通常都为0,各占1bit
OPCODE:表示报文的类型,占4bit 0x00:标识一个中间数据包0x01:标识一个text数据包0x02:标识一个二进制数据包0x03~07:保留0x08:标识一个断开连接数据包0x09:标识一个ping数据包0x0A:标识一个pong数据包0x0B~F:保留
MASK:用于表示数据是否经常掩码处理,为1时,Masking-key即存在,占1bit
Payload len:表示数据长度,即Payload Data的长度,当Payload len为0~125时,表示的值就是Payload Data的真实长度;当Payload len为126时,报文其后的2个字节形成的16bits无符号整型数的值是Payload Data的真实长度(网络字节序,需转换);当Payload len为127时,报文其后的8个字节形成的64bits无符号整型数的值是Payload Data的真实长度(网络字节序,需转换);
Masking-key:掩码,当Mask为1时存在,占4字节32bit
Payload Data:表示数据
C++对websocket协议处理

/*** @brief getWSFrameData 解析websocket的协议包,不能解决粘包半包问题* @param msg 待解析的数据* @param msgLen 待解析的数据长度* @param outBuf 解析完成数据* @return*/int unPackingWSFrameData(char *msg,int msgLen,std::vector &outBuf){//报文长度一定大于2字节,对于小于的,做返回处理if(msgLen < 2){return -3;}uint8_t opcode_ = 0;uint8_t mask_ = 0;uint8_t masking_key_[4] = {0,0,0,0};uint64_t payload_length_ = 0;int pos = 0;//Opcodeopcode_ = msg[pos] & 0x0f;pos++;//MASKmask_ = (unsigned char)msg[pos] >> 7;//Payload lengthpayload_length_ = msg[pos] & 0x7f;pos++;if(payload_length_ == 126){uint16_t length = 0;memcpy(&length, msg + pos, 2);pos += 2;payload_length_ = ntohs(length);}else if(payload_length_ == 127){uint32_t length = 0;memcpy(&length, msg + pos, 8);pos += 8;payload_length_ = ntohl(length);}//Masking-keyif(mask_ == 1){for(int i = 0; i < 4; i++){masking_key_[i] = msg[pos + i];}pos += 4;}//取出消息数据if (msgLen >= pos + payload_length_ ){outBuf.clear();if(mask_ != 1){char* dataBegin = msg + pos;outBuf.insert(outBuf.begin(), dataBegin, dataBegin+payload_length_);}else{for(uint i = 0; i < payload_length_; i++){int j = i % 4;outBuf.push_back(msg[pos + i] ^ masking_key_[j]);}}}else{//此时包长小于报文中记录的包长return -2;}//断开连接类型数据包if ((int)opcode_ == 0x8)return -1;return 0;}
以上函数即实现了对收到websocket数据的解析,返回结果为:vectoroutput;

通常会在函数外面对此进行转换为char*,方便我们使用,见下:

vectoroutput;char* out = &output[0];
当然,现在的解析还不是完美的解决方法,因为在实际的使用当中,会存在接收的包粘包,半包等等问题,而以上函数只能解决收到包正好是一个完整的包的情况;具体解决粘包半包问题,留待下次博客吧!

参考资料:后台私信“资料”送

结尾:

只为记录,只为分享! 愿所写能对你有所帮助。不忘记点个赞,谢谢~

后台私信可以领取 内容包括:C/C++,Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体, WebRTC ,CDN,P2P,K8S,Docker,Golang, TCP/IP,MTK , 嵌入式 , 协程,DPDK等等 。
————————————————
版权声明:本文为优快云博主「沂琳」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.youkuaiyun.com/weixin_35033275/article/details/112156062

<think>好的,我现在需要帮助用户解决在前端通过WebSocket发送ping消息以保持连接活跃的问题。用户具体想要一个实现ping pong心跳机制的示例,并且可能使用Vue2框架,因为他们在之前的引用里提到了Vue2的WebSocket示例。首先,我需要回忆WebSocket的心跳机制通常是怎么实现的,以及如何在Vue2中集成这个机制。 首先,心跳机制的目的是保持WebSocket连接活跃,防止因为长时间没有数据传输而被防火墙或服务器断开。通常的做法是定期发送一个ping消息,然后等待pong响应。如果一段时间内没有收到pong,就重新连接。 接下来,我需要考虑如何在JavaScript中实现这个机制。WebSocket API本身有ping和pong的方法吗?我记得在浏览器环境中,WebSocketping方法可能被直接支持,或者需要特定的方式发送数据。过,根据规范,浏览器可能自动处理ping/pong帧,但应用层可能需要自己实现心跳,比如通过定时发送特定数据,并在收到响应时重置计时器。 用户提到的引用[1]中有Vue2的示例,可能需要参考那个结构。例如,在Vue的组件中创建WebSocket连接,设置定时器发送心跳消息,并处理onmessage事件来检测pong响应。同时,需要处理连接中断后的重连逻辑。 可能的步骤包括: 1. 创建WebSocket连接实例。 2. 在连接打开时,启动心跳检测定时器,定期发送ping消息。 3. 监听message事件,如果接收到pong响应,则重置计时器。 4. 如果一定时间内没有收到pong,关闭当前连接并尝试重连。 5. 处理网络波动,可能需要指数退避策略来重连,避免频繁请求。 此外,要注意在组件销毁时清除定时器,避免内存泄漏。在Vue的beforeDestroy或destroyed生命周期钩子中清理定时器和关闭WebSocket连接。 现在,我需要将这些步骤转化为具体的代码示例。例如,在Vue的data中定义websocket对象、重连次数、定时器ID等变量。在methods中定义连接、发送心跳、关闭等方法。在mounted钩子中初始化连接,并在销毁前清理资源。 还需要处理错误事件和close事件,以便在连接异常时触发重连。心跳消息可以是一个特定的字符串,比如'ping',服务器需要回应'pong'。前端在发送ping后,如果在设定的时间内没有收到pong,就认为连接断开,执行重连。 关于代码的正确性,需要注意定时器的使用,setTimeout和setInterval的区别,可能使用setTimeout递归调用来实现间隔发送,这样可以更灵活地控制时间间隔,尤其是在重连时可能需要动态调整间隔时间。 另外,用户可能关心如何测试这个机制,比如使用Chrome开发者工具查看WebSocket消息,或者模拟网络断开的情况来验证重连是否有效。 最后,需要确保示例代码符合用户的要求,使用Vue2的语法,结构清晰,并且包含心跳和重连的关键部分。同时,根据用户提供的引用,可能需要引用相关参考资料,比如引用[1]中的Vue2示例,以及引用[2]中的心跳机制实现。</think>以下是在 Vue2 中通过 WebSocket 实现 **ping/pong 心跳机制**的示例代码,包含连接管理、心跳检测和自动重连功能: ```vue <template> <div> <p>连接状态: {{ connectionStatus }}</p> </div> </template> <script> export default { data() { return { ws: null, reconnectCount: 0, // 重连计数器 maxReconnect: 5, // 最大重连次数 pingInterval: 30000, // 心跳间隔30秒 timeoutObj: null, // 心跳定时器 serverTimeoutObj: null // 服务端响应超时检测 }; }, computed: { connectionStatus() { return this.ws?.readyState === WebSocket.OPEN ? '已连接' : '未连接'; } }, mounted() { this.initWebSocket(); }, beforeDestroy() { this.closeWebSocket(); }, methods: { initWebSocket() { this.ws = new WebSocket('wss://your-websocket-endpoint'); this.ws.onopen = () => { console.log('WebSocket 连接成功'); this.reconnectCount = 0; // 重置重连计数器 this.startHeartbeat(); }; this.ws.onmessage = (event) => { if (event.data === 'pong') { console.log('收到服务器 pong 响应'); this.resetHeartbeat(); // 收到响应后重置心跳检测 } // 处理其他业务消息... }; this.ws.onclose = (e) => { console.log('连接关闭', e.reason); this.handleReconnect(); }; this.ws.onerror = (error) => { console.error('连接异常:', error); this.handleReconnect(); }; }, // 开始心跳检测 startHeartbeat() { this.timeoutObj = setInterval(() => { if (this.ws.readyState === WebSocket.OPEN) { this.ws.send('ping'); // 发送心跳消息 this.serverTimeoutObj = setTimeout(() => { console.warn('未收到服务器响应,主动关闭连接'); this.ws.close(); // 超时未收到响应则关闭连接 }, 5000); // 等待5秒后检测 } }, this.pingInterval); }, // 重置心跳检测 resetHeartbeat() { clearTimeout(this.serverTimeoutObj); }, // 处理重连 handleReconnect() { clearInterval(this.timeoutObj); clearTimeout(this.serverTimeoutObj); if (this.reconnectCount < this.maxReconnect) { this.reconnectCount++; const delay = Math.min(1000 * 2 ** this.reconnectCount, 30000); // 指数退避策略 console.log(`将在${delay}ms后尝试第${this.reconnectCount}次重连`); setTimeout(this.initWebSocket, delay); } else { console.error('已达最大重连次数,放弃连接'); } }, // 关闭连接 closeWebSocket() { if (this.ws) { this.ws.close(); clearInterval(this.timeoutObj); clearTimeout(this.serverTimeoutObj); } } } }; </script> ``` ### 关键实现说明: 1. **心跳机制**:通过 `setInterval` 每 30 秒发送一次 `ping` 消息,同时启动 5 秒等待定时器。若服务器未及时返回 `pong` 则主动断开连接[^1] 2. **自动重连**:使用指数退避策略(1s, 2s, 4s...),避免频繁重连造成服务器压力[^2] 3. **状态管理**:通过 WebSocket 的 `readyState` 属性判断连接状态 4. **资源释放**:在组件销毁前清除定时器并关闭连接,防止内存泄漏 ### 服务端配合要求: - 收到 `ping` 消息后应立即返回 `pong` 响应 - 可参考 Go 语言实现的心跳机制示例
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值