文章示例环境配置信息
jdk版本:1.8
开发工具:Intellij iDEA 2020.1
springboot:2.3.9.RELEASE
什么是WebSocket?
WebSocket,是HTML5下一种新的协议,支持web浏览器和服务器端之间双向全双工通信 ,基于TCP协议实现。
WebSocket主要特性
1、WebSocket是一种全新的协议,不属于http无状态协议,协议名为“ws”;
2、WebSocket是基于TCP的,属于可靠性传输协议,按OSI网络模型划分,归属应用层协议;
3、WebSocket是双向通信协议,可以双向发送或接受信息,与http相比最明显的区别就是允许服务端向客户端浏览器主动推送数据,而HTTP是单向的,只能由客户端发起请求,服务端针对客户端请求作出响应;
4、使用WebSocket通信前,浏览器和服务器端需要先进行握手建立连接;
5、WebSocket在握手建立连接的时候,数据是通过HTTP传输的,但是在建立连接之后,真正传输数据的时候是不需要HTTP协议的;
6、WebSocket建立连接后,数据是以帧序列的形式传输,浏览器端与服务器端的发送和接受消息是在同一个持久连接上发起,即浏览器端与服务器端的WebSocket连接未断开时,不需要重新发起连接请求就可以多次发送和接受消息;在并发及交互负载流量大的情况下,极大节省了网络带宽资源,有明显的性能优势,实现了真正的“长连接”;
WebSocket基本使用演示
通过WebSocket功能特性来看,WebSocket是一个非常重要的有实际应用意义的通信协议,下面以单机模式下的Springboot项目中集成WebSocket,来学习一下WebSocket使用方法及主要功能特性。
后端
1、引入WebSocket所需要starter模块依赖包;
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2、定义WebSocketConfig配置类,引入WebSocket的Springboot的自动配置类ServerEndpointExporter,这个配置类主要用于扫描WebSocket相关的注解;
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
3、定义一个WebSocket类,使用@Component注解把WebSocket类注册到Spring容器中,使用@ServerEndpoint注解把当前类定义成一个服务器端并标记好客户端发起WebSocket连接请求的URL;在WebSocket类内用到了四个注解:@Open、@Close、@OnMessage、@OnError:
@Open:当WebSocket连接建立成功后会触发这个注解修饰的方法;
@Close:当WebSocket连接关闭或中断后会触发这个注解修饰的方法;
@OnMessage:当客户端发送消息到服务端时,会触发这个注解修饰的方法;
@OnError:当 websocket 建立连接时出现异常会触发这个注解修饰的方法;
@Component
@ServerEndpoint("/websocket/{userId}")
@Slf4j
public class WebSocket {
private Session session;
private static Map<String, WebSocket> webSocketMap = new ConcurrentHashMap<>();
/**
* 客户端发起websocket连接请求成功建立连接时触发
* @param session
* @param userId
*/
@OnOpen
public void onOpen(Session session, @PathParam("userId") String userId) {
this.session = session;
this.webSocketMap.put(userId, this);
log.info("【websocket消息】有新的连接, 总数:{}", webSocketMap.size());
}
/**
* 客户端关闭时websocket连接时触发
*/
@OnClose
public void onClose(@PathParam("userId") String userId) throws IOException {
session.close();
this.webSocketMap.remove(userId);
log.info("【websocket消息】连接断开, 总数:{}", webSocketMap.size());
}
/**
* 客户端向服务端发送消息时触发
* @param session
* @param message
* @throws IOException
*/
@OnMessage
public void onMessage(Session session, String message) throws IOException {
Map<String, List<String>> requestParameterMap = session.getRequestParameterMap();
String userId = requestParameterMap.get("userId").get(0);
log.info("【websocket消息】收到客户端发来的消息:{}", message);
if (message.startsWith("hello")) {
session.getBasicRemote().sendText("你好,"+userId+"!信息已经收到,欢迎光临" + message.replace("hello", ""));
}
}
/**
* 当 websocket 建立连接时出现异常会触发
* @param session
*/
@OnError
public void onError(Session session){
Map<String, List<String>> requestParameterMap = session.getRequestParameterMap();
String userId = requestParameterMap.get("userId").get(0);
log.info("连接异常,请求参数:"+userId);
}
}
前端
在resources目录下的静态资源目录static下,新建一个index.html;
index.html的逻辑很简单,就是在页面上放三个按钮,分别用于建立websocket连接、关闭websocket连接、向服务端发送文本消息;具体的实现步骤是这样的:
1、引入jquery.js,这样就可以使用"$"符号了;
2、在body标签内分别放置三个按钮,标签id分别是open、close、send;
3、定义一个div标签,用来显示操作信息和从服务端发送过来的文本消息;
4、在<scrip>标签内,声明websocket变量,然后在open按钮的点击事件触发进,向服务端发起连接请求,这里要特别注意一下,请求的URL是:“ws://172.18.229.61/websocket/gaox2”;与http请求类似的是,http请求的开头是http,websocket请求的开头是ws;
5、与服务端比较类似的是,客户端在websocket连接建立后,也会注册websocket连接事件在连接成功建立、连接发生错误、接收到服务端消息、连接关闭时的回调方法;
6、websocket连接建立成功后,在发送信息、关闭连接按钮被触发时,编写相应的处理逻辑;
<!DOCTYPE HTML>
<html lang="en">
<head>
<title>websocket测试</title>
<meta charset="UTF-8">
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
</head>
<body>
<div id="message"></div>
<input type="button" name="" id="open" value="建立连接"/>
<input type="button" name="" id="close" value="关闭连接">
<input type="button" name="" id="send" value="发送信息">
</body>
<script type="text/javascript">
var websocket = null;
$("#open").click(function () {
//判断当前浏览器是否支持WebSocket
if ('WebSocket' in window) {
websocket = new WebSocket("ws://172.18.229.61/websocket/gaox2");
} else {
alert('Not support websocket')
}
//连接发生错误的回调方法
websocket.onerror = function () {
document.getElementById('message').innerHTML += ("发生错误") + '<br/>';
};
//连接成功建立的回调方法
websocket.onopen = function (event) {
document.getElementById('message').innerHTML += ("建立连接") + '<br/>';
}
//接收到消息的回调方法
websocket.onmessage = function (event) {
console.log(event.data);
document.getElementById('message').innerHTML += event.data + '<br/>';
}
//连接关闭的回调方法
websocket.onclose = function () {
document.getElementById('message').innerHTML += ("关闭连接") + '<br/>';
}
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function () {
alert("已关闭连接");
websocket.close();
}
})
var i = 0;
$("#send").click(function () {
i = i + 1;
websocket.send("hello" + i);
})
$("#close").click(function () {
websocket.close();
})
</script>
</html>
测试结果
总结
至此,在Springboot项目中,如何使用WebSocket进行数据交互的基本方法就是这样了。其实还是挺简单的,主要就是两部分:1、前端如何发起请求和处理服务端发回的消息;2、后端处理如何处理前端发起的websocket请求以及如何主动发消息给浏览器端;
但是大家有没有发现一个问题:单机模式下,浏览器端向服务端发起websocket请求,服务端处理请求并主动发消息给浏览器端,这种点对点的通信确实不复杂,但是实际的业务场景中,大部分项目都是分布式部署的,一个工程布署了多个服务节点,前端并不直接请求具体服务节点,而是先到nginx或其他代理服务器,通过nginx的负载均衡机制再转发到具体的服务节点,浏览器端在发起websocket连接时,直接连接到具体的服务节点显然不太合适的,那么向nginx发起websocket连接,再由其转发到具体的节点是否可以呢?又是怎么配置的呢?另外服务端向客户端发送消息时,用到了session,当然这里的session和servlet的session不是一个session,servlet中的session是jvm级别的,本身是不支持跨节点共享的,那websocket的session是否支持跨节点共享?如果不支持,则用什么办法实现session的共享?
这篇文章和大家一块简单学习一下websocket的基本使用方法,下一篇文章再来和大家一块分享一下,如何把websocket应用到实际的业务开发中。
版权归原作者 凡夫贩夫 所有, 如有侵权,请联系我们删除。