一、什么是websocket?
定义:
webSocket是一种在单个TCP连接上进行全双工通信的协议。websocket使得客户端和服务器之间的数据交换变得更加简单,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,属于服务器推送技术的一种。
在Websocket API中,浏览器和服务器只需要完成一次握手,两者之间可以创建持久性的连接,并进行双向数据传输。websocket本质上是一种计算机网络应用层的协议,用来弥补http协议在持久通信能力上的不足。
特点:
1.建立在TCP协议之上,服务器端的实现比较容易
为什么选择TCP?
可靠性:TCP提供可靠的传输,确保数据包按顺序到达且无丢失,而且TCP具有错误恢复机制,可重传丢失的包和校验数据完整性;
面向连接的通信:Websocket设计为长连接,TCP的面向连接(通过三次握手建立连接,四次挥手关闭连接)特性支持长期稳定的通信通道;TCP的全双工模式为websocket的双向通信提供底层支持。
与HTTP握手兼容:Websocket握手阶段使用HTTP协议(通过Upgrade头切换协议),而HTTP本身基于TCP,使得Websocket无缝融入现有的Web生态。
补充说明:为何不选择UDP?
虽然UDP具有低延迟和无连接特性,但其不可靠性(丢包、乱序)不适合WebSocket的典型使用场景。若基于UDP,WebSocket需自行处理重传、排序等问题,增加复杂性,违背其设计初衷(简化实时通信)。
2.与HTTP协议有着良好的兼容性。默认端口也是80(非加密)和443(加密),并且握手阶段采用HTTP协议,因此握手时不容易屏蔽,能通过各种HTTP代理服务器。
当浏览器向服务器发起websocket连接时,会先发送http请求(带有Upgrade头)进行握手,服务器响应101 Switching Protocols状态码,表示同意将连接升级为Websocket协议,之后双方可以基于Websocket进行通信,而这个过程对于浏览器和中间代理服务器来说,就像处理普通的HTTP请求一样,只是在握手成功后才切换到Websocket协议进行数据传输。
3.没有同源限制,客户端可以与任意服务器通信
4.websocket的数据帧结构相对简单,它只包含一些必要的头部信息和数据内容,不像HTTP协议那样有大量的头部字段和复杂的消息格式。这使得在数据传输过程中,websocket能减少数据冗余,减低传输开销,提高通信效率
5.websocket可以发送文本,也能直接传输二进制数据,如图片、音频、视频等
6. WebSocket 使用 ws****作为协议标识符来标识非加密的 WebSocket 连接,而对于加密的 WebSocket 连接则使用 wss。
ws://example.com:8080/ws
wss://secure.example.com/ws
网络协议连接方式http/https/ws/wss比较

