Spring boot集成websocket通信
介绍
websocket是一个持久化的协议,实现了浏览器与服务器的全双工通信。不再像http那样,只有在浏览器发出request之后才有response,websocket能实现服务器主动向浏览器发出消息。
Spring boot项目集成实现
- 在pom.xml中添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
- 集成TextWebSocketHandler类或实现AbstractWebSocketHandler接口
@Component
public class WebSocketMsgHandler extends TextWebSocketHandler {
private final Logger logger = LoggerFactory.getLogger(WebSocketMsgHandler.class);
/**
* 会话保存集合
*/
public static ConcurrentHashMap<String, WebSocketSession> SESSIONS = new ConcurrentHashMap<>();
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
String userId = (String) session.getAttributes().get("userId");
WebSocketSession old = SESSIONS.get(userId);
// TODO 剔除当前节点旧连接,多实例多节点部署时,其他节点的旧会话可以通过MQ广播消息剔除,暂时不实现
if (old != null && !old.getId().equals(session.getId())) {
String oldId = old.getId();
old.sendMessage(new TextMessage("logout"));
old.close();
logger.info("剔除旧连接,当前连接数:{},session key:{},session id:{}", SESSIONS.size(), userId,oldId);
}
SESSIONS.put(userId, session);
logger.info("连接打开,当前连接数:{},session key:{},session id:{}", SESSIONS.size(), userId, session.getId());
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
String userId = (String) session.getAttributes().get("userId");
SESSIONS.remove(userId);
logger.info("连接关闭,当前连接数:{},session key:{}", SESSIONS.size(), userId);
}
@Override
public void handleTransportError(WebSocketSession session, Throwable e) throws Exception {
String userId = (String) session.getAttributes().get("userId");
logger.info("连接异常,userId:" + userId+ "," + e.getMessage(), e);
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
String userId = (String) session.getAttributes().get("userId");
logger.info("接收到前端推送的消息,userId:{},消息内容:{}", userId, message);
}
/**
* 找到会话,使用会话发送消息
*
* 如果是服务集群,则无法确定用户会话所在节点
* 这种情况可借助MQ,通过MQ将消息广播到各个服务节点,服务节点收到广播消息后,如果存在对应的会话则执行消息推送至前端
* @param userId
* @param message
* @throws IOException
*/
public static void sendMsg(String userId, String message) throws IOException {
WebSocketSession session = SESSIONS.get(userId);
if (session != null) {
if (session.isOpen()) {
session.sendMessage(new TextMessage(message));
return;
}
SESSIONS.remove(userId);
}
}
}
- websocket配置
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Autowired
private WebSocketMsgHandler webSocketMsgHandler;
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
HandshakeInterceptor handshakeInterceptor = new HandshakeInterceptor() {
// 在处理握手之前调用
@Override
public boolean beforeHandshake(ServerHttpRequest req, ServerHttpResponse resp, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
// 可在此做鉴权,是进行握手( true )还是中止( false )
// String userId = ((ServletServerHttpRequest) req).getServletRequest().getParameter("userId");
// attributes.put("userId",userId);
return true;
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
//握手完成后调用
}
};
registry.addHandler(webSocketMsgHandler, "/websocket")
.addInterceptors(handshakeInterceptor)
.setAllowedOrigins("*");
registry.addHandler(webSocketMsgHandler, "/websocket_sockjs")
.addInterceptors(handshakeInterceptor)
// 跨域
.setAllowedOrigins("*")
// 启用SockJS,前端需要导入sockjs.js库,并以sockjs的方式连接
.withSockJS();
}
}
- 原生websocket的前端示例页面,使用ws协议
<!DOCTYPE HTML>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<base href="http://localhost:8080/">
<title>My WebSocket</title>
</head>
<body lang="en">
<table>
<tr>
<td style="width: 80px;text-align: right;">url地址:</td>
<td>
<input id="websocket_url" type="text" value="ws://127.0.0.1:2001/websocket"/>
<button onclick="openWs()" >Open</button>
<button onclick="closeWs()" >Close</button>
</td>
</tr>
<tr>
<td style="width: 80px;text-align: right;">用户id:</td>
<td><input id="userId" type="text" value="123456"/></td>
</tr>
<tr>
<td style="width: 80px;text-align: right;">消息:</td>
<td>
<input id="msg" type="text"/>
<button onclick="send()">Send</button>
</td>
</tr>
</table>
<div>服务器推送的消息:</div>
<div id="message" style="border: 1px solid grey;width: 600px;min-height: 150px;"></div>
</body>
<script type="text/javascript">
var websocket = null;
var isOpen = false;
function openWs() {
if (websocket != null) {
websocket.close();
}
var ws_url = document.getElementById('websocket_url').value;
var userId = document.getElementById('userId').value;
websocket = new WebSocket(ws_url + "?userId=" + userId);
//连接发生错误的回调方法
websocket.onerror = function (e) {
setMessageInnerHTML("error");
console.info("error:");
console.info(e);
};
//连接成功建立的回调方法
websocket.onopen = function (event) {
isOpen = true;
setMessageInnerHTML("open");
console.info("onopen:");
console.info(event)
}
//接收到消息的回调方法
websocket.onmessage = function (event) {
setMessageInnerHTML(event.data);
}
//连接关闭的回调方法
websocket.onclose = function (e) {
isOpen = false;
setMessageInnerHTML("close");
console.info("onclose:");
console.info(e);
}
}
//关闭连接
function closeWs() {
if (websocket != null) {
websocket.close();
}
}
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function () {
closeWs();
}
//将消息显示在网页上
function setMessageInnerHTML(innerHTML) {
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}
//发送消息
function send() {
if (!isOpen) {
setMessageInnerHTML("websocket is close");
return;
}
var message = document.getElementById('msg').value;
websocket.send(message);
}
</script>
</html>
- 原生sockjs的前端示例页面,使用http协议,需导入sockjs.js库
<!DOCTYPE HTML>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<base href="http://localhost:8080/">
<title>My WebSocket</title>
<script src="https://cdn.bootcdn.net/ajax/libs/sockjs-client/1.5.0/sockjs.js"></script>
</head>
<body lang="en">
<table>
<tr>
<td style="width: 80px;text-align: right;">url地址:</td>
<td>
<input id="websocket_url" type="text" value="http://127.0.0.1:2001/websocket_sockjs"/>
<button onclick="openWs()" >Open</button>
<button onclick="closeWs()" >Close</button>
</td>
</tr>
<tr>
<td style="width: 80px;text-align: right;">用户id:</td>
<td><input id="userId" type="text" value="123456"/></td>
</tr>
<tr>
<td style="width: 80px;text-align: right;">消息:</td>
<td>
<input id="msg" type="text"/>
<button onclick="send()">Send</button>
</td>
</tr>
</table>
<div>服务器推送的消息:</div>
<div id="message" style="border: 1px solid grey;width: 600px;min-height: 150px;"></div>
</body>
<script type="text/javascript">
var websocket = null;
var isOpen = false;
function openWs() {
if (websocket != null) {
websocket.close();
}
var ws_url = document.getElementById('websocket_url').value;
var userId = document.getElementById('userId').value;
websocket = new SockJS(ws_url + "?userId=" + userId);
//连接发生错误的回调方法
websocket.onerror = function (e) {
setMessageInnerHTML("error");
console.info("error:");
console.info(e);
};
//连接成功建立的回调方法
websocket.onopen = function (event) {
isOpen = true;
setMessageInnerHTML("open");
console.info("onopen:");
console.info(event)
}
//接收到消息的回调方法
websocket.onmessage = function (event) {
setMessageInnerHTML(event.data);
}
//连接关闭的回调方法
websocket.onclose = function (e) {
isOpen = false;
setMessageInnerHTML("close");
console.info("onclose:");
console.info(e);
}
}
//关闭连接
function closeWs() {
if (websocket != null) {
websocket.close();
}
}
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function () {
closeWs();
}
//将消息显示在网页上
function setMessageInnerHTML(innerHTML) {
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}
//发送消息
function send() {
if (!isOpen) {
setMessageInnerHTML("websocket is close");
return;
}
var message = document.getElementById('msg').value;
websocket.send(message);
}
</script>
</html>