WebSocket
概述
相比 HTTP 协议来说,WebSocket 协议对大多数后端开发者是比较陌生的。相比来说, WebSocket 协议重点是提供了服务端主动向客户端发送数据的能力,这样我们就可以完成实时性较高的需求。例如:聊天IM即时通讯功能,消息订阅服务、网页游戏等
这里有个误区,WebSocket相比普通的Socket来说,仅仅是借助HTTP协议完成握手,创建连接。后续所有的通讯都和HTTP协议无关。
理清 WebSocket 和 HTTP 的关系
总结:
- WebSocket 和 HTPP 都是基于TCP协议的两个不同的协议
- WebSocket 依赖于HTTP连接
WebSocket 依赖于 HTTP 连接,那么它如何从链接的 HTTP 协议转化为 WebSocket 协议?
每个 WebSocket 链接都始于 HTTP 请求。具体来说, WebSocket 协议第一次握手连接时,通过 HTTP 协议在传送 WebSocket 支持的版本号、协议的子版本号、原始地址、主机地址等等一系列字段给服务端:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key:dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Version: 13
注意:关键的地方是,这里面有个Upgrade首部,用来把当前的HTTP请求升级到 WebSocket 协议,这是 HTTP 协议本身的内容,是为了扩展其他的通讯协议。如果服务器支持新的协议,则必须返回 101:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept:s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
至此,HTTP请求物尽其用,如果成功出发onopen事件,否则触发onerror事件,后面的传输则不再依赖HTTP协议。
WebSocket 为什么要依赖 HTTP 协议的连接 ?
- WebSocket 设计上就是天生为 HTTP 增强通讯,所以在 HTTP 协议链接的基础上是自然的一件事,并因此能获得 HTTP 的诸多便利。
- 诸多便利中有一条很重要,基于 HTTP 连接将获取最大的一个兼容支持,比如即使服务器不支持 WebSocket 也能建立 HTTP 通信,只不过返回的是 onerror 而已,这显然比服务器无响应要好得多。
在实现提供 WebSocket 服务的项目中,一般有如下几种解决方案:
- 方案一 Spring WebSocket
- 方案二 Tomcat WebSocket
- 方案三 Netty WebSocket
如果仅仅是仅仅提供 WebSocket 协议的支持,可以考虑采用方案一或者方案二。在使用上,两个方案是比较接近的。相比来说,方案一 Spring WebSocket 内置了对 STOMP 协议的支持。
WebSocket Demo
C:\Users\Administrator\Desktop\mall\spring-websocket-demo
C:\Users\Administrator\Desktop\mall\Tomcat-websocket-demo
http://www.iocoder.cn/Spring-Boot/WebSocket/
如何保证消息一定送达给用户呢?
如果用户不在线的时候,消息持久化到MySQL、MongoDB、Redis等等数据库中,这个是必要的。
我们在考虑下边界情况,客户端网络环境差,特别是在移动端场景下,出现网络闪断,可能会出现连接实际断开,而服务器以为客户端处于在线情况。吃屎,服务端会将消息发送给客户端,那么消息实际就会发送到 “空气”中,产生丢失情况,要解决这种情况的问题,需要引入客户端的 ACK 消息机制。目前主流的做法有两种方法。
第一种基于每一条消息编号 ACK 整体流程如下:
- 无论客户端是否在线,服务端都先把接收到的消息持久化到数据库中。如果客户端在线,服务端将完整的消息推送给客户端
- 客户端接收到消息后,发送 ACK 消息编号给服务端,告知已收到该消息。服务端在收到 ACK 消息编号的时,标记该消息已经发送成功。
- 服务端定时轮询,在线的客户端,是否有超过N秒未 ACK 的消息,如果有,则重新发送消息给客户端。
不过因为服务端仍然需要定时轮询,也会导致服务端压力较大。所以,这种方案基本已经不采用了。
第二种,基于滑动窗口 ACK,整体流程如下:
- 无论客户端是否在线,服务端都先把接收到的消息持久化到数据库中。 如果客户端此时在线,服务端将消息编号推送给客户端。
- 客户端在接收到消息编号之后,和本地的消息编号进行对比。如果比本地的小,说明该消息一经收到,忽略不计;如果比本地大,使用本地的消息编号,向服务端拉取大于本地的消息编号的消息列表,增量消息列表。拉去完之后,更新消息列表中最大的消息编号为新的本地的消息编号
- 服务端在收到客户端拉取增量的消息列表时,将请求的编号记录到数据库中,用于知道客户端此时本地的最新消息编号。
- 考虑到服务端将消息编号推送给客户端,也会存在丢失的情况,所以客户端会每 N 秒定时向服务端拉取大于本地的消息编号的消息列表。
这种方式,在业务被称为推拉结合的方案,在分布式消息队列、配置中心、注册中心实现实时的数据同步,经常被采用。