websocket是一种在单个tcp连接上进行双工通信的协议,能提供比传统的HTTP连接更加高效,低延迟的实时通信
websocket
基本介绍
websocket主要有如下特点:
1. 双工通信
在客户端发起与服务器端建立连接之后,websocket允许客户端和服务器端进行双工通信,在连接的情况下,任何一方在任何时候都可以主动的发送消息。
2. 持久连接
websocekt是持久连接的,一旦建立连接,它将保持打开状态,允许任何时候发送消息,而不需要重新建立连接
3. 低延迟
由于webscoket的全双工通信,它比传统的http请求-响应模型具有更低的延迟,不需要每次都建立连接,所以它非常适合实时通信的应用,如聊天和游戏。
4. 简化的协议
websocket的协议相对简单,基于帧结构,与传统的HTTP相比,它没有请求头和响应头,减少了通信的开销
5. 跨域通信
websocket是协议标识符是ws(未加密)和wss(加密),它不受浏览器同源策略的影响,可以在不同域名之间进行通信,不需要处理跨域问题。
连接过程
websocket虽然是基于ws协议,但是创建连接的过程与http类似,也是需要建立一个tcp连接,在这个过程中会告知服务器升级为ws协议,如果服务器支持ws服务,则返回101状态码告诉浏览器已经切换为ws协议
1. 握手阶段
客户端发起连接
客户端通过向服务器端发送一个http请求来发起websocket连接,这个请求包括两个特殊的头部connection: upgrade 和 upgrade:websocket,即升级为websocket协议
服务器响应握手
服务器端收到请求后,如果支持websocket,会返回一个101状态码,表示切换协议
建立连接
握手成功后,就正式建立了连接,此时客户端和服务器端可以通过这个连接进行双工通信
2. 通信阶段
数据帧传输
建立连接后,双端是通过发送数据帧进行通信的,websocket数据帧是协议定义的基本数据单元
帧格式
每个数据帧可以包含文本文本,二进制数据或控制帧,帧的格式包括一个帧头和帧体
3. 保持连接
心跳检测
为了保持连接的活跃性,客户端和服务端通常会定期发送心跳(ping)消息,如果一方在定义的时间内没有收到对方的心跳响应,就可以认为连接已经断开
重连机制
重连机制建立在心跳检查的基础上,如果心跳检查确定断开连接,则发起websockt的重新连接,一般会重复进行尝试,等尝试都失败后,就可以认为服务器已经死掉,无法建立连接。
4. 关闭连接
关闭握手
当任一方决定关闭连接时,它可以发送一个关闭握手帧来表示希望关闭连接,对方收到关闭帧后,也会发送一个关闭帧作为确认
连接关闭
一旦双方都发送了关闭帧,则连接就会正式关闭。
基本使用
websocket是浏览器支持的一个模块,可以在JavaScript中直接使用
// 通过拼接url的方式传递参数
const socket = new Websocket('ws://xxxxx?userId=xxx')
// 建立连接
socket.onopen = () => {
console.log('连接建立...')
socket.send('发送消息')
}
// 监听消息
socket.onmessage = (event) => {
console.log(event.data)
socket.close() // 关闭连接
}
// 连接关闭
socket.onclose = () => {
}
// 连接错误
socket.onerror = () => {
}
心跳检测与重连
心跳检测主要用在客户端,用来判断与服务器的连接是否保持畅通,常见的场景是客户端网络突然断开或服务器端重启,这都会造成websocket连接中断。
实现方案:客户端定时向服务器的发起一个心跳消息,服务器收到消息后向客户端发送一个心跳,客户端监听心跳消息,如果能监听到则连接正常,否则就出现异常,进行重新。
let heart = false
const reConnect =() => {}
// 连接成功设置心跳为true
socket.on('connect', () => {
heart = true
// 开始心跳检测
setInterval(() => {
if (heart) {
heart = false
socket.emit('heart')
} else {
// 如果为false则心跳检测失败,需要重连
reConnect()
}
}, 3000)
// 监听服务端的心跳
socket.on('heart', () => {
heart = true
})
})
// 监听到客户端的心跳后,马上给客户端回复一个消息
socket.on('heart', () => {
socket.emit('heart')
})
socket.io
虽然浏览器提供的websocket api完全可以用来实现实时通信的功能,但是很多的异常场景都需要自己去实现,难免会有很多地方考虑的不周全,目前用到最多的就是socket.io, 支持客户端和服务器端。
socket.io服务端
创建连接监听
import http from 'http';
import express from 'express';
import { Server } from 'socket.io'
const app = express();
const server = http.createServer(app);
app.use(express.static('public'))
const io = new Server(server, {
cors: {
origin: '*'
}
});
io.on('connection', (socket) => {
// 获取参数
const { userId, userName } = socket.handshake.query;
socket.broadcast.emit('message') // 广播消息
socekt.emit('message') // 发送消息
console.log('连接成功')
// 监听客户端发送的消息
socket.on('message', (data) => {
})
// 监听用户断开连接
socket.on('disconnect', () => {
console.log(userId + '断开连接')
});
});
server.listen(8000, () => {
console.log("server is up and running on port 8000");
});
socket.io客户端
创建连接监听
import { io } from 'socket.io-client'
const socket = io('http://192.168.x.x:8000', {
query: {
userId: xxx
}
})
socket.on('connection', ()=> {
console.log('连接成功')
})
socket.on('message', (data)=> {
console.log('监听到消息')
socket.emit('发送消息...')
})
socket.on('disconnect', ()=> {
console.log('断开连接')
})
两者对比
-
websocket是html5里面的一个标准的协议,主要用来实现客户端与浏览器端数据的双向通信。
-
socket.io是一个封装了websocket,长轮询等传输方式的库,它的目标是提供统一的接口,使得在不同环境(不管支持或不支持websocket的i情况下)都能实现实时的双向通信。
-
socket.io创建的服务不是标准的websocket服务,尽管socket.io可以使用websocket作为其中的一种传输方式,但socket.io本身并不仅仅限于websocket协议,它是一个独立的实现,具有自己的通信协议和机制。
-
一般情况下webscoket客户端和socket.io服务器之间不能直接连接,反之socket.io客户端和websocket服务器端也不能直接连接。
-
websocket建立连接时通过http握手升级到websocket协议,socket.io首先默认发起一个普通的http请求(路径会携带/socket.io), 这个请求告知socket.io服务器希望升级为websocket服务,如果支持websocket则升级,如果不支持则降低到其他方式(长轮询),所以创建socket.io连接时会存在跨域问题。