Spring boot項(xiàng)目整合WebSocket方法
WebSocket是一種在單個(gè)TCP連接上進(jìn)行全雙工通信的協(xié)議。WebSocket通信協(xié)議于2011年被IETF定為標(biāo)準(zhǔn)RFC 6455,并由RFC7936補(bǔ)充規(guī)范。WebSocket API也被W3C定為標(biāo)準(zhǔn)。WebSocket使得客戶端和服務(wù)器之間的數(shù)據(jù)交換變得更加簡(jiǎn)單,允許服務(wù)端主動(dòng)向客戶端推送數(shù)據(jù)。在WebSocket API中,瀏覽器和服務(wù)器只需要完成一次握手,兩者之間就直接可以創(chuàng)建持久性的連接,并進(jìn)行雙向數(shù)據(jù)傳輸。
springboot對(duì)websocket支持很友好,只需要繼承webSocketHandler類,重寫(xiě)幾個(gè)方法就可以了
這個(gè)類是對(duì)消息的一些處理,比如是發(fā)給一個(gè)人,還是發(fā)給所有人,并且前端連接時(shí)觸發(fā)的一些動(dòng)作
/**
* 創(chuàng)建一個(gè)WebSocket server
*
* @ClassName: CustomWebSocketHandler
* @Description: TODO
* @author OnlyMate
* @Date 2018年8月16日 下午3:17:34
*
*/
@Service
public class CustomWebSocketHandler extends TextWebSocketHandler implements WebSocketHandler {
private Logger logger = LoggerFactory.getLogger(CustomWebSocketHandler.class);
// 在線用戶列表
private static final Map<String, WebSocketSession> users;
// 用戶標(biāo)識(shí)
private static final aString CLIENT_ID = "mchNo";
static {
users = new HashMap<>();
}
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
logger.info("成功建立websocket-spring連接");
String mchNo = getMchNo(session);
if (StringUtils.isNotEmpty(mchNo)) {
users.put(mchNo, session);
session.sendMessage(new TextMessage("成功建立websocket-spring連接"));
logger.info("用戶標(biāo)識(shí):{},Session:{}", mchNo, session.toString());
}
}
@Override
public void handleTextMessage(WebSocketSession session, TextMessage message) {
logger.info("收到客戶端消息:{}", message.getPayload());
JSONObject msgJson = JSONObject.parseObject(message.getPayload());
String to = msgJson.getString("to");
String msg = msgJson.getString("msg");
WebSocketMessage<?> webSocketMessageServer = new TextMessage("server:" +message);
try {
session.sendMessage(webSocketMessageServer);
if("all".equals(to.toLowerCase())) {
sendMessageToAllUsers(new TextMessage(getMchNo(session) + ":" +msg));
}else {
sendMessageToUser(to, new TextMessage(getMchNo(session) + ":" +msg));
}
} catch (IOException e) {
logger.info("handleTextMessage method error:{}", e);
}
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
if (session.isOpen()) {
session.close();
}
logger.info("連接出錯(cuò)");
users.remove(getMchNo(session));
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
logger.info("連接已關(guān)閉:" + status);
users.remove(getMchNo(session));
}
@Override
public boolean supportsPartialMessages() {
return false;
}
public void sendMessage(String jsonData) {
logger.info("收到客戶端消息sendMessage:{}", jsonData);
JSONObject msgJson = JSONObject.parseObject(jsonData);
String mchNo = StringUtils.isEmpty(msgJson.getString(CLIENT_ID)) ? "陌生人" : msgJson.getString(CLIENT_ID);
String to = msgJson.getString("to");
String msg = msgJson.getString("msg");
if("all".equals(to.toLowerCase())) {
sendMessageToAllUsers(new TextMessage(mchNo + ":" +msg));
}else {
sendMessageToUser(to, new TextMessage(mchNo + ":" +msg));
}
}
/**
* 發(fā)送信息給指定用戶
* @Title: sendMessageToUser
* @Description: TODO
* @Date 2018年8月21日 上午11:01:08
* @author OnlyMate
* @param mchNo
* @param message
* @return
*/
public boolean sendMessageToUser(String mchNo, TextMessage message) {
if (users.get(mchNo) == null)
return false;
WebSocketSession session = users.get(mchNo);
logger.info("sendMessage:{} ,msg:{}", session, message.getPayload());
if (!session.isOpen()) {
logger.info("客戶端:{},已斷開(kāi)連接,發(fā)送消息失敗", mchNo);
return false;
}
try {
session.sendMessage(message);
} catch (IOException e) {
logger.info("sendMessageToUser method error:{}", e);
return false;
}
return true;
}
/**
* 廣播信息
* @Title: sendMessageToAllUsers
* @Description: TODO
* @Date 2018年8月21日 上午11:01:14
* @author OnlyMate
* @param message
* @return
*/
public boolean sendMessageToAllUsers(TextMessage message) {
boolean allSendSuccess = true;
Set<String> mchNos = users.keySet();
WebSocketSession session = null;
for (String mchNo : mchNos) {
try {
session = users.get(mchNo);
if (session.isOpen()) {
session.sendMessage(message);
}else {
logger.info("客戶端:{},已斷開(kāi)連接,發(fā)送消息失敗", mchNo);
}
} catch (IOException e) {
logger.info("sendMessageToAllUsers method error:{}", e);
allSendSuccess = false;
}
}
return allSendSuccess;
}
/**
* 獲取用戶標(biāo)識(shí)
* @Title: getMchNo
* @Description: TODO
* @Date 2018年8月21日 上午11:01:01
* @author OnlyMate
* @param session
* @return
*/
private String getMchNo(WebSocketSession session) {
try {
String mchNo = session.getAttributes().get(CLIENT_ID).toString();
return mchNo;
} catch (Exception e) {
return null;
}
}
}
這個(gè)類的作用就是在連接成功前和成功后增加一些額外的功能
我們希望能夠把websocketSession和httpsession對(duì)應(yīng)起來(lái),這樣就能根據(jù)當(dāng)前不同的session,定向?qū)ebsocketSession進(jìn)行數(shù)據(jù)返回;在查詢資料之后,發(fā)現(xiàn)spring中有一個(gè)攔截器接口,HandshakeInterceptor,可以實(shí)現(xiàn)這個(gè)接口,來(lái)攔截握手過(guò)程,向其中添加屬性
/**
* WebSocket握手時(shí)的攔截器
* @ClassName: CustomWebSocketInterceptor
* @Description: TODO
* @author OnlyMate
* @Date 2018年8月16日 下午3:17:04
*
*/
public class CustomWebSocketInterceptor implements HandshakeInterceptor {
private Logger logger = LoggerFactory.getLogger(CustomWebSocketInterceptor.class);
/**
* 關(guān)聯(lián)HeepSession和WebSocketSession,
* beforeHandShake方法中的Map參數(shù) 就是對(duì)應(yīng)websocketSession里的屬性
*/
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler handler, Map<String, Object> map) throws Exception {
if (request instanceof ServletServerHttpRequest) {
logger.info("*****beforeHandshake******");
HttpServletRequest httpServletRequest = ((ServletServerHttpRequest) request).getServletRequest();
HttpSession session = httpServletRequest.getSession(true);
logger.info("mchNo:{}", httpServletRequest.getParameter("mchNo"));
if (session != null) {
map.put("sessionId",session.getId());
map.put("mchNo", httpServletRequest.getParameter("mchNo"));
}
}
return true;
}
@Override
public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {
logger.info("******afterHandshake******");
}
}
這個(gè)類是配置類向Spring中注入handler
/**
* websocket的配置類
* @ClassName: CustomWebSocketConfig
* @Description: TODO
* @author OnlyMate
* @Date 2018年8月16日 下午3:17:26
*
*/
@Configuration
@EnableWebSocket
public class CustomWebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(customWebSocketHandler(), "/webSocketBySpring/customWebSocketHandler").addInterceptors(new CustomWebSocketInterceptor()).setAllowedOrigins("*");
registry.addHandler(customWebSocketHandler(), "/sockjs/webSocketBySpring/customWebSocketHandler").addInterceptors(new CustomWebSocketInterceptor()).setAllowedOrigins("*").withSockJS();
}
@Bean
public WebSocketHandler customWebSocketHandler() {
return new CustomWebSocketHandler();
}
}
補(bǔ)充說(shuō)明:
setAllowedOrigins("*")一定要加上,不然只有訪問(wèn)localhost,其他的不予許訪問(wèn)
setAllowedOrigins(String[] domains),允許指定的域名或IP(含端口號(hào))建立長(zhǎng)連接,如果只允許自家域名訪問(wèn),這里輕松設(shè)置。如果不限時(shí)使用"*"號(hào),如果指定了域名,則必須要以http或https開(kāi)頭
經(jīng)查閱官方文檔springwebsocket 4.1.5版本前默認(rèn)支持跨域訪問(wèn),之后的版本默認(rèn)不支持跨域,需要設(shè)置
使用withSockJS()的原因:
一些瀏覽器中缺少對(duì)WebSocket的支持,因此,回退選項(xiàng)是必要的,而Spring框架提供了基于SockJS協(xié)議的透明的回退選項(xiàng)。
SockJS的一大好處在于提供了瀏覽器兼容性。優(yōu)先使用原生WebSocket,如果在不支持websocket的瀏覽器中,會(huì)自動(dòng)降為輪詢的方式。
除此之外,spring也對(duì)socketJS提供了支持。
如果代碼中添加了withSockJS()如下,服務(wù)器也會(huì)自動(dòng)降級(jí)為輪詢。
registry.addEndpoint("/coordination").withSockJS();
SockJS的目標(biāo)是讓?xiě)?yīng)用程序使用WebSocket API,但在運(yùn)行時(shí)需要在必要時(shí)返回到非WebSocket替代,即無(wú)需更改應(yīng)用程序代碼。
客戶端與JAVA WebSocket建立連接
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<c:set var="ctx" value="${pageContext.request.contextPath}" />
<c:set var="ctxpath"
value="${pageContext.request.scheme}${'://'}${pageContext.request.serverName}${':'}${pageContext.request.serverPort}${pageContext.request.contextPath}" />
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta charset=UTF-8">
<title>登錄測(cè)試</title>
</head>
<body>
<h2>Hello World! Web Socket by Spring</h2>
<div>
<span>sessionId:</span>
<%
HttpSession s= request.getSession();
out.println(s.getId());
%>
</div>
<input id="sessionId" type="hidden" value="<%=session.getId() %>" />
<input id="text" type="text" />
<button onclick="send()">發(fā)送消息</button>
<hr />
<button onclick="closeWebSocket()">關(guān)閉WebSocket連接</button>
<hr />
<div id="message"></div>
</body>
<script type="text/javascript" src="http://localhost:8088/static/js/sockjs-0.3.min.js"></script>
<script type="text/javascript">
var websocket = null;
//判斷當(dāng)前瀏覽器是否支持WebSocket
//判斷當(dāng)前瀏覽器是否支持WebSocket
if('WebSocket' in window) {
websocket = new WebSocket("ws://localhost:8088/websocket/webSocketBySpring/customWebSocketHandler?mchNo="+ 123);
} else if('MozWebSocket' in window) {
websocket = new MozWebSocket("ws://localhost:8088/websocket/webSocketBySpring/customWebSocketHandler?mchNo="+ 123);
} else {
websocket = new SockJS("http://localhost:8088/websocket/sockjs/webSocketBySpring/customWebSocketHandler?mchNo="+ 123);
}
//連接發(fā)生錯(cuò)誤的回調(diào)方法
websocket.onerror = function () {
setMessageInnerHTML("WebSocket連接發(fā)生錯(cuò)誤");
};
//連接成功建立的回調(diào)方法
websocket.onopen = function () {
setMessageInnerHTML("WebSocket連接成功");
}
//接收到消息的回調(diào)方法
websocket.onmessage = function (event) {
setMessageInnerHTML(event.data);
}
//連接關(guān)閉的回調(diào)方法
websocket.onclose = function () {
setMessageInnerHTML("WebSocket連接關(guān)閉");
}
//監(jiān)聽(tīng)窗口關(guān)閉事件,當(dāng)窗口關(guān)閉時(shí),主動(dòng)去關(guān)閉websocket連接,防止連接還沒(méi)斷開(kāi)就關(guān)閉窗口,server端會(huì)拋異常。
window.onbeforeunload = function () {
closeWebSocket();
}
//將消息顯示在網(wǎng)頁(yè)上
function setMessageInnerHTML(innerHTML) {
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}
//關(guān)閉WebSocket連接
function closeWebSocket() {
websocket.close();
}
//發(fā)送消息
function send() {
var message = document.getElementById('text').value;
websocket.send(message);
}
</script>
</html>
到此這篇關(guān)于WebSocket使用Spring boot整合方法的文章就介紹到這了,更多相關(guān)Spring boot整合WebSocket內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JAVA中出現(xiàn)異常、拋出異常后續(xù)代碼是否執(zhí)行情況詳析
當(dāng)產(chǎn)生異常后,并在異常處理器中進(jìn)行執(zhí)行之后,程序會(huì)是如何的一種狀態(tài),是終止還是繼續(xù)執(zhí)行處理之后的代碼呢,下面這篇文章主要給大家介紹了關(guān)于JAVA中出現(xiàn)異常、拋出異常后續(xù)代碼是否執(zhí)行情況的相關(guān)資料,需要的朋友可以參考下2024-05-05
Spring Boot項(xiàng)目中定制攔截器的方法詳解
這篇文章主要介紹了Spring Boot項(xiàng)目中定制攔截器的方法詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10
深入了解Java語(yǔ)言中的并發(fā)性選項(xiàng)有何不同
這篇文章主要介紹了深入了解Java語(yǔ)言中的并發(fā)性選項(xiàng)有何不同,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,,需要的朋友可以參考下2019-06-06
SpringBoot使用Maven實(shí)現(xiàn)多環(huán)境配置管理
軟件開(kāi)發(fā)中經(jīng)常有開(kāi)發(fā)環(huán)境、測(cè)試環(huán)境、生產(chǎn)環(huán)境,而且一般這些環(huán)境配置會(huì)各不相同,本文主要介紹了SpringBoot使用Maven實(shí)現(xiàn)多環(huán)境配置管理,感興趣的可以了解一下2024-01-01
簡(jiǎn)單了解Spring中BeanFactory與FactoryBean的區(qū)別
這篇文章主要介紹了簡(jiǎn)單了解Spring中BeanFactory與FactoryBean的區(qū)別,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12
SpringBoot項(xiàng)目啟動(dòng)后再請(qǐng)求遠(yuǎn)程接口的解決方式
Spring?Boot是由Pivotal團(tuán)隊(duì)提供的全新框架,其設(shè)計(jì)目的是用來(lái)簡(jiǎn)化Spring應(yīng)用的創(chuàng)建、運(yùn)行、調(diào)試、部署等,這篇文章主要介紹了SpringBoot項(xiàng)目啟動(dòng)后再請(qǐng)求遠(yuǎn)程接口的實(shí)現(xiàn)方式?,需要的朋友可以參考下2023-02-02
SpringBoot整合Javamail實(shí)現(xiàn)郵件發(fā)送功能
郵件發(fā)送是一個(gè)很普遍的功能,springboot整合了相關(guān)的starter,本文給大家介紹了可以實(shí)現(xiàn)一個(gè)簡(jiǎn)單的郵件發(fā)送功能的實(shí)例,文中通過(guò)代碼給大家介紹的非常詳細(xì),感興趣的朋友可以參考下2023-12-12

