SpringBoot集成WebSocket
前言
案例github地址(如果有用点个star呗) https://github.com/chenxiban/BlogCaseSet.git
WebSocket 介绍
WebSocket 是一种网络通信协议。WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通
讯的协议。
WebSocket的引入
了解计算机网络协议的人,应该都知道:HTTP 协议是一种无状态的、无连接的、单向的应用层协议。它采
用了请求/响应模型。通信请求只能由客户端发起,服务端对请求做出应答处理。
这种通信模型有一个弊端:HTTP 协议无法实现服务器主动向客户端发起消息。
这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。大多数 Web 应用程
序将通过频繁的异步JavaScript和XML(AJAX)请求实现长轮询。轮询的效率低,非常浪费资源(因为必须
不停连接,或者 HTTP 连接始终打开)。如图:
因此,工程师们一直在思考,有没有更好的方法。WebSocket 就是这样发明的。WebSocket 连接允许客户
端和服务器之间进行全双工通信,以便任一方都可以通过建立的连接将数据推送到另一端。WebSocket 只需
要建立一次连接,就可以一直保持连接状态。这相比于轮询方式的不停建立连接显然效率要大大提高。 如
图:
WebSocket对象
以下 API 用于创建 WebSocket 对象。
var Socket = new WebSocket(url, [protocol]);
以上代码中的第一个参数 url
, 指定连接的 URL
。第二个参数 protocol
是可选的,指定了可接受的子协议。
WebSocket属性
以下是WebSocket
对象的属性,假定我们使用了以上代码创建了WebSocket对象。
属性 | 描述 |
---|---|
Socket.readyState | 只读属性 readyState表示连接状态,可以是以下值:0 – 表示连接尚未建立。1 – 表示连接已建立,可以进行通信。2 – 表示连接正在进行关闭。3 – 表示连接已经关闭或者连接不能打开。 |
Socket.bufferedAmount | 只读属性 bufferedAmount 已被 send() 放入正在队列中等待传输,但是还没有发出的 UTF-8 文本字节数。 |
WebSocket事件
以下是 WebSocket对象的相关事件。假定我们使用了以上代码创建了 Socket 对象:
事件 | 事件处理程序 | 描述 |
---|---|---|
open | Socket.onopen | 连接建立时触发 |
message | Socket.onmessage | 客户端接收服务端数据时触发 |
error | Socket.onerror | 通信发生错误时触发 |
close | Socket.onclose | 连接关闭时触发 |
WebSocket方法
以下是 WebSocket对象的相关方法。假定我们使用了以上代码创建了 Socket 对象:
方法 | 描述 |
---|---|
Socket.send() | 使用连接发送数据 |
Socket.close() | 关闭连接 |
SpringBoot整合WebSocket
SpringBoot对WebSocket提供了很好的支持,具体集成过程如下:
添加依赖
springboot的高级组件会自动引用基础的组件,像spring-boot-starter-websocket就引入了spring-boot-starter-web和spring-boot-starter,所以不要重复引入 。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
<version>1.3.5.RELEASE</version>
</dependency>
项目结构
搭建项目的整体结构,如下图:
WebSocket客户端实现
客户端首先创建WebSocket对象,与服务器端建立连接,接下来开始通信,主要代码如下:(只是个栗子)
<script type="text/javascript">
var wsurl = "ws://localhost:800/myWebsocket";
//WebSocket服务器地址
var sockurl = "ws:localhost:800/myWebsocket/sockjs";
//基本没用
var websocket; //声明一个WebSocket客户端
//检测浏览器兼容性
if ('WebSocket' in window) {
websocket = new
WebSocket(wsurl); //当前所有浏览器全部都支持
console.log("当前所有浏览器全部都支持 WebSocket in window =>" + websocket);
} else if ('MozWebSocket' in window) {
websocket = new MozWebSocket(wsurl); //基本没用
console.log("基本没用 MozWebSocket in window =>" + websocket);
} else {
websocket = new SockJS(sockurl); //基本没用
console.log("基本没用 else new SockJS=>" + websocket);
}
//********* 成功连接服务器*********
websocket.onopen = function(event) {
console.log("WebSocket:成功连接服务器 ");
};
//********* 接收服务器消息 *********
websocket.onmessage = function(event) {
console.log("wesocket接收服务器数据event=>" + event);
console.log("wesocket接收服务器数据event.data=>" + event.data);
// var mage=JSON.parse(event.data);
//event.data是JSON格式字符串,转为JS对象
// console.log("wesocket接收服务器data=>"+mage);
};
//********* 服务器发生异常错误 *********
websocket.onerror = function(event) {
console.log(event);
};
//********* 服务器关闭 *********
websocket.onclose = function(event) {
console.log("WebSocket:已关闭");
console.log("WebSocket:服务器发生异常错误 ");
console.log(event);
};
</script>
- onopeng事件,当与服务端成功创建连接时触发。
- onmessage事件,接收服务器发送来的消息。
- onerror事件,当服务器端发生异常时触发。
- onclose事件,当服务器端关闭时触发。
- send方法,向服务器端发送信息。
编写index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>WebSocket客户端</title>
<script type="text/javascript" src="jquery.min.js" ></script>
</head>
<body>
<input type="text" id="msg" /><button onclick="sendMsg()">发给服务器</button>
<script type="text/javascript">
//6. 主动发送数据给服务器
function sendMsg(){
websocket.send( $('#msg').val() );
}
//7. 主动关闭通信
function close(){
websocket.close();
}
</script>
<script type="text/javascript">
//WebSocket服务器地址
var wsurl = "ws://localhost:8080/SpringBootWebSocket?token=cocococococcoc¶m=张三";
//1. 与服务器建立websocket连接
var websocket = new WebSocket(wsurl);
// 2. 连接成功
websocket.onopen = function(event){
console.log("成功与服务器建立连接!!!");
console.log(event);
}
//3. 接收服务器数据
websocket.onmessage = function(event){
console.log("wesocket接收服务器数据event.data=>"+event.data);
console.log(event);
}
//4. 服务器发生异常错误
websocket.onerror = function(event) {
console.log("WebSocket:服务器发生异常错误 ");
console.log(event);
};
//5. 服务器关闭
websocket.onclose = function(event) {
console.log("WebSocket:已关闭");
console.log(event);
};
</script>
</body>
</html>
WebSocket服务器端实现
- 编写通信握手拦截器。
该拦截器的作用就是在连接成功前和成功后增加一些额外的功能,此处可记录当前建立连接的用户信息,用
于定向信息发送的动能前提。代码如下:
编写WebSocket通信 握手拦截器
它只拦截握手SpringBootHandshakeInterceptor
实现HandshakeInterceptor
package com.ysd.boot.websocket;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
/**
* WebSocket通信 握手拦截器 它只拦截握手
*
*/
@Component
public class SpringBootHandshakeInterceptor implements HandshakeInterceptor {
/**
* 握手之前 ServerHttpRequest : 请求对象 ServerHttpResponse : 响应对象 WebSocketHandler :
* WebSocket服务处理类,在这里指的是SpringBootWebSocketHandler attributes :
* WebSocketSession.getAttributes()
*/
@Override
public boolean beforeHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse,
WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
ServletServerHttpRequest servletServerHttpRequest = (ServletServerHttpRequest) serverHttpRequest;
HttpServletRequest request = servletServerHttpRequest.getServletRequest();// 得到Http协议的请求对象
HttpSession session = request.getSession(true);
System.out.println("握手拦截器^^^^^^^^^^^^ request.getParameter(\"param\")=>" + request.getParameter("param"));
System.out.println("握手拦截器^^^^^^^^^^^^ request.getParameter(\"token\")=>" + request.getParameter("token"));
System.out.println("握手拦截器^^^^^^^^^^^^ HttpSession.getAttribute(\"user\")=>" + session.getAttribute("user"));
// 数据中转 可以把http协议的会话对象数据中转到ws协议的会话对象中
attributes.put("param", request.getParameter("param"));
// 非前后端分离架构:把HttpSession中的数据中转到WebSocketSession中
if (session.getAttribute("user") != null)
attributes.put("user", session.getAttribute("user"));
// 如果是前后端分离架构:把Http协议中的token令牌中转到ws协议的WebSocketSession中
attributes.put("token", request.getParameter("token"));
return true;
}
/**
* 握手之后
*/
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Exception exception) {
// TODO Auto-generated method stub
}
}
该拦截器需要实现HandshakeInterceptor接口,其中方法的作用如下:
- beforeHandshake:在调用handler前处理方法。常用在注册用户信息,绑定WebSocketSession,在
handler里根据用户信息获取WebSocketSession发送消息。 - afterHandshake:在握手之后执行该方法. 无论是否握手成功都指明了响应状态码和相应头
编写WebSocket
服务配置类SpringBootWebSocketConfigurer
实现WebMvcConfigurer
, WebSocketConfigurer
由于这个类是配置类,即
WebSocket
的入口,用于给WebSocket
服务配置URL
,比如绑定客户端url,所以
需要在spring mvc
配置文件中加入对这个类的扫描,第一个addHandler
是对正常连接的配置,第二个是如果
浏览器不支持websocket
,使用socketjs
模拟websocket
的连接。该类需要实现WebSocketConfigurer
接口,
重写registerWebSocketHandlers
方法,这是一个核心实现方法,配置websocket
入口,允许访问的域、注
册Handler
、SockJs
支持和拦截器。参考代码如下:
package com.ysd.boot.websocket;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
/**
* WebSocket服务配置类
*
*/
// @Component //非单例模式bean
@Configuration // 单例模式bean
@EnableWebSocket // 启动WebSocket服务器
//public class SpringBootWebSocketConfigurer extends WebMvcConfigurerAdapter implements WebSocketConfigurer{ //SpringBoot1.0系列用法
public class SpringBootWebSocketConfigurer implements WebMvcConfigurer, WebSocketConfigurer { // SpringBoot2.0系列用法
@Autowired
private SpringBootWebSocketHandler handler;
@Autowired
private SpringBootHandshakeInterceptor handshakeInterceptor;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// -------------------- 允许跨域访问WebSocket ------------------------
String[] allowsOrigins = { "*" };// 允许连接的域,只能以http或https开头
/**
* http://localhost:8080 http://localhost:8080/index.html
*/
// 7. 设置websocket服务器地址 ws://localhost:8080/SpringBootWebSocket
registry.addHandler(handler, "/SpringBootWebSocket").addInterceptors(handshakeInterceptor)
.setAllowedOrigins(allowsOrigins);
// registry.addHandler(handler, "/SpringBootWebSocket/sockjs").addInterceptors(handshakeInterceptor).setAllowedOrigins(allowsOrigins).withSockJS();//兼容以前老版本的sockJS
}
}
其中的方法含义解析如下:
- registry.addHandler():注册和路由的功能,当客户端发起websocket连接,把/path交给对应的
handler处理,而不实现具体的业务逻辑,可以理解为收集和任务分发中心。 - setAllowedOrigins(String[] domains),允许指定的域名或IP(含端口号)建立长连接,如果只允许自家域
名访问,这里轻松设置。如果不限时使用”*”号,如果指定了域名,则必须要以http或https开头。 - addInterceptors,顾名思义就是为handler添加拦截器,可以在调用handler前后加入我们自己的逻辑
代码。
若需要注册路由,给
WebSocket
服务配置URL
,则在registerWebSocketHandlers
方法中添加如下代码
即可:
//兼容以前老版本的sockJS
// registry.addHandler(handler, "/SpringBootWebSocket/sockjs").addInterceptors(handshakeInterceptor).setAllowedOrigins(allowsOrigins).withSockJS();
编写自定义实现WebSocket
服务处理类SpringBootWebSocketHandler
实现WebSocketHandler
该类用于处理与客户端的通话过程。整个通话过程主要有以下几个阶段:
- 客户端成功连接服务器(@OnOpen):当连接成功的时候,会触发onopen方法,这里对应的方法为
- afterConnectionEstablished。
接收客户端消息(@OnMessage):接收文本消息,并发送出去。当UI客户端在用js调用
websocket.send()时候,会调用该方法 。在客户端通过Websocket的API发送的消息会经过这里,然后
进行相应的处理。 - 客户端发生异常错误(@OnError):消息传输抛出异常时执行,当消息传输错误时进行处理。
- 客户端关闭(@OnClose): 客户端关闭连接后执行。
具体代码实现案例如下所示:
package com.ysd.boot.websocket;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;
/**
* WebSocket服务处理类
*/
@Component
public class SpringBootWebSocketHandler implements WebSocketHandler {
// 存储所有客户端的会话WebSocketSession,key使用客户端的唯一标识方便存取
private static Map<String, WebSocketSession> allWebSocketSession = new HashMap<String, WebSocketSession>();
/**
* 1. 客户端成功建立连接时触发
*/
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
System.out.println("客户端成功建立连接=>" + session.getId());
// 在ws服务类中任何位置都可以使用以下取值
System.out.println("WebSocketSession.getAttributes().get(\"param\")" + session.getAttributes().get("param"));
System.out.println("WebSocketSession.getAttributes().get(\"user\")" + session.getAttributes().get("user"));
System.out.println("WebSocketSession.getAttributes().get(\"token\")" + session.getAttributes().get("token"));
// 存储所有客户端的WebSocketSession
allWebSocketSession.put((String) session.getAttributes().get("param"), session);
}
/**
* 2. 接收客户端数据时触发
*/
@Override
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
System.out.println("接收客户端数据=>" + message.getPayload().toString());
// 给客户端回一句话意思意思,随便给客户端发数据
this.send(session, "&&&&&&服务器回复的&&&&&&" + message.getPayload().toString());
}
/**
* 3. 通信异常时触发
*/
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
System.out.println("通信异常=>" + session.getId());
}
/**
* 4.客户端关闭连接时触发
*/
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
System.out.println("客户端关闭连接=>" + session.getId());
// 移除通行关闭的客户端
allWebSocketSession.remove((String) session.getAttributes().get("param"));
}
/**
* 是否支持分段传输
*/
@Override
public boolean supportsPartialMessages() {
// TODO Auto-generated method stub
return false;// 一次输出完毕
}
/**
* 5.服务器主动发送数据
*
* @param webSocketSession
* @param msg
*/
public void send(WebSocketSession webSocketSession, String msg) {
try {
webSocketSession.sendMessage(new TextMessage(msg));
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 6. 服务器主动关闭通信
*
* @param webSocketSession
* @param msg
*/
public void close(WebSocketSession webSocketSession, String msg) {
try {
webSocketSession.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
除了以上各阶段方法外,WebSocket处理类中还有一些其他处理方法,如supportsPartialMessages()方法用
于WebSocketHandler是否处理部分消息。如果这个标志被设置为true,而底层的WebSocket服务器支持部
分消息,那么一个大的WebSocket消息,或者一个未知的大小可能会被拆分,并且可能会收到多个调用的
handleMessage(WebSocketSession,WebSocketMessage)。标记WebSocketMessage.isLast()表示该消息是
否是部分消息,以及它是否是最后一部分:
/**
* 是否支持分段传输
*/
@Override
public boolean supportsPartialMessages() {
// TODO Auto-generated method stub
return false;// 一次输出完毕
}
服务器发送消息(当然也可以写成向某个用户发送)
/**
* 5.服务器主动发送数据
*
* @param webSocketSession
* @param msg
*/
public void send(WebSocketSession webSocketSession, String msg) {
try {
webSocketSession.sendMessage(new TextMessage(msg));
} catch (IOException e) {
e.printStackTrace();
}
}
编写controller控制器,代码如下:
package com.ysd.boot.controller;
import javax.servlet.http.HttpSession;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class WelcomeController {
/**
* http://localhost:8080
* @return
*/
@RequestMapping("/")
public String welcome(HttpSession session) {
session.setAttribute("user", "我是HttpSession中的数据");
return "index.html";
}
}
注意:配置spring容器,在需要使用时可自动注入,我们可以通过这种方式把想用的工具类实例对象先放入Spirng容器中,在需要使用到的时候自动注入即可。例如配置一个ObjectMapper实例对象和SimpleDateFormat实例对象,参考代码如下:
最后
-
更多参考精彩博文请看这里:《陈永佳的博客》
-
喜欢博主的小伙伴可以加个关注、点个赞哦,持续更新嘿嘿!
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/97604.html