refer:https://blog.youkuaiyun.com/z1790424577/article/details/81011416
https://blog.youkuaiyun.com/zxwu_1993/article/details/81034087
前言
思考:像这样的消息功能怎么实现?
如果网页不刷新,服务端有新消息如何推送到浏览器?
解决方案,采用轮询的方式。即:通过js不断的请求服务器,查看是否有新数据,如果有,就获取到新数据。
这种解决方法是否存在问题呢?
当然是有的,如果服务端一直没有新的数据,那么js也是需要一直的轮询查询数据,这就是一种资源的浪费。
那么,有没有更好的解决方案? 有!那就是采用WebSocket技术来解决。
一、什么是WebSocket
WebSocker是一个保持web客户端与服务器长链接的技术,它实现了浏览器与服务器全双工通信(full-duplex)。
这样在两端通信过程中 ,任意一端如果有消息想 想发送给对端时,无需等待对端发送一个请求了。
它是一种在单个TCP连接基础上 进行全双工通讯协议,一开始的握手需要借助HTTP请求完成。
什么是全双工?
全双工(Full Duplex)是通讯传输的一个术语。通信允许数据在两个方向上同时传输,它在能力上相当
于两个单工通信方式的结合。全双工指可以同时(瞬时)进行信号的双向传输(A→B且B→A)。指
A→B的同时B→A,是瞬时同步的。
好处:
它实现了浏览器与服务器全双工通信,它能够实现Web浏览器和服务器之间的异步通信。能更好的节省服务器资源和带宽并达到实时通讯。
在海量并发及客户端与服务器交互负载流量大的情况下,极大的节省了网络带宽资源的消耗,有明显的性能优势,且客户端发送和接受消息是在同一个持久连接上发起,实时性优势明显。
WebSocket 请求响应客户端服务器交互图:
浏览器中查看建立连接的过程:
二、SpringBoot中使用WebSocket步骤
1、导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2、编写消息处理类
较简单的方式就是直接继承AbstractWebSocketHandler
,并覆写其中的处理方法,或者继承TextWebSocketHandler
.
/**
* websocket 消息处理类
*
* @author liangqi
* @date 2021/5/23 0:07
*/
public class MySocketHandler extends TextWebSocketHandler {
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
System.out.println("获取到消息 >> " + message.getPayload());
session.sendMessage(new TextMessage("消息已收到"));
if (message.getPayload().equals("10")) {
for (int i = 0; i < 10; i++) {
session.sendMessage(new TextMessage("消息 -> " + i));
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
Object uuid = session.getAttributes().get("uuid");
session.sendMessage(new TextMessage("欢迎【" + uuid + "】连接到ws服务"));
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
System.out.println("断开连接!");
}
}
3、编写握手拦截器
在进行三次握手建立连接之前,若需要进行一些操作,如:校验操作。则可以编写一个握手拦截器。
/**
* 握手拦截器
* 可以在三次握手建立连接之前,进行拦截。写一些业务逻辑,如;校验等
*
* @author liangqi
* @date 2021/5/23 0:25
*/
@Component
public class MyHandShakeInterceptor implements HandshakeInterceptor {
/**
* 握手之前进行判断,若返回false,则不建立链接
*
* @param serverHttpRequest
* @param serverHttpResponse
* @param webSocketHandler
* @param map
* @return
* @throws Exception
*/
@Override
public boolean beforeHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map<String, Object> map) throws Exception {
//将用户id放入socket处理器的会话(WebSocketSession)中
map.put("uuid", 1001);
System.out.println("开始握手。。。。。。。");
return true;
}
/**
* 握手成功,建立连接之后执行
* @param serverHttpRequest
* @param serverHttpResponse
* @param webSocketHandler
* @param e
*/
@Override
public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {
System.out.println("握手成功啦。。。。。。");
}
}
4、WebSocket的配置类
/**
* websocket 消息配置类
*
* @author liangqi
* @date 2021/5/23 0:11
*/
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Autowired
WebSocketHandler handler;
@Autowired
private MyHandShakeInterceptor interceptor;
/**
* handler 与 请求url进行映射。
* @param registry
*/
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(handler, "/ws")
.setAllowedOrigins("*")
.addInterceptors(interceptor);
}
@Bean
public WebSocketHandler mySocketHandler() {
return new MySocketHandler();
}
}
5、测试:
这样,在html页面中可以直接访问这个类配置的url,就可以访问到对应的Handler,从而进行相关逻辑的处理,而不是经过controller层来管理,有了这个类,WebSocketConfig就相当于是controller层了。相关逻辑的处理代码位于自定义的 Handler()
中。
可以通过 工具进行测试: http://www.websocket-test.com/