1 背景
在 WebSocket 出现之前,为了实现推送技能,所用的技能都是轮询,轮询是指浏览器每隔一段时间向服务器发出 HTTP 哀求,服务器再返回最新的数据给客户端
常见的轮询方式分为轮询与长轮询,它们的区别如下图所示:
这种传统的模式带来很明显的缺点,即浏览器需要不绝向服务器发出哀求,然而 HTTP 哀求与相应可能会包罗较长的头部,此中真正有效的数据可能只是很少的一部分,这样会斲丧很多带宽资源。因此,HTML5 定义了 WebSocket 协议,能更好地节省服务器资源和带宽,并且能够更实时地举行通讯
2 什么是WebSocket
WebSocket 是一种网络传输协议,可在单个 TCP 毗连上举行全双工通讯,它使得客户端和服务器之间的数据交换变得更加简单,只需要完成一次握手,两者之间就可以创建持久性的毗连,并举行双向数据传输,本质上一种计算机网络应用层的协议,用来弥补 HTTP 协议在持久通讯能力上的不足
它有以下特点:
- 创建在 TCP 协议之上
- 与 HTTP 协议有着精良的兼容性:与 HTTP 和 HTTPS 使用相同的 TCP 端口,可以绕过大多数防火墙的限定,默认端口是 80(ws) 和 443(wss,运行在 TLS 之上),并且握手阶段采用 HTTP 协议
- 较少的控制开销:毗连创建后,ws 客户端、服务端举行数据交换时,协议控制的数据包头部较小,而 HTTP 协议每次通讯都需要携带完备的头部
- 可以发送文本,也可以发送二进制数据
- 没有同源限定,客户端可以与恣意服务器通讯
- 协议标识符是 ws,如果加密则为 wss,服务器网址就是 URL
- 支持扩展:ws 协议定义了扩展,用户可以扩展协议,或者实现自定义的子协议,比如支持自定义压缩算法
3 原理
3.1 创建毗连
在 WebSocket 开始通讯之前,通讯双方需要先辈行握手,WebSocket复用了 HTTP 的握手通道,即客户端通过 HTTP 哀求与 WebSocket 服务端协商升级协议,协议升级完成后,后续的数据交换则遵照 WebSocket 的协议
利用 HTTP 完成握手有以下利益:
- 可以让 WebSocket 和 HTTP 基础设备兼容(运行在 80 端口 或 443 端口)
- 可以复用 HTTP 的 Upgrade 机制,完成升级协议的协商过程
3.2 交换数据
WebSocket 的每条消息可能会被切分成多个数据帧,发送端会将消息切割成多个帧发送给接收端,接收端接收消息帧,并将关联的帧重新组装成完备的消息
以下是MDN 上的示例:
- Client: FIN=1, opcode=0x1, msg="hello"
- Server: (process complete message immediately) Hi.
- Client: FIN=0, opcode=0x1, msg="and a"
- Server: (listening, newmessage containing text started)
- Client: FIN=0, opcode=0x0, msg="happy new"
- Server: (listening, payload concatenated to previous message)
- Client: FIN=1, opcode=0x0, msg="year!"
- Server: (process complete message) Happy new year to you too!
复制代码 在该示例中,客户端向服务器发送了两条消息,第一个消息在单个帧中发送,而第二个消息跨三个帧发送。当 WebSocket 的接收方收到一个数据帧时,会根据 FIN 字段值来判断是否收到消息的最后一个数据帧;利用 FIN 和 Opcode,我们就可以实现跨帧发送消息
此中 Opcode 表示操作码,它的可能值有:
- 0x1:传输数据是文本
- 0x2:传输数据是二进制数据
- 0x0:表示该帧是一个延续帧,这意味着服务器应该将帧的数据毗连到从该客户端接收到的最后一个帧
- 0x3-7:保存的操作代码,用于后续定义的非控制帧
- 0x8:表示毗连断开
- 0x9:表示这是一个心跳哀求ping
- 0xA:表示这是一个心跳相应pong
- 0xB-F:保存的操作代码,用于后续定义的控制帧
具体的数据帧格式大概如下图所示,单位是比特:
- FIN:1 个比特,值为 1 表示这是消息的最后一帧,为 0 则不是
- RSV1, RSV2, RSV3:各占 1 个比特,一般情况下全为 0,非零值表示采用 WebSocket 扩展
- Mask: 1 个比特,表示是否要对数据举行掩码操作
- Payload length:数据负载的长度,单位是字节,为 7 位,或 7+16 位,或 1+64 位
- Masking-key:0 或 4 字节(32 位),所有从客户端传送到服务端的数据帧,数据都举行了掩码操作,Mask 为 1,且携带了 4 字节的 Masking-key;如果 Mask 为 0,则没有 Masking-key
- Payload data:具体数据
3.3 维持毗连
使用 WebSocket 举行通讯,可以通过创建心跳机制来判断毗连正常没有断开或者服务是否可用,所谓心跳机制,就是定时发送一个数据包,让对方知道本身在线且正常工作,确保通讯有效;如果对方无法相应,便可以弃用旧毗连,发起新的毗连了
需要重连的场景可能包括:网络题目或者机器故障导致毗连断开、毗连没断但不可用了或者毗连对端的服务不可用
发送方 -> 接收方:ping
接收方 -> 发送方:pong
ping 、pong 的操作,对应的是 WebSocket 的两个控制帧,Opcode 分别是 0x9、0xA。比如说,WebSocket 服务端向客户端发送 ping:
- // ping
- ws.ping()
- // pong
- ws.on('pong', () => {
- console.log('pong received')
- })
复制代码 客户端也可以发送:
- // 发送心跳包
- ws.send('heart check')
- // 接收响应
- ws.onmessage = (e) => {
- const response = e.data
- if (response.message === 'connection alive') {
- // 重置计时器
- }
- }
复制代码 4 WebSocket使用
以下是一个 Vue + WebSocket 使用的 demo,逐步解释每个部分是如何工作的,将从 创建毗连 到 发送消息和接收消息 全面讲解 WebSocket 的使用
4.1 创建 WebSocket 客户端
1)创建 WebSocket 实例
创建一个 WebSocket 毗连,并毗连到ws://localhost:8080 这个 WebSocket 服务器地点。这个地点指向服务器上的 WebSocket 服务,如果服务器乐成启动并运行在 8080 端口上,客户端会创建毗连
- const socket = new WebSocket('ws://localhost:8080')
复制代码 2)监听 WebSocket 变乱
- onopen:毗连创建乐成时触发
- onmessage:接收到消息时触发
- onclose:毗连关闭时触发
- onerror:发生错误时触发
onopen触发后,我们将毗连状态设置为“毗连已创建”,并发送一条初始化消息到服务器
- socket.onopen = () => {
- console.log('WebSocket 连接已建立')
- connectionStatus.value = '连接已建立'
- socket.send('客户端已连接,发送初始化消息')
- }
复制代码 onmessage处理函数每次收到服务器消息时都会执行,将接收到的消息显示到页面上
- socket.onmessage = (event) => {
- console.log('从服务器接收到消息:', event.data)
- receivedMessage.value = event.data // 将收到的消息存储到响应式变量中
- }
复制代码 3)发送消息
通过 WebSocket 的send()方法发送消息,sendMessage()函数会在点击按钮时触发,查抄 WebSocket 是否已经毗连 (readyState === WebSocket.OPEN),如果毗连乐成,则发送消息
- function sendMessage() {
- if (socket.readyState === WebSocket.OPEN) {
- const message = '这是客户端发送的消息'
- socket.send(message)
- console.log('已发送消息:', message)
- } else {
- console.log('WebSocket 连接未打开,无法发送消息')
- }
- }
复制代码 4)状态消息
使用 ref 来处理 WebSocket 毗连的状态
- connectionStatus用来显示 WebSocket 毗连的状态,如“未毗连”、“毗连已创建”或“毗连已关闭”
- receivedMessage用来显示从服务器接收到的消息
- const connectionStatus = ref('未连接')
- const receivedMessage = ref('等待服务器消息...')
复制代码 团体 vue 代码如下:
- <template> <div> <h2>WebSocket 使用示例</h2> <!-- 显示当前毗连状态 --> <p>毗连状态: {{ connectionStatus }}</p> <!-- 按钮触发发送消息 --> <button @click="sendMessage">发送消息</button> <!-- 显示从服务器接收到的消息 --> <p><strong>从服务器接收到的消息:</strong> {{ receivedMessage }}</p> </div></template><script setup>import { ref } from 'vue'// 设置 WebSocket 相干状态const connectionStatus = ref('未连接')
- const receivedMessage = ref('等待服务器消息...')
- // 创建 WebSocket 实例,毗连到 WebSocket 服务器// 确保 WebSocket 服务器已经在 8080 端口上运行const socket = new WebSocket('ws://localhost:8080')
- // 监听 WebSocket 毗连打开变乱socket.onopen = () => {
- console.log('WebSocket 连接已建立')
- connectionStatus.value = '连接已建立'
- socket.send('客户端已连接,发送初始化消息')
- }
- ;// 监听 WebSocket 接收到消息变乱socket.onmessage = (event) => {
- console.log('从服务器接收到消息:', event.data)
- receivedMessage.value = event.data // 将收到的消息存储到响应式变量中
- }
- ;// 监听 WebSocket 关闭变乱socket.onclose = () => { console.log('WebSocket 毗连已关闭') connectionStatus.value = '毗连已关闭'};// 监听 WebSocket 错误变乱socket.onerror = (error) => { console.log('WebSocket 出现错误:', error) connectionStatus.value = '毗连错误'};// 发送消息的函数function sendMessage() {
- if (socket.readyState === WebSocket.OPEN) {
- const message = '这是客户端发送的消息'
- socket.send(message)
- console.log('已发送消息:', message)
- } else {
- console.log('WebSocket 连接未打开,无法发送消息')
- }
- }
- </script><style scoped>h2 { color: #42b983;}button { margin-top: 10px; padding: 10px; background-color: #42b983; color: white; border: none; cursor: pointer}button:hover { background-color: #36a372;}</style>
复制代码 4.2 运行 WebSocket 服务器
以下是一个简单的 WebSocket 服务器,使用 Node.js 和 ws 库
1)安装 ws 包
2)WebSocket 服务器代码
- // server.js
- const WebSocket = require('ws')
- // 创建 WebSocket 服务器,监听 8080 端口
- const wss = new WebSocket.Server({ port: 8080 })
- // 当有客户端连接时触发
- wss.on('connection', (ws) => {
- console.log('客户端已连接')
- // 向客户端发送一条消息
- ws.send('欢迎连接 WebSocket 服务器!')
- // 监听客户端发送的消息
- ws.on('message', (message) => {
- console.log('收到来自客户端的消息:', message)
- // 将收到的消息原封不动地返回给客户端
- ws.send(`服务器已收到: ${message}`)
- });
- // 处理连接关闭
- ws.on('close', () => {
- console.log('客户端已断开连接')
- });
- });
- console.log('WebSocket 服务器正在监听端口 8080...')
复制代码 3)启动 WebSocket 服务器
在 Node.js 项目对应的目录中,运行以下命令来启动服务器,xxx是该 js 文件名
4.3 运行 Vue 项目
运行 Vue 项目后,在浏览器中打开 Vue 页面后,大致毗连流程如下:
1)WebSocket 毗连的状态:未毗连 → 毗连已创建
2)点击按钮可以发送消息到 WebSocket 服务器
3)从 WebSocket 服务器接收到的相应消息,展示到页面中
5 优缺点及实用场景
长处:
- 实时性: WebSocket 提供了双向通讯,服务器可以自动向客户端推送数据,实时性非常高,实用于实时谈天、在线协作等应用
- 减少网络耽误: 与轮询和长轮询相比,WebSocket 可以显著减少网络耽误,由于不需要在每个哀求之间创建和关闭毗连
- 较小的数据传输开销: WebSocket 的数据帧相比于 HTTP 哀求报文较小,减少了在每个哀求中传输的开销,特别实用于需要频繁通讯的应用
- 较低的服务器资源占用: 由于 WebSocket 的长毗连特性,服务器可以处理更多的并发毗连,相较于短毗连有更低的资源占用
- 跨域通讯: 与一些其他跨域通讯方法相比,WebSocket 更轻易实现跨域通讯
缺点:
- 毗连状态保持: 长时间保持毗连可能会导致服务器和客户端都需要维护毗连状态,可能增长一些负担
- 不实用于所有场景: 对于一些哀求-相应模式较为简单的场景,WebSocket 的实时特性可能并不是必要的,使用 HTTP 哀求可能更为符合
- 复杂性: 与传统的 HTTP 哀求相比,WebSocket 的实现和管理可能稍显复杂,尤其是在处理毗连状态、异常等方面
实用场景:
- 实时谈天应用: WebSocket 是实实际时谈天室、即时通讯应用的理想选择,由于它能够提供低耽误和高实时性
- 在线协作和协同编辑: 对于需要多用户协同工作的应用,如协同编辑文档或绘图,WebSocket 的实时性使得用户能够看到其他用户的操作
- 实时数据展示: 对于需要实时展示数据变化的应用,例如股票行情、实时监控系统等,WebSocket 提供了一种高效的通讯方式
- 在线游戏: 在线游戏通常需要快速、实时的通讯,WebSocket 能够提供低耽误和高并发的通讯能力
- 推送服务: 用于实现消息推送服务,向客户端自动推送更新或通知
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |