MySQL8.0資料庫基礎教程(二)-理解"關係"
- WebSocket是HTML5開始提供的一種在單個 TCP 連線上進行全雙工通訊的協議。
-
在WebSocket API中,瀏覽器和伺服器只需要做一個握手的動作,然後,瀏覽器和伺服器之間就形成了一條快速通道。兩者之間就直接可以資料互相傳送。
瀏覽器通過 JavaScript 向伺服器發出建立 WebSocket 連線的請求,連線建立以後,客戶端和伺服器端就可以通過 TCP 連線直接交換資料。
當你獲取 Web Socket 連線後,你可以通過 send() 方法來向伺服器傳送資料,並通過 onmessage 事件來接收伺服器返回的資料 - spring整合websocket
- 新增maven依賴
<!-- spring websocket 開始--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-messaging</artifactId> <version>4.0.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-websocket</artifactId> <version>4.0.6.RELEASE</version> </dependency> <dependency> <groupId>javax.websocket</groupId> <artifactId>javax.websocket-api</artifactId> <version>1.0</version> <scope>provided</scope> </dependency> <!-- spring websocket 結束-->
- 配置task註解applicationContext-task.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
- 通過websocket定時重新整理需要互動的資料
import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; import com.alibaba.fastjson.JSON; import com.founder.szhd.business.water.dto.WaterAlarmInfoDTO; import com.founder.szhd.business.water.service.WaterAlarmInfoService; /** * 通過webSocket定時重新整理前端數量 * */ @Component public class FrontWebDataSchedule { private final static Logger logger = LoggerFactory.getLogger(FrontWebDataSchedule.class); @Autowired private WaterAlarmInfoService<WaterAlarmInfoDTO> waterAlarmInfoService; @Bean//這個註解會從Spring容器拿出Bean public CustomWebSocketHandler infoHandler() { return new CustomWebSocketHandler(); } /** * @return void * @throws * @Description: TODO 定時更新報警資訊 * @author xiehui * @date 2018/8/25 上午11:58 */ @Scheduled(fixedDelayString = "1500") protected void updateWarnInfo() throws IOException { Map<String, WebSocketSession> users = infoHandler().getUsers(); System.out.println(" 定時更新報警資訊"); Set<String> mchNos = users.keySet(); WebSocketSession session = null; for (String mchNo : mchNos) { session = users.get(mchNo); String departId = String.valueOf(session.getAttributes().get("departId")); Map warnInfoMap = new HashMap(); warnInfoMap.put("navi", getNaviWarnInfo(departId)); String message = JSON.toJSONString(warnInfoMap); infoHandler().sendMessageByDepartment(new TextMessage(message), departId); } } /** * 查詢報警資訊 * @param key * @return */ private Map<String, Object> getNaviWarnInfo(String key) { Map<String, Object> waterMap = null; try { waterMap = waterAlarmInfoService.findWarningPro(key); } catch (Exception e) { logger.error(e.getMessage()); } return waterMap; } }
- springboot對websocket支援很友好,只需要繼承webSocketHandler類,重寫幾個方法就可以了,這個類的作用就是在連線成功前和成功後增加一些額外的功能
import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Set; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.TextWebSocketHandler; /** * 建立一個WebSocket server * * */ @Service public class CustomWebSocketHandler extends TextWebSocketHandler implements WebSocketHandler { private Logger logger = LoggerFactory.getLogger(CustomWebSocketHandler.class); // 線上使用者列表 private static final Map<String, WebSocketSession> users; // 使用者標識 private static final String CLIENT_ID = "username"; @Autowired private FrontWebDataSchedule frontWebData; 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連線")); //連線後第一次推送當前報警資訊 frontWebData.updateWarnInfo(); logger.info("使用者標識:{},Session:{}", mchNo, session.toString()); } } @Override public void handleTextMessage(WebSocketSession session, TextMessage message) { logger.info("收到客戶端訊息:{}", message.getPayload()); String mchNo = getMchNo(session); if (StringUtils.isNotEmpty(mchNo)) { // 獲取提交過來的訊息詳情 logger.debug("收到使用者 " + mchNo + "的訊息:" + message.toString()); } } @Override public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { if (session.isOpen()) { session.close(); } logger.info("連接出錯"); users.remove(getMchNo(session)); } @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { logger.info("連線已關閉:" + status); users.remove(getMchNo(session)); } @Override public boolean supportsPartialMessages() { return false; } /** * 傳送資訊給指定使用者 * @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("客戶端:{},已斷開連線,傳送訊息失敗", 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("客戶端:{},已斷開連線,傳送訊息失敗", mchNo); } } catch (IOException e) { logger.info("sendMessageToAllUsers method error:{}", e); allSendSuccess = false; } } return allSendSuccess; } /** * @Description: TODO 傳送資訊給相同部門 * @author xiehui * @date 2018/8/25 下午4:49 */ public void sendMessageByDepartment(TextMessage message, String departId) { Set<String> mchNos = users.keySet(); WebSocketSession session = null; for (String mchNo : mchNos) { try { session = users.get(mchNo); if (session.isOpen() && departId.equals(String.valueOf(session.getAttributes().get("departId")))) { session.sendMessage(message); } } catch (IOException e) { logger.info("sendMessageToAllUsers method error:{}", e); } } } /** * 獲取使用者標識 * @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; } } public static Map<String, WebSocketSession> getUsers() { return users; } }
- 把websocketSession和httpsession對應起來,這樣就能根據當前不同的session,定向對websocketSession進行資料返回;在查詢資料之後,發現spring中有一個攔截器介面,HandshakeInterceptor,可以實現這個介面,來攔截握手過程,向其中新增屬性
import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.server.HandshakeInterceptor; import com.founder.commons.web.login.dto.LoginUser; /** * WebSocket握手時的攔截器 * @ClassName: CustomWebSocketInterceptor * */ public class CustomWebSocketInterceptor implements HandshakeInterceptor { private Logger logger = LoggerFactory.getLogger(CustomWebSocketInterceptor.class); /** * 關聯HeepSession和WebSocketSession, * beforeHandShake方法中的Map引數 就是對應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); if (session != null) { ///使用userName區分WebSocketHandler,以便定向傳送訊息 LoginUser systemLoginName = (LoginUser) session.getAttribute("systemLoginName"); //一般直接儲存user實體 if (systemLoginName!=null) { map.put("sessionId",session.getId()); map.put("username",systemLoginName.getUserName()); map.put("departId", systemLoginName.getDepartmentID()); } } } return true; } @Override public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) { logger.info("******afterHandshake******"); } }
- 配置類向Spring中注入handler
package com.founder.commons.websocket; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.config.annotation.WebSocketConfigurer; import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; import com.founder.szhd.websocket.CustomWebSocketHandler; /** * websocket的配置類 * */ @Configuration @EnableWebSocket public class CustomWebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(customWebSocketHandler(), "/webSocketBySpring/customWebSocketHandler.do").addInterceptors(new CustomWebSocketInterceptor()); registry.addHandler(customWebSocketHandler(), "/sockjs/webSocketBySpring/customWebSocketHandler").addInterceptors(new CustomWebSocketInterceptor()).withSockJS(); } @Bean public WebSocketHandler customWebSocketHandler() { return new CustomWebSocketHandler(); } }
- 前端js中呼叫
var websocket = null;
//判斷當前瀏覽器是否支援WebSocket
//判斷當前瀏覽器是否支援WebSocket
if('WebSocket' in window) {
websocket = new WebSocket("ws://localhost:8080/xxxx/webSocketBySpring/customWebSocketHandler.do");
} else if('MozWebSocket' in window) {
websocket = new MozWebSocket("ws://localhost:8080/xxxx/webSocketBySpring/customWebSocketHandler.do");
} else {
websocket = new SockJS("http://localhost:8080/xxxx/sockjs/webSocketBySpring/customWebSocketHandler.do");
}
//連線發生錯誤的回撥方法
websocket.onerror = function () {
setMessageInnerHTML("WebSocket連線發生錯誤");
};
//連線成功建立的回撥方法
websocket.onopen = function () {
setMessageInnerHTML("WebSocket連線成功");
}
//接收到訊息的回撥方法
websocket.onmessage = function (event) {
setMessageInnerHTML(event.data);
}
//連線關閉的回撥方法
websocket.onclose = function () {
setMessageInnerHTML("WebSocket連線關閉");
}
//監聽視窗關閉事件,當視窗關閉時,主動去關閉websocket連線,防止連線還沒斷開就關閉視窗,server端會拋異常。
window.onbeforeunload = function () {
closeWebSocket();
}
//將訊息顯示在網頁上
function setMessageInnerHTML(innerHTML) {
alert(innerHTML);
}
//關閉WebSocket連線
function closeWebSocket() {
websocket.close();
}
//傳送訊息
function send() {
var message = "傳送訊息";
websocket.send(message);
} -
補充說明:
setAllowedOrigins("*")一定要加上,不然只有訪問localhost,其他的不予許訪問
setAllowedOrigins(String[] domains),允許指定的域名或IP(含埠號)建立長連線,如果只允許自家域名訪問,這裡輕鬆設定。如果不限時使用"*"號,如果指定了域名,則必須要以http或https開頭
經查閱官方文件springwebsocket 4.1.5版本前預設支援跨域訪問,之後的版本預設不支援跨域,需要設定
使用withSockJS()的原因:
一些瀏覽器中缺少對WebSocket的支援,因此,回退選項是必要的,而Spring框架提供了基於SockJS協議的透明的回退選項。
SockJS的一大好處在於提供了瀏覽器相容性。優先使用原生WebSocket,如果在不支援websocket的瀏覽器中,會自動降為輪詢的方式。
除此之外,spring也對socketJS提供了支援。如果程式碼中添加了withSockJS()如下,伺服器也會自動降級為輪詢。
registry.addEndpoint("/coordination").withSockJS();
SockJS的目標是讓應用程式使用WebSocket API,但在執行時需要在必要時返回到非WebSocket替代,即無需更改應用程式程式碼。
SockJS是為在瀏覽器中使用而設計的。它使用各種各樣的技術支援廣泛的瀏覽器版本。對於SockJS傳輸型別和瀏覽器的完整列表,可以看到SockJS客戶端頁面。
傳輸分為3類:WebSocket、HTTP流和HTTP長輪詢(按優秀選擇的順序分為3類)