要了解的內容:javascript
sockjs,對於低版本的ie等不支持websocket的瀏覽器,採用js模擬websocket對象的辦法來實現兼容(其實也有輪詢的狀況)。sockjs地址 https://github.com/sockjs/sockjs-client前端
stomp 協議,一種格式比較簡單且被普遍支持的通訊協議,spring4提供了以stomp協議爲基礎的websocket通訊實現。java
------------------------------------------------------------------------------------------git
而後,重點來了,spring實現websocket的大概原理是什麼樣子的呢?spring 的websocket實現,其實是一個簡易版的消息隊列(並且是主題-訂閱模式的),對於要發給具體用戶的消息,spring的實現是建立了一個跟用戶名相關的主題,實際上若是同一用戶登陸多個客戶端,每一個客戶端都會收到消息,所以能夠看出來,spring的websocket實現是基於廣播模式的,要實現真正的單客戶端用戶區分(單用戶多端登陸只有一個收到消息),只能依賴於session(至關於一個終端一個主題了)。消息的具體處理過程如何,先上一圖:github
客戶端發送消息,服務端接收後先判斷該消息是否須要通過後臺程序處理,也就是是不是application消息(圖中的/app分支),若是是,則根據消息的url路徑轉到controller中相關的處理方法進行處理,處理完畢後發送到具體的主題或者隊列;若是不是application消息,則直接發送到相關主題或者隊列,而後通過處理髮送給客戶端。web
所以在使用的時候,有了一開始的客戶端註冊到指定url,這個所謂的註冊到執行url的過程,實際就是客戶端跟服務端創建websocket鏈接的過程,鏈接創建以後,要發送或者接收什麼消息都經過這一個websocket通訊鏈接來完成,而不是每個主題創建一個鏈接,這樣能夠節省開銷。其中服務端代碼.withSockJS()的做用是聲明咱們想要使用 SockJS 功能,若是WebSocket不可用的話,會使用 SockJS。spring
@Configuration @EnableWebSocketMessageBroker //在 WebSocket 上啓用 STOMP public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { //webServer就是websocket的端點,客戶端須要註冊這個端點進行連接, registry.addEndpoint("/webServer").setAllowedOrigins("*").withSockJS(); } @Override public void configureMessageBroker(MessageBrokerRegistry registry) { // registry.setPathMatcher(new AntPathMatcher("."));//能夠已「.」來分割路徑,看看類級別的@messageMapping和方法級別的@messageMapping registry.enableSimpleBroker("/topic","/user"); registry.setUserDestinationPrefix("/user/"); registry.setApplicationDestinationPrefixes("/app");//走@messageMapping } @Override public boolean configureMessageConverters(List<MessageConverter> messageConverters) { return true; } @Override public void configureWebSocketTransport(WebSocketTransportRegistration webSocketTransportRegistration) { } @Override public void configureClientInboundChannel(ChannelRegistration channelRegistration) { } @Override public void configureClientOutboundChannel(ChannelRegistration registration) { // TODO Auto-generated method stub } @Override public void addArgumentResolvers(List<org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver> list) { } @Override public void addReturnValueHandlers(List<org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler> list) { } }
@EnableWebSocketMessageBroker 的做用是在WebSocket 上啓用 STOMP,registerStompEndpoints方法的做用是websocket創建鏈接用的(也就是所謂的註冊到指定的url),configureMessageBroker方法做用是配置一個簡單的消息代理。若是補充在,默認狀況下會自動配置一個簡單的內存消息隊列,用來處理「/topic」爲前綴的消息,但通過重載後,消息隊列將會處理前綴爲「/topic」、「/user」的消息,並會將「/app」的消息轉給controller處理。json
@RequestMapping("/myws") @Controller public class WebSocketController { @Resource private SimpMessagingTemplate simpMessagingTemplate; @MessageMapping("/hello") // @SendTo("/topic/hello")//會把方法的返回值廣播到指定主題(「主題」這個詞並不合適) public void toTopic(SocketMessageVo msg , String name) { System.out.println(msg.getName()+","+msg.getMsg()); this.simpMessagingTemplate.convertAndSend("/topic/hello",msg.getName()+","+msg.getMsg()); // return "消息內容:"+ msg.getName()+"--"+msg.getMsg(); } @MessageMapping("/message") // @SendToUser("/message")//把返回值發到指定隊列(「隊列」實際不是隊列,而是跟上面「主題」相似的東西,只是spring在SendTo的基礎上加了用戶的內容而已) public void toUser(SocketMessageVo msg ) { System.out.println(msg.getName()+","+msg.getMsg()); this.simpMessagingTemplate.convertAndSendToUser("123","/message",msg.getName()+msg.getMsg()); } @RequestMapping("/sendMsg") public void sendMsg(HttpSession session){ System.out.println("測試發送消息:隨機消息" +session.getId()); this.simpMessagingTemplate.convertAndSendToUser("123","/message","後臺具體用戶消息"); } }
WebSocketConfig 中配置setApplicationDestinationPrefixes()的消息會被轉發到WebSocketController 中 @MessageMapping 相應方法進行處理。@SendTo("/topic/hello") 會把方法的返回值序列化爲json串,而後發送到指定的主題,不用此註解,使用 simpMessagingTemplate.convertAndSend 效果相同;若爲 @SendToUser("/message") 則爲發送到指定的用戶隊列(實際隊列名字爲/user/用戶名/原隊列名),不用此註解,使用 simpMessagingTemplate.convertAndSendToUser() 效果相同;瀏覽器
後臺主動往前端推送消息,直接調用 simpMessagingTemplate.convertAndSendToUser() 跟 simpMessagingTemplate.convertAndSend() 便可將消息發往隊列或者主題。websocket
前端代碼:
//創建websocket鏈接 function openWs(){ websocket = new SockJS("http://localhost:8080/autotest" + "/webServer"); var stompClient = Stomp.over(websocket); stompClient.connect({}, function(frame) { stompClient.subscribe('/topic/hello', function(data) { //訂閱消息 console.log("收到topic消息:"+data.body);//body中爲具體消息內容 }); stompClient.subscribe('/user/' + 123 + '/message', function(message){ console.log("收到session消息:"+message.body);//body中爲具體消息內容 }); }); document.getElementById("sendws").onclick = function() { stompClient.send("/app/message", {}, JSON.stringify({ name: "nane", msg: "發送的消息aaa" })); } } //關閉鏈接 function wsClose() { websocket.close(); }
代碼完成後運行效果以下:
能夠看到鏈接創建握手的過程,以及訂閱成功後的消息打印,<<<爲從服務端接收到的消息,>>>爲往服務端發的消息。
--------------------------------------------------------------------------------
最後,websocket 跟輪詢,長鏈接相比有啥優點,參見:https://www.zhihu.com/question/20215561
這裏有一點不明,websocket跟長鏈接都是每一個客戶端跟服務端創建了一個鏈接,爲何說長鏈接對服務端資源消耗嚴重,而不提websocket對服務端的消耗呢?是websocket協議更底層,只在物理鏈路上有個鏈接,並無實際消耗jvm的資源?有知道的大神請留言指教。