HTTP协议
客户端基于TCP/IP协议与服务器建立连接;连接建立后,客户端发送HTTP请求,服务器接收请求,以请求URL确定处理方式并向客户端返回响应内容。
HTTPS协议:
- 客户端和服务器先通过TCP协议建立连接,确定双方能可靠传输数据。
- TLS/SSL握手
- 握手完成,建立安全通道,之后客户端和服务器间数据传输都经 TLS/SSL 加密,保证数据机密性、完整性 ,防止数据被窃取、篡改。
补充TLS/SSL握手过程
1.建立TCP连接后,客户端向服务器发送握手消息,其中包含它所支持的加密算法和以一个随机数。
2.服务器 收到消息后,服务器接收到客户端的信息后,从客户端提供的加密算法列表中选择一个它也支持的算法。将包含公共密钥的证书(包含服务器的公钥)及自身的随机数发送给客户端。(握手消息)
3.客户端验证证书(检查签发机构、证书是否过期),抽取公共密钥,生成 pre_master_secret 随机密码串,用服务器公钥加密后发送给服务器(只有持有对应私钥的服务器才能解密 pre_master_secret )
4.双方依据 pre_master_secret 及各自随机数独立计算出用于后续数据加密的加密密钥 ,使用消息认证码(MAC)计算 MAC 密钥
5.客户端和服务器分别使用计算得到的 MAC 密钥对握手消息计算 MAC 值,并发送给对方 ;对方收MAC值,使用相同的密钥对收到的握手消息计算MAC值,对比之后如果相同说明双方的MAC密钥一致,握手成功。
6.此时,双方可以开始使用协商好的密钥进行安全的数据传输,因为双方的密钥一致,并且能够验证消息的完整性和真实性,所以可以保证数据在传输过程中不被篡改,且确实来自合法的通信对方
WS:
- 客户端向服务器发送特殊 HTTP 请求(含 WebSocket 相关头部字段,如 Upgrade: websocket ,表示要升级协议),请求建立 WebSocket 连接。
- 服务器接收请求,若支持 WebSocket 协议,回复 101 Switching Protocols 状态码,确认协议升级,双方切换到 WebSocket 协议通信。
- 握手成功,在 TCP 连接基础上建立持久连接,后续双方可随时双向通信,无需重复建立连接。
- 连接建立后,客户端和服务器可互相发送文本或二进制数据,实现实时通信,如聊天消息发送、实时数据推送。
WSS:
- 同 HTTPS,先通过 TCP 协议建立基础连接。
- 与 HTTPS 的 TLS/SSL 握手类似,客户端和服务器协商加密算法、交换密钥等,建立安全通道。
- TLS/SSL 握手成功,基于已建立的安全通道,进行 WebSocket 握手,过程同 WS 协议的握手 ,建立 WebSocket 连接。
- 通过已建立的加密 WebSocket 连接,客户端和服务器进行双向数据传输,数据在传输中被加密,保障安全。
二、为什么需要WebSocket
HTTP协议有一个缺陷:**通信只能由客户端发起,不具备服务器推送能力。**这种单向请求的特点,客户端获取服务器消息只能使用轮询:每隔一段时间,客户端发出一个询问,了解服务器有没有新的信息。最典型的场景就是聊天室,客户端与服务器交互频繁,如果使用HTTP协议,轮询方式导致效率低,非常浪费资源。
- 短轮询:客户端定时向服务器发送 HTTP 请求询问是否有新消息。比如每 5 秒发一次请求,不管有无新消息都得发。若服务器没新消息,这次请求就白白消耗了网络带宽和服务器资源;频繁请求还会增加客户端设备的功耗和性能开销。
- 长轮询:客户端向服务器发送 HTTP 请求来获取消息。如果服务器当时没有新消息可供发送,它不会像传统的 HTTP 请求那样立即返回一个没有消息的响应,而是将这个请求保持挂起状态,也就是让连接一直处于打开状态。服务器会等待,直到有新消息产生或者达到了预先设定的超时时间,才会向客户端发送响应。这样做的目的是为了让客户端能够尽可能及时地获取到新消息,而不需要像短轮询那样频繁地发送请求去询问是否有新消息。
在websocket协议出现前,基于HTTP协议实现Web应用与服务端双通道通信需不停轮询,引发服务端需维持大量客户端连接,以及轮询请求产生高开销(如携带多余header致无用的数据传输)等问题。
为了解决这些问题,websocket协议由此而生,于2011年被IETF定为标准RFC6455,并被RFC7936所补充规范。并且在HTML5标准中增加了有关WebSocket协议的相关api,所以只要实现了HTML5标准的客户端,就可以与支持WebSocket协议的服务器进行全双工的持久通信了。
三、WebSocket与HTTP的区别
相同点::都是一样基于TCP的,都是可靠性传输协议,都是应用层协议
联系:WebSocket在建立握手时,数据是通过HTTP传输的。但是握手之后,数据传输是通过ws协议的

