针对聊天、订单状态推送这类实时数据更新等场景,通常有多种解决方案
一.普通短轮询
就是固定时间轮询一次,比如设置为10秒,那就是会攒够10秒的所有信息,再统一发送
缺点:
1.无效请求占比高,资源浪费严重,假如一天就跟别人说了一句话,那么只有那一句话的轮询是有效的,其他时间的轮询就是无效轮询
2.实时性差,存在延长,如果设置时间越短,那么实时性当然就越好,但这样无效请求就更多,消耗资源就越严重,那么出于优化,将时间延长,那么这样实时性就差了
3.需要客户端不断发送请求,导致客户端卡顿,体验差,若是移动端,还会有电量消耗和流量消耗
4.无法应对突发高并发,针对突发流量,短轮询会在这一段所有进行积攒,然后就瞬间压垮服务端
二.长轮询
就是发送一次长轮询,如果服务端没有数据,就一直挂着,保持连接,直到有数据了/超时,此时会立即返回信息(超时就是空响应),并断开连接,客户端接收到响应后,又马上再发送一次长轮询,如此循环
缺点:
1.仍然是HTTP请求,每次发送轮询请求,都会消耗网络开销(三次握手/四次挥手)
2.连接挂在服务器仍然占用资源
3.有空窗期,在断开连接,到再次发送连接这一段时间,就是空窗期,实时性不如无间隙的
三.MQTT(轻量级物联网通信协议)
主要采用发布者、订阅者、代理服务器三个角色,主要用于物联网。
四.WebRTC
主要用于视频流、音频流的推送,一般不用于传输文本数据
五.WebSocket(消息推送机制)
WebSocket是一个应用层协议,与HTTP协议是并列同级的关系,是基于传输层TCP实现的一个协议,一旦连接建立完成,客户端或服务端都可以主动的向对方发送数据。
1.WebSocket的报文格式

其中FIN就是表示是否要关闭WebSocket,然后RSV有三位,作为保留位,就是现在不用,留着以后用
opcode是操作码,描述了当前WebSocket数据帧,是起到什么作用的比如说0x1这个操作码就表示是一个文本数据,0x2表示是二进制数据
所以WebSocket既可以传输二进制数据,也可以传输文本数据
MASK代表是否开启掩码操作,掩码操作主要是为了避免"缓冲区溢出"的问题
payload length,也就是载荷长度,因为有7个bit位,所以是2^7-1=127,然后单位是字节127字节会不会太少了呢,的确,所以payload length有三种模式
(1)7bit,当payload length<126时,采用模式1
(2)16bit,当payload length是126时,采用模式2
(3)64bit,当payload length是127时,采用模式3
payload data那就是要传输的数据了
2.WebSocket的握手过程

一开始浏览器和服务器是采用HTTP协议进行发送,浏览器给服务器发送的请求中会有一些特殊的header,分别是Connection:upgrade(升级)和Upgrade:websocket
第一个Connection表示我这次来的目的是想要从HTTP协议进行升级
第二个Upgrade表示我要升级成哪种协议,比如websocket
同理服务器也会在响应报文中,带上这两个特殊的header,表示我也愿意跟你升级一下
其中HTTP的状态码是101,101就表示协议切换
这下就建立了Websocket的连接,就可以使用websocket进行数据传输
3.WebSocket的使用
先导入依赖
java
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
继承TextWebSocketHandler类并重写方法
java
package com.example.java_chatroom.api;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
@Component
public class TestWebSocketAPI extends TextWebSocketHandler {
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
// 这个方法会在 websocket 连接建立成功后, 被自动调用.
System.out.println("TestAPI 连接成功!");
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
// 这个方法是在 websocket 收到消息的时候, 被自动调用的.
System.out.println("TestAPI 收到消息!" + message.toString());
// session 是个会话, 里面就记录了通信双方是谁. (session 中就持有了 websocket 的通信连接)
session.sendMessage(message);
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
// 这个方法是在连接出现异常的时候, 被自动调用的.
System.out.println("TestAPI 连接异常!");
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
// 这个方法是在连接正常关闭后, 被自动调用的
System.out.println("TestAPI 连接关闭!");
}
}
然后再创建一个WebSocketAPI类继承TextWebSocketHandler,重写这几个方法,具体代码就根据实际业务来编写
再对websocket进行配置
java
package com.example.java_chatroom.config;
import com.example.java_chatroom.api.TestWebSocketAPI;
import com.example.java_chatroom.api.WebSocketAPI;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Autowired
private TestWebSocketAPI testWebSocketAPI;
@Autowired
private WebSocketAPI webSocketAPI;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// 通过这个方法, 把刚才创建好的 Handler 类给注册到具体的 路径上.
// 此时当浏览器, websocket 的请求路径是 "/test" 的时候, 就会调用到 TestWebSocketAPI 这个类里的方法.
// registry.addHandler(testWebSocketAPI, "/test");
registry.addHandler(webSocketAPI, "/WebSocketMessage")
// 通过注册这个特定的 HttpSession 拦截器, 就可以把用户给 HttpSession 中添加的 Attribute 键值对
// 往我们的 WebSocketSession 里也添加一份.
.addInterceptors(new HttpSessionHandshakeInterceptor());
}
}
4.Websocket连接断开的判断和重连机制
Websocket内置了心跳包机制,所以在断开后就能感知到是否连接断开,并且提供了onclose和onerror回调,就能在断开后通过回调通知到业务代码
关于重连的机制有六种
(1)立即重连:优点是能够短时间内快速恢复连接,缺点是当无法重连时,会对服务器造成额外压力,导致资源的无效循环利用
(2)指数退避策略:每次重连失败后会增加延迟,采用的是指数退避策略,也就是说每一次失败等待时间会越来越久,可以减少对服务器的压力,并且最后能够重新建立上连接
(3)设置重试限制:设置最大重试次数,防止客户端不断重试连接到不可用的服务器,从而避免了不必要的资源使用
(4)超时设置:为每次重连设置合理的超时值,避免无限期等待服务器响应
(5)用户反馈:在用户界面有明确清晰的连接状态反馈
(6)错误处理:在重连失败后提供明确的错误信息
六.WebTransport
目的是解决传统协议(如:WebSocket)的一些局限性,但使用的广泛程度不如websocket
而WebTransport是基于UDP协议实现的,可以通过流API可靠地发送数据,以及数据报API不可靠地发送数据
相比于WebSocket的优势:
高级功能:WebSocket很难通过单个连接发送不同类型的数据,因为对数据进行分包的时候,无法区分数据中不同类型的编码,因此就需要建立多个连接,然后不同连接发送不同格式的数据。而WebTransport支持多个流(也就是多路复用),即可以通过一个连接发送不同类型的数据,从而减少建立和维护多个连接的开销。
改进性能:WebTransport是一种基于UDP的传输协议,比基于TCP的协议(如Websocket)性能更高,因为缺乏纠错和重传功能,延迟会更低
可靠性:虽然没有纠错和重传功能,但并非不如WebSocket可靠,在设计上就比WebSocket更可靠,为可靠的单向或双向数据传输提供了流API,由底层QUIC协议实现,通过切换为流API这个模式就包含重传、确认、无乱序等可靠机制
安全性:默认提供端到端加密,确保在传输过程中不会被截获或修改,其中一个安全功能是使用"Origin"标头, 为服务器能够验证请求是否来自可靠信源,有助于防止跨站请求伪造