WebSocket是一種在單個TCP鏈接上進行全雙工通信的協議。WebSocket通訊協議於2011年被IETF定爲標準RFC 6455,並由RFC7936補充規範。WebSocket API也被W3C定爲標準。javascript
WebSocket使得客戶端和服務器之間的數據交換變得更加簡單,容許服務端主動向客戶端推送數據。在WebSocket API中,瀏覽器和服務器只須要完成一次握手,二者之間就直接能夠建立持久性的鏈接,並進行雙向數據傳輸html
STOMP是面向文本的消息傳送協議。STOMP客戶端與支持STOMP協議的消息代理進行通訊。STOMP使用不一樣的命令,如鏈接,發送,訂閱,斷開等進行通訊。前端
具體參考:官方介紹java
SockJS是一個JavaScript庫,提供跨瀏覽器JavaScript的API,建立了一個低延遲、全雙工的瀏覽器和web服務器之間通訊通道git
以上內容出自維基百科和百度百科github
使用websocket有兩種方式:1是使用sockjs,2是使用h5的標準。使用Html5標準天然更方便簡單,因此記錄的是配合h5的使用方法。web
一、pom引入spring
核心是@ServerEndpoint這個註解。這個註解是Javaee標準裏的註解,tomcat7以上已經對其進行了實現,若是是用傳統方法使用tomcat發佈項目,只要在pom文件中引入javaee標準便可使用。apache
1 <dependency> 2 <groupId>javax</groupId> 3 <artifactId>javaee-api</artifactId> 4 <version>7.0</version> 5 <scope>provided</scope> 6 </dependency>
但使用springboot的內置tomcat時,就不須要引入javaee-api了,spring-boot已經包含了。使用springboot的websocket功能首先引入springboot組件。api
1 <dependency> 2 <groupId>org.springframework.boot</groupId> 3 <artifactId>spring-boot-starter-websocket</artifactId> 4 </dependency>
二、使用@ServerEndpoint創立websocket endpoint
首先要注入ServerEndpointExporter,這個bean會自動註冊使用了@ServerEndpoint註解聲明的Websocket endpoint。要注意,若是使用獨立的servlet容器,而不是直接使用springboot的內置容器,就不要注入ServerEndpointExporter,由於它將由容器本身提供和管理。
1 @Configuration 2 public class WebSocketConfig { 3 @Bean 4 public ServerEndpointExporter serverEndpointExporter() { 5 return new ServerEndpointExporter(); 6 } 7 8 }
下面事websocket的具體實現方法,代碼以下:
1 import org.springframework.stereotype.Component; 2 3 import javax.websocket.*; 4 import javax.websocket.server.ServerEndpoint; 5 import java.io.IOException; 6 import java.util.concurrent.CopyOnWriteArraySet; 7 8 @ServerEndpoint(value = "/websocket") 9 @Component //此註解千萬千萬不要忘記,它的主要做用就是將這個監聽器歸入到Spring容器中進行管理 10 public class WebSocket { 11 //靜態變量,用來記錄當前在線鏈接數。應該把它設計成線程安全的。 12 private static int onlineCount = 0; 13 14 //concurrent包的線程安全Set,用來存放每一個客戶端對應的MyWebSocket對象。 15 private static CopyOnWriteArraySet<WebSocket> webSocketSet = new CopyOnWriteArraySet<WebSocket>(); 16 17 //與某個客戶端的鏈接會話,須要經過它來給客戶端發送數據 18 private Session session; 19 20 /** 21 * 鏈接創建成功調用的方法 22 */ 23 @OnOpen 24 public void onOpen(Session session) { 25 this.session = session; 26 webSocketSet.add(this); //加入set中 27 addOnlineCount(); //在線數加1 28 System.out.println("有新鏈接加入!當前在線人數爲" + getOnlineCount()); 29 try { 30 sendMessage("Hello world"); 31 } catch (IOException e) { 32 System.out.println("IO異常"); 33 } 34 } 35 36 /** 37 * 鏈接關閉調用的方法 38 */ 39 @OnClose 40 public void onClose() { 41 webSocketSet.remove(this); //從set中刪除 42 subOnlineCount(); //在線數減1 43 System.out.println("有一鏈接關閉!當前在線人數爲" + getOnlineCount()); 44 } 45 46 /** 47 * 收到客戶端消息後調用的方法 48 * 49 * @param message 客戶端發送過來的消息 50 */ 51 @OnMessage 52 public void onMessage(String message, Session session) { 53 System.out.println("來自客戶端的消息:" + message); 54 55 //羣發消息 56 for (WebSocket item : webSocketSet) { 57 try { 58 item.sendMessage(message); 59 } catch (IOException e) { 60 e.printStackTrace(); 61 } 62 } 63 } 64 65 /** 66 * 發生錯誤時調用 67 */ 68 @OnError 69 public void onError(Session session, Throwable error) { 70 System.out.println("發生錯誤"); 71 error.printStackTrace(); 72 } 73 74 75 public void sendMessage(String message) throws IOException { 76 this.session.getBasicRemote().sendText(message); 77 //this.session.getAsyncRemote().sendText(message); 78 } 79 80 81 /** 82 * 羣發自定義消息 83 */ 84 public static void sendInfo(String message) throws IOException { 85 for (WebSocket item : webSocketSet) { 86 try { 87 item.sendMessage(message); 88 } catch (IOException e) { 89 continue; 90 } 91 } 92 } 93 94 public static synchronized int getOnlineCount() { 95 return onlineCount; 96 } 97 98 public static synchronized void addOnlineCount() { 99 WebSocket.onlineCount++; 100 } 101 102 public static synchronized void subOnlineCount() { 103 WebSocket.onlineCount--; 104 } 105 }
使用springboot的惟一區別是要@Component聲明下,而使用獨立容器是由容器本身管理websocket的,但在springboot中連容器都是spring管理的。
雖然@Component默認是單例模式的,但springboot仍是會爲每一個websocket鏈接初始化一個bean,因此能夠用一個靜態set保存起來。
三、前端代碼
1 <!DOCTYPE HTML> 2 <html> 3 <head> 4 <title>My WebSocket</title> 5 </head> 6 7 <body> 8 Welcome<br/> 9 <input id="text" type="text" /><button onclick="send()">Send</button> <button onclick="closeWebSocket()">Close</button> 10 <div id="message"> 11 </div> 12 </body> 13 14 <script type="text/javascript"> 15 var websocket = null; 16 17 //判斷當前瀏覽器是否支持WebSocket 18 if('WebSocket' in window){ 19 websocket = new WebSocket("ws://localhost:8084/websocket"); 20 } 21 else{ 22 alert('Not support websocket') 23 } 24 25 //鏈接發生錯誤的回調方法 26 websocket.onerror = function(){ 27 setMessageInnerHTML("error"); 28 }; 29 30 //鏈接成功創建的回調方法 31 websocket.onopen = function(event){ 32 setMessageInnerHTML("open"); 33 } 34 35 //接收到消息的回調方法 36 websocket.onmessage = function(event){ 37 setMessageInnerHTML(event.data); 38 } 39 40 //鏈接關閉的回調方法 41 websocket.onclose = function(){ 42 setMessageInnerHTML("close"); 43 } 44 45 //監聽窗口關閉事件,當窗口關閉時,主動去關閉websocket鏈接,防止鏈接還沒斷開就關閉窗口,server端會拋異常。 46 window.onbeforeunload = function(){ 47 websocket.close(); 48 } 49 50 //將消息顯示在網頁上 51 function setMessageInnerHTML(innerHTML){ 52 document.getElementById('message').innerHTML += innerHTML + '<br/>'; 53 } 54 55 //關閉鏈接 56 function closeWebSocket(){ 57 websocket.close(); 58 } 59 60 //發送消息 61 function send(){ 62 var message = document.getElementById('text').value; 63 websocket.send(message); 64 } 65 </script> 66 </html>
以上代碼,實現了websocket簡單消息推送,能夠實現兩個頁面間的消息顯示,可是Java後臺主動推送消息時,沒法獲取消息推送的websocket下的session,即沒法實現websocket下session的共享。
爲解決主動推送的難題,須要在創建鏈接時,將websocket下的session與servlet下的HttpSession(或者其餘session,咱們這用到了shiro下的session)創建關聯關係。
webSocket配置Java類:
1 import com.bootdo.common.utils.ShiroUtils; 2 import org.apache.catalina.session.StandardSessionFacade; 3 import org.apache.shiro.session.Session; 4 import org.springframework.context.annotation.Bean; 5 import org.springframework.context.annotation.Configuration; 6 import org.springframework.web.socket.server.standard.ServerEndpointExporter; 7 8 import javax.servlet.http.HttpSession; 9 import javax.websocket.HandshakeResponse; 10 import javax.websocket.server.HandshakeRequest; 11 import javax.websocket.server.ServerEndpointConfig; 12 import javax.websocket.server.ServerEndpointConfig.Configurator; 13 14 @Configuration 15 public class WebSocketConfig extends Configurator { 16 17 @Override 18 public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) { 19 /*若是沒有監聽器,那麼這裏獲取到的HttpSession是null*/ 20 StandardSessionFacade ssf = (StandardSessionFacade) request.getHttpSession(); 21 if (ssf != null) { 22 HttpSession httpSession = (HttpSession) request.getHttpSession(); 23 //關鍵操做 24 sec.getUserProperties().put("sessionId", httpSession.getId()); 25 System.out.println("獲取到的SessionID:" + httpSession.getId()); 26 } 27 } 28 29 /** 30 * 引入shiro框架下的session,獲取session信息 31 */ 32 /* 33 @Override 34 public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) { 35 Session shiroSession = ShiroUtils.getSubjct().getSession(); 36 sec.getUserProperties().put("sessionId", shiroSession.getId()); 37 } 38 */ 39 40 @Bean 41 public ServerEndpointExporter serverEndpointExporter() { 42 //這個對象說一下,貌似只有服務器是tomcat的時候才須要配置,具體我沒有研究 43 return new ServerEndpointExporter(); 44 } 45 }
webSocket消息實現類方法:
1 import org.springframework.stereotype.Component; 2 3 import javax.websocket.*; 4 import javax.websocket.server.ServerEndpoint; 5 import java.io.IOException; 6 import java.util.concurrent.CopyOnWriteArraySet; 7 8 //configurator = WebsocketConfig.class 該屬性就是我上面配置的信息 9 @ServerEndpoint(value = "/websocket", configurator = WebSocketConfig.class) 10 @Component //此註解千萬千萬不要忘記,它的主要做用就是將這個監聽器歸入到Spring容器中進行管理 11 public class WebSocket { 12 //靜態變量,用來記錄當前在線鏈接數。應該把它設計成線程安全的。 13 private static int onlineCount = 0; 14 15 //concurrent包的線程安全Set,用來存放每一個客戶端對應的MyWebSocket對象。 16 private static CopyOnWriteArraySet<WebSocket> webSocketSet = new CopyOnWriteArraySet<WebSocket>(); 17 18 //與某個客戶端的鏈接會話,須要經過它來給客戶端發送數據 19 private Session session; 20 21 /** 22 * 鏈接創建成功調用的方法 23 * <p> 24 * config用來獲取WebsocketConfig中的配置信息 25 */ 26 @OnOpen 27 public void onOpen(Session session, EndpointConfig config) { 28 29 //獲取WebsocketConfig.java中配置的「sessionId」信息值 30 String httpSessionId = (String) config.getUserProperties().get("sessionId"); 31 32 this.session = session; 33 webSocketSet.add(this); //加入set中 34 addOnlineCount(); //在線數加1 35 System.out.println("有新鏈接加入!當前在線人數爲" + getOnlineCount()); 36 try { 37 sendMessage("Hello world"); 38 } catch (IOException e) { 39 System.out.println("IO異常"); 40 } 41 } 42 43 /** 44 * 鏈接關閉調用的方法 45 */ 46 @OnClose 47 public void onClose() { 48 webSocketSet.remove(this); //從set中刪除 49 subOnlineCount(); //在線數減1 50 System.out.println("有一鏈接關閉!當前在線人數爲" + getOnlineCount()); 51 } 52 53 /** 54 * 收到客戶端消息後調用的方法 55 * 56 * @param message 客戶端發送過來的消息 57 */ 58 @OnMessage 59 public void onMessage(String message, Session session) { 60 System.out.println("來自客戶端的消息:" + message); 61 62 //羣發消息 63 for (WebSocket item : webSocketSet) { 64 try { 65 item.sendMessage(message); 66 } catch (IOException e) { 67 e.printStackTrace(); 68 } 69 } 70 } 71 72 /** 73 * 發生錯誤時調用 74 */ 75 @OnError 76 public void onError(Session session, Throwable error) { 77 System.out.println("發生錯誤"); 78 error.printStackTrace(); 79 } 80 81 82 public void sendMessage(String message) throws IOException { 83 this.session.getBasicRemote().sendText(message); 84 //this.session.getAsyncRemote().sendText(message); 85 } 86 87 88 /** 89 * 羣發自定義消息 90 */ 91 public static void sendInfo(String message) throws IOException { 92 for (WebSocket item : webSocketSet) { 93 try { 94 item.sendMessage(message); 95 } catch (IOException e) { 96 continue; 97 } 98 } 99 } 100 101 public static synchronized int getOnlineCount() { 102 return onlineCount; 103 } 104 105 public static synchronized void addOnlineCount() { 106 WebSocket.onlineCount++; 107 } 108 109 public static synchronized void subOnlineCount() { 110 WebSocket.onlineCount--; 111 } 112 }
注意,有上面配置後,若是配置獲取的信息爲null,需加入監聽實現類:
1 import org.springframework.stereotype.Component; 2 3 import javax.servlet.ServletRequestEvent; 4 import javax.servlet.ServletRequestListener; 5 import javax.servlet.http.HttpServletRequest; 6 import javax.servlet.http.HttpSession; 7 8 /** 9 * 監聽器類:主要任務是用ServletRequest將咱們的HttpSession攜帶過去 10 */ 11 @Component //此註解千萬千萬不要忘記,它的主要做用就是將這個監聽器歸入到Spring容器中進行管理,至關於註冊監聽吧 12 public class RequestListener implements ServletRequestListener { 13 @Override 14 public void requestInitialized(ServletRequestEvent sre) { 15 //將全部request請求都攜帶上httpSession 16 HttpSession httpSession= ((HttpServletRequest) sre.getServletRequest()).getSession(); 17 System.out.println("將全部request請求都攜帶上httpSession " + httpSession.getId()); 18 } 19 20 public RequestListener() { 21 } 22 23 @Override 24 public void requestDestroyed(ServletRequestEvent arg0) { 25 } 26 }
對應的前端頁面無需改變。
以上信息類之於網絡上多篇文章整理的信息,參考文章:spring boot Websocket;使用spring boot +WebSocket實現(後臺主動)消息推送 ; Springboot-WebSocket初探-獲取HttpSession問題