根据以上图我们来说明一下其区别:
1.连接方式
HTTP:连接相对短暂,客户端向服务器发送请求,服务器响应请求后连接即结束。即使在 HTTP/1.1 及后续版本中支持持久连接,也主要是为了在同一连接上进行多个请求 / 响应,本质上还是基于请求 - 响应模式。
WebSocket:在客户端和服务器之间建立持久的双向连接,连接一旦建立,双方可随时主动发送数据,实现实时通信。
2.数据传输
HTTP:请求和响应数据格式较为复杂 ,包含大量的头部信息等,每次传输可能会携带很多冗余数据。数据传输是单向的,由客户端发起请求,服务器进行响应。
WebSocket:数据格式相对轻量 ,传输更加高效。而且支持全双工通信,客户端和服务器可以同时发送和接收数据,在实时性要求高的场景(如在线游戏、实时聊天等)中表现更优。
3.建立连接的方式
HTTP:客户端通过向服务器发送 HTTP 请求来建立连接,服务器根据请求的 URL、方法等信息进行相应的处理并返回响应。
WebSocket:连接建立需要通过 HTTP 协议进行握手。客户端发送带有特殊请求头的 HTTP 请求到服务器,服务器响应表示同意升级连接为 WebSocket 连接,连接建立后就可以使用 WebSocket 协议进行通信,不再依赖 HTTP 协议的请求 - 响应模式。
4.协议特点
HTTP:是无状态协议,服务器不保留客户端的状态信息,每个请求都是独立的,这使得服务器的实现相对简单,但在处理一些需要状态管理的业务逻辑时,需要额外的机制(如会话管理、Cookie 等)来维护状态。
websocket:连接建立后,双方可以在同一连接上持续进行数据交互,具有状态性,更适合处理需要保持上下文信息的业务场景。
5.应用场景:
HTTP:适用于获取静态资源(如网页、图片、文件等)以及进行简单的信息查询和提交等场景,例如浏览普通的新闻网站、提交表单数据等。
WebSocket:主要用于需要实时交互和双向通信的场景,如在线聊天、实时监控、股票行情推送、多人协作编辑等,能为用户提供更流畅、实时的体验。
四、websocket协议的原理
与http协议一样,websocket协议也需要通过已建立的TCP连接来传输数据,具体实现上是通过http协议建立通道,然后在此基础上用真正的websocket协议进行通信。
1.先建立TCP连接
2.握手过程
客户端发起请求
javascript
//get是http请求方法,表示从服务器的/chart路径请求目标资源;HTTP/1.1表示使用的HTTP协议版本
GET /chat HTTP/1.1
//指定请求目标服务器的主机名和端口号(若未明确指定端口,对于 HTTP 默认 80 端口,HTTPS 默认 443 端口)
Host: server.example.com
//告诉服务器客户端希望将当前的HTTP连接升级为Websocket连接
Upgrade: websocket
//配合 "Upgrade" 字段使用,表明客户端希望服务器在处理完本次请求后,将连接升级为 "Upgrade" 字段指定的协议(此处为 WebSocket)。它表示连接的属性要进行改变,从 HTTP 协议切换到 WebSocket 协议。
Connection: Upgrade
//由客户端随机生成的Base64编码的密钥。服务器接收到密钥后,使用特定算法与固定字符串拼接后进行哈希计算,再将结果进行Base64编码返回给客户端
//客户端收到响应密钥后使用与服务器端相同的算法对其处理,客户端将自己计算出的结果与服务器返回的字段比较;相对则通过,否则拒绝连接;
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
//客户端向服务器表明自己支持的子协议列表。"chat" 和 "superchat" 就是这里列举的两个子协议,服务器可以从中选择一个双方都支持的子协议进行后续通信。如果服务器不支持任何一个,它可以选择不接受这个 WebSocket 连接,或者忽略该字段使用默认的 WebSocket 通信方式。
Sec-WebSocket-Protocol: chat, superchat
//用于指定客户端使用的 WebSocket 协议版本。服务器会检查该版本号,如果服务器支持这个版本,就可以继续进行连接升级流程;如果不支持,服务器可能会返回错误信息,拒绝升级连接。
Sec-WebSocket-Version: 13
// /发起这个 WebSocket 连接请求的网页所在的域
Origin: http://example.com
然后服务器返回以下字段,表示已经接收到请求,成功建立Websocket
javascript
//响应行,101状态码表示服务器同意将连接升级为新的协议。
HTTP/1.1 101 Switching Protocols
//确认将连接升级为 WebSocket 协议。
Upgrade: websocket
//确认改变连接的协议。
Connection: Upgrade
//服务器根据客户端的Sec-WebSocket-Key计算得到的 Base64 编码的响应密钥,用于验证连接的合法性。
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
//服务器选择的子协议。
Sec-WebSocket-Protocol: chat
3.数据传输阶段
握手成功后,客户端和服务器之间建立websocket连接,可以进行双向传输数据,websocket数据帧是websocket协议中用于传输数据的基本单位,格式如下:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -+
| Payload Data continued ... |
+---------------------------------------------------------------+
- FIN(1 bit):表示这是否是消息的最后一个数据帧。如果为 1,则表示是最后一个数据帧;如果为 0,则表示还有后续数据帧。
- RSV1、RSV2、RSV3(各 1 bit):保留位,用于扩展 WebSocket 协议,默认值为 0。
- opcode(4 bits):表示数据帧的类型,常见的类型有:
-
0x0
:表示延续帧,用于分割大消息。0x1
:表示文本帧,数据为 UTF-8 编码的文本。0x2
:表示二进制帧,数据为二进制数据。0x8
:表示关闭帧,用于关闭 WebSocket 连接。0x9
:表示 Ping 帧,用于检测连接是否存活。0xA
:表示 Pong 帧,是对 Ping 帧的响应。
- MASK(1 bit):表示数据是否进行了掩码处理。客户端发送的数据帧必须进行掩码处理,服务器发送的数据帧不能进行掩码处理。
- Payload length(7 bits、7+16 bits 或 7+64 bits):表示数据帧的有效负载长度。如果值在 0 - 125 之间,则表示实际的负载长度;如果值为 126,则接下来的 16 位表示负载长度;如果值为 127,则接下来的 64 位表示负载长度。
- Masking-key(0 或 32 bits) :如果
MASK
为 1,则包含一个 32 位的掩码密钥,用于对数据进行掩码处理。 - Payload Data:实际传输的数据。
4.连接关闭阶段:
当客户端或服务器需要关闭 WebSocket 连接时,会发送一个关闭帧(opcode 为0x8
)。关闭帧可以包含一个状态码和一个可选的关闭原因。对方收到关闭帧后,会发送一个确认关闭帧进行响应,然后双方关闭连接。以下是一个示例关闭帧的状态码和含义:
- 1000:表示正常关闭。
- 1001:表示端点离开,例如服务器关闭或浏览器关闭页面。
- 1002:表示由于协议错误而关闭。
- 1003:表示由于不支持的数据类型而关闭。
- 1005:表示没有状态码的关闭。
- 1006:表示异常关闭,没有发送或接收关闭帧。
五、websocket的优缺点
优点:
- WebSocket协议一旦建议后,互相沟通所消耗的请求头是很小的
- 服务器可以向客户端推送消息了
缺点:
- 少部分浏览器不支持,浏览器支持的程度与方式有区别(IE10)
六、websocket应用场景
- 即时聊天通信:用户在连天客户端(如网页聊天窗口)输入消息后,客户端将消息通过已经建立的websocket连接发送给服务器。服务器收到消息后,根据消息的目标(单聊时的特定用户、群聊时的群组),再通过 WebSocket 将消息推送给相应的接收方客户端。
- 多玩家游戏:以在线多人对战游戏为例,玩家操作游戏角色(如移动、攻击等)时,客户端将操作指令通过 WebSocket 发送给游戏服务器。服务器实时更新游戏世界状态(如角色位置、生命值变化),再通过 WebSocket 把最新状态推送给所有参与游戏的玩家客户端,客户端据此更新游戏画面。
- 在线协同编辑:多个用户同时打开在线文档编辑页面,客户端与服务器建立 WebSocket 连接。当某个用户进行编辑操作(如输入文字、修改格式),操作数据会立即通过 WebSocket 发送给服务器。服务器整合这些操作,更新文档状态后,再通过 WebSocket 将新的文档内容推送给其他正在编辑的用户客户端,实现文档内容实时同步。
- 实时地图位置:以共享出行软件为例,用户开启软件后,手机的定位系统获取用户位置信息,通过客户端的 WebSocket 连接将位置数据发送给服务器。服务器实时更新用户在地图上的位置,并通过 WebSocket 把附近其他用户(如拼车乘客、司机)的位置信息推送给该用户客户端,客户端在地图上显示出各方位置。
不能使用websocket的场景:
如果我们需要通过网络传输的任何实时更新或连续数据流,则可以使用WebSocket
。如果我们要获取旧数据,或者只想获取一次数据供应用程序使用,则应该使用HTTP
协议,不需要很频繁或仅获取一次的数据可以通过简单的HTTP
请求查询,因此在这种情况下最好不要使用WebSocket
。
七、websocket心跳机制和断线重连
onopen
:在 WebSocket 连接成功建立之后调用。onmessage
:当客户端接收到服务器发送的消息时调用。onclose
:在 WebSocket 连接关闭时调用。onerror
:在 WebSocket 连接出现错误时调用。
客户端
1.如何与服务器进行通信:调用方法this.send({type:'消息类型',message:'消息内容'})
2.心跳机制:心跳机制是为了实时检测连接的状态,客户端每隔一段时间发送ping包,如果能够接收到服务器的pong响应,说明连接正常;否则说明连接出现问题:可能是断网了
怎么做心跳机制?
- 设置心跳定时器heartbeatTimer,用于每隔一段事件发送一次心跳包;设置pingTimeout存储ping的超时定时器,如果规定时间内没有收到pong响应说明服务器有问题则主动断开ws连接
- 封装startHeartbeat开启心跳,当连接成功建立时在onopen里开启心跳
javascript
//开启心跳检测
startHeartbeat(){
//心跳定时器
this.heartbeatTimer=setInterval(()=>{
//发送心跳包
this.send({type:ping})
//如果5s还没收到响应断开连接
this.pingTimeout=setTimeout(()=>{
if(this.ws.readyState==WebSocket.OPEN){
this.ws.close()
}
},5000)
},25000)
}
- 封装stopHeartbeat停止心跳,在连接关闭的时候停止心跳
javascript
//停止心跳
stopHeartbeat() {
console.log("停止心跳")
clearInterval(this.heartbeatTimer);
if (this.pingTimeout) {
clearTimeout(this.pingTimeout);
}
}
3.重连机制:如果不是客户端主动断开ws连接,由于网络等原因被迫断开的连接调用重连机制
- 设置重连定时器reconnectTimer,使用指数退避策略实施重连,随着重连失败次数增多重连延时时间延长,节省网络资源;记录重连尝试次数reconnectAttempts和最大重连尝试次数maxReconnectAttempts停止重连
- 封装重连机制
javascript
//计划重连
scheduleReconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.log('已达最大重连次数,停止重试')
return
}
//指数退避策略
const delay = Math.pow(2, this.reconnectAttempts) * 1000
this.reconnectTimer = setTimeout(() => {
console.log('尝试重连', this.reconnectAttempts)
this.reconnectAttempts++
this.connect()
}, delay)
}
完整代码如下:
javascript
//模拟客户端websocket
export class webSocketClient{
constructor(){
//websocket实例
this.ws=null,
//存储心跳定时器
this.heartbeatTimer=null,
//心跳响应超时定时器
this.pingTimeout=null,
//重连次数
this.reconnectAttemps=0,
//重连最大次数
this.maxReconnectAttemps=5
//重连定时器
this.reconnectTimer=null
}
//初始化连接
connect(){
//这里假如一个用户只用一个ws连接
if(this.ws) this.ws.close()
//初始化一个ws连接
this.ws=new WebSocket('握手的接口')
//连接成功回调
this.ws.onopen=()=>{
console.log("ws连接成功")
//初始化重连次数,开始心跳
this.reconnectAttemps=0
this.startHeartbeat()
}
//接收服务器消息的回调
this.ws.onmessage=(event)=>{
const data=JSON.parse(event.data)
if(data.type=='pong'){
console.log("心跳正常")
if(this.pingTimeout){
clearTimeout(this.pingTimeout)
this.pingTimeout=null
}
}else{
//处理业务消息
}
}
this.ws.onerror=(error)=>{
console.log('ws连接出错',error)
}
//连接关闭触发回调
this.ws.onclose=(event)=>{
console.log('ws连接关闭的原因',event.reason)
//停止心跳检测
this.stopHeartbeat()
//如果是异常关闭,非主动关闭,则尝试重连
if(!event.wasClean){
console.log("重连")
this.scheduleReconnect()
}
}
}
//开启心跳检测
startHeartbeat(){
//心跳定时器
this.heartbeatTimer=setInterval(()=>{
//发送心跳包
this.send({type:ping})
//如果5s还没收到响应断开连接
this.pingTimeout=setTimeout(()=>{
if(this.ws.readyState==WebSocket.OPEN){
this.ws.close()
}
},5000)
},25000)
}
//停止心跳
stopHeartbeat(){
console.log('停止心跳')
clearInterval(this.heartbeatTimer)
if(this.pingTimeout){
clearTimeout(this.pingTimeout)
}
}
//send封装
send(message){
if(this.ws.readyState==WebSocket.OPEN){
this.ws.send(JSON.stringify(message))
}
}
//重连
scheduleReconnect(){
if(this.reconnectAttemps>=this.maxReconnectAttemps){
console.log("以达到最大重连次数,停止重连")
return
}
//指数退避策略
const delay=Math.pow(2,this.reconnectAttemps)*1000
this.reconnectTimer=setTimeout(()=>{
this.reconnectAttemps++
this.connect()
},delay)
}
}
//记得配置代理的时候ws:true
服务端:
1.当有新的客户端连接到服务器时触发连接对应的回调,wss.on('connection',()=>{})
2.验证客户端的携带的token是否合法,如果合法才做出相应的处理,否则关闭连接
- 绑定用户连接userConnections,将用户id与ws连接相对应存储起来,后续如果要给客户端发送消息时,找到相对应的用户的连接,可以调用send方法给指定的用户id发送消息
验证原理
JSON Web Token 是一种用于在网络应用中安全传输信息的开放标准(RFC 7519)。一个 JWT 通常由三部分组成,用点(
.
)分隔,分别是头部(Header)、载荷(Payload)和签名(Signature)。验证 JWT 的核心就是验证签名的有效性,确保 JWT 在传输过程中没有被篡改。验证过程
- 解析 JWT :首先,
jwt.verify
方法会将传入的 JWT 字符串按照点(.
)进行分割,得到头部、载荷和签名三部分。- 验证签名 :使用相同的签名算法(在头部中指定)和密钥(
JWT_SECRET
)对头部和载荷进行签名计算,得到一个新的签名。然后将新计算的签名与 JWT 中的签名进行比较,如果两者相同,则说明签名有效,JWT 在传输过程中没有被篡改。- 验证过期时间 :如果 JWT 的载荷中包含
exp
(过期时间)字段,jwt.verify
方法会检查当前时间是否已经超过了exp
指定的时间。如果超过了,则说明 JWT 已经过期,验证失败。- 返回结果 :如果签名有效且没有过期,验证成功,回调函数的
err
参数为null
,decoded
参数包含解码后的载荷信息;如果签名无效或者 JWT 已经过期,验证失败,回调函数的err
参数包含相应的错误信息,decoded
参数为undefined
。验证合法的条件
验证合法需要满足以下两个条件:
- 签名有效:新计算的签名与 JWT 中的签名相同,说明 JWT 在传输过程中没有被篡改。
- 未过期 :如果 JWT 的载荷中包含
exp
字段,当前时间必须在exp
指定的时间之前,说明 JWT 还在有效期内。
3.服务器主动检测心跳
上面的客户端我们自定义了一个心跳机制,服务端我们用ws.ping()触发websocket底层的心跳机制
这里使用了ws的isAlive属性记录客户端心跳状态,true表示客户端存活,false表示客户端离线了
javascript
//全局心跳检测(服务器主动检测,如果客户端超时回应pong断开连接)
setInterval(()=>{
wss.clients.forEach((ws)=>{
if(ws.isAlive==false){
console.log('心跳超时,关闭连接')
return ws.terminate()//强制断开
}
ws.isAlive=false
// 该方法是 WebSocket 协议的一部分,用于发送一个 ping 帧到客户端。
// 当客户端收到 ping 帧后,协议层会自动返回一个 pong 帧作为响应。
// 这个过程在 WebSocket 协议内部处理,不需要你在客户端手动监听或回复 pong。
ws.ping()
})
},30000)
完整代码如下:
javascript
const http=require('http')
const WebSocket=require('ws')
const express=require('express')
//创建应用
const app=express()
const server=http.createServer(app)
///创建ws服务器实例
const wss=new WebSocket({noServer:true})
//握手阶段,将http连接升级为ws连接
//request是 http.IncomingMessage 实例,代表客户端的http请求,包按请求头headers,请求方法,url等信息
//socket是net.Socket 实例,代表与客户端之间底层的TCP套接字连接,你可以借助这个对象对底层的网络连接进行控制,比如读和写数据socket.write()向客户端发送数据
//head是 Buffer 类型的对象,包含了客户端发送的额外数据,这些数据是在升级请求中紧跟在 HTTP 头之后的部分。
server.on('upgrade',(request,socket,head)=>{
//handleUpgrade 方法的作用是把普通的 HTTP 连接升级为 WebSocket 连接。升级成功后调用回调
//回调函数中的 ws 是一个 ws.WebSocket 实例,代表与客户端建立的 WebSocket 连接
wss.handleUpgrade(request,socket,head,(ws)=>{
wss.emit('connection',ws,request)
})
})
wss.on('connection',(ws,req)=>{
console.log("处理客户端的ws连接")
//验证用户身份
const token=new URL(req.url,'baseURL').searchParams.get('token')
jwt.verify(token,JWT_SECRET,(err,decoded)=>{
if(err){
console.log('无效token,关闭连接')
ws.close(4003,'Athentication Failed')
return
}
//这里可以绑定用户信息等逻辑操作
//处理心跳,自定义isAlive将客户端的状态设置为存活
ws.isAlive=true
//监听pong消息,这里是响应服务器触发底层的心跳机制,客户端会自动返回pong
ws.on('pong',()=>{
ws.isAlive=true
})
//处理消息
ws.on('message',(data)=>{
try{
const message=JSON.parse(data)
//这里是响应客户端自定义的心跳机制
if(message.type=='ping'){
ws.send(JSON.stringify({type:'pong'}))
}else{
//处理业务消息
}
}catch(err){
ws.send('{"type":"error","message":"消息格式错误"}')
}
})
})
})
//全局心跳检测(服务器主动检测,如果客户端超时回应pong断开连接)
setInterval(()=>{
wss.clients.forEach((ws)=>{
if(ws.isAlive==false){
console.log('心跳超时,关闭连接')
return ws.terminate()//强制断开
}
ws.isAlive=false
// 该方法是 WebSocket 协议的一部分,用于发送一个 ping 帧到客户端。
// 当客户端收到 ping 帧后,协议层会自动返回一个 pong 帧作为响应。
// 这个过程在 WebSocket 协议内部处理,不需要你在客户端手动监听或回复 pong。
ws.ping()
})
},30000)
参考:
一文吃透 WebSocket 原理 刚面试完,趁热赶紧整理因为项目中使用到了 WebSocket ,面试官在深挖项目经验 - 掘金