考虑网页中的以下场景:
上述场景有一个共同特点——实时性
这种对实时性有要求的页面,会带来一些问题:由于 HTTP 协议是请求-响应模式,请求必须在前,响应必须在后,这就导致了服务器无法「主动」的把消息告诉客户端
短轮询是一种「话痨式」的方式,客户端每隔一小段时间就向服务器请求一次,询问有没有新消息
![[短轮询.svg]]
实现短轮询是非常简单的,客户端只需要设置一个轮询器不断发送请求即可
这种方案的缺陷是非常明显的:
我们的前辈在有限的条件下,充分发挥智慧,来解决短轮询的问题,于是演化为长轮询
![[长轮询.svg]]
长轮询有效的解决了「话痨问题」,让每一次请求和响应都是有意义的
但长轮询仍然存在问题:
伴随着 HTML5 出现的 WebSocket,从协议上赋予了服务器主动推送消息的能力(实践上是全双工)
实际上 TCP 是支持全双工的,但是 HTTP 为了保证请求-响应模型,只利用了半双工的通信
![[20211028171840.png|500]]
虽然优于轮询方案,但 WebSocket 仍然是有缺点的:
![[Pasted image 20240322113322.png]]
WebSocket 协议是一个高扩展性的协议,详细内容会比较复杂,这里仅讲解面试中会问到的握手协议
当客户端需要和服务器使用 WebSocket 进行通信时,首先会使用 HTTP 协议完成一次特殊的请求-响应,这一次请求-响应就是 WebSocket 握手
在握手阶段,首先由客户端向服务器发送一个请求,请求地址格式如下:
ws://mysite.com/path # 使用HTTP
wss://mysite.com/path # 使用HTTPS
请求头如下:
Connection: Upgrade # 升级协议
Upgrade: websocket # 协议升级为 WebSocket
Sec-WebSocket-Version: 13 # WebSocket 的版本
Sec-WebSocket-Key: YWJzZmFkZmFzZmRhYw== # 暗号:天王盖地虎
服务器如果同意,就应该响应下面的消息:
HTTP/1.1 101 Switching Protocols
Connection: Upgrade # 协议升级了
Upgrade: websocket # 升级到 Websocket
Sec-WebSocket-Accept: ZzIzMzQ1Z2V3NDUyMzIzNGVy # 暗号:小鸡炖蘑菇
握手完成,后续消息收发不再使用 HTTP,而是使用 WebSocket 进行全双工通信,任何一方都可以主动发消息给对方
const ws = new WebSocket("地址"); // 创建 Websocket 连接,浏览器自动握手
// 事件:握手完成后触发
ws.onopen = function () {
console.log('连接到了服务器');
};
// 事件:收到服务器消息后触发
ws.onmessage = function (e) {
console.log(e.data); // e.data:服务器发送的消息
};
// 事件:连接关闭后触发
ws.onclose = function () {
console.log('连接关闭了');
};
// 发送消息到服务器
ws.send(消息);
// 连接状态:0-正在连接中;1-已连接;2-正在关闭中;3-已关闭
ws.readyState
![[20211029113505.png|500]]
上图是一段时间中服务器给客户端推送的数据,你能区分这些数据都是什么意思吗?
这就是问题所在:WebSocket 连接双方可以在任何时候发送任何类型的数据,另一方必须要清楚这个数据的含义是什么?
虽然我们可以自行解决这些问题,但毕竟麻烦。Socket.IO 是对原生 WebSocket API 做的一层封装,帮助我们解决了这些问题,它把消息放到不同的事件中,通过监听和触发事件来实现对不同消息的处理:
![[20211029123907.png]]
客户端和服务器双方事先约定好不同的事件,事件由谁监听,由谁触发,就可以把各种消息进行有序管理了