我們首先要知道WebSocket的應用場景:
①在線股票網站
②即時聊天
③多人在線游戲
④應用集群通信
⑤系統性能及時監控
......
下面讓我們開始從項目中學習WebSocket:
(一)首先創建一個Spring Boot項目,如下圖,博主用的是IDEA:
后續過程不太難,如果還是不太會的話,請看https://www.cnblogs.com/Trojan00/p/11317839.html:
(二)添加依賴:
(三)配置WebSocket(WebScoketMessageBroker.java):
1 package com.example.demo; 2 3 import org.springframework.context.annotation.Configuration; 4 import org.springframework.messaging.simp.config.MessageBrokerRegistry; 5 import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; 6 import org.springframework.web.socket.config.annotation.StompEndpointRegistry; 7 import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; 8 9 @Configuration 10 @EnableWebSocketMessageBroker 11 public class WebScoketMessageBroker implements WebSocketMessageBrokerConfigurer { 12 @Override 13 public void configureMessageBroker(MessageBrokerRegistry config){ 14 config.enableSimpleBroker("/topic"); 15 config.setApplicationDestinationPrefixes("/app"); 16 } 17 @Override 18 public void registerStompEndpoints(StompEndpointRegistry registry){ 19 registry.addEndpoint("/chat").withSockJS(); 20 } 21 }
講解時間到:
①自定義類WebSocketConfig繼承自WebSocketMessageBrokerConfigurer進
行WebSocket配置,然后通過@EnableWebSocketMessageBgroker注解開啟
WebSocket消息代理;
②config.enableSimpleBroker("/topic")表示設置消息代理的前綴,即如果消息的
前綴是"/topic",就會將消息轉發給消息代理(Broker),再由消息代理將消息廣
播給當前連接的客戶端。
③config.setApplicationDestinationPrefixes("/app")表示配置一個或多個前綴,
通過這些前綴過濾出需要備注接方法處理的消息。例如,前綴為“/app”的d-
estination可以通過@MessageMapping注解的方法處理,而其他destination
(例如:"/topic" "/queue")將被直接交給broker處理。
④registry.adEndpoing("/chat").withSockJS()則表示定義一個前綴為"/chat"的
EndPoint,並開啟sockjs支持,sockjs可以解決瀏覽器對WebSocket的兼容性
問題,客戶戶端將通過這里配置的URL來建立WebSocket連接。
(四)定義Controller(GreetingController.java):
1 package org.sang.wschat.controller; 2 3 import org.sang.wschat.model.Chat; 4 import org.sang.wschat.model.Message; 5 import org.springframework.beans.factory.annotation.Autowired; 6 import org.springframework.messaging.handler.annotation.MessageMapping; 7 import org.springframework.messaging.handler.annotation.SendTo; 8 import org.springframework.messaging.simp.SimpMessagingTemplate; 9 import org.springframework.scheduling.annotation.Scheduled; 10 import org.springframework.stereotype.Controller; 11 12 import java.security.Principal; 13 14 @Controller 15 public class GreetingController { 16 @Autowired 17 SimpMessagingTemplate messagingTemplate; 18 19 @MessageMapping("/hello") 20 @SendTo("/topic/greetings") 21 public Message greeting(Message message) throws Exception { 22 return message; 23 } 24 // @MessageMapping("/chat") 25 // public void chat(Principal principal, Chat chat) { 26 // String from = principal.getName(); 27 // chat.setFrom(from); 28 // messagingTemplate.convertAndSendToUser(chat.getTo(), "/queue/chat", chat); 29 // } 30 }
講解時間到:
在這一段代碼中我們看到@MessageMapping("/hello")注解的方法將用來接
收"/app/hello"(上一段代碼講解的第三步)路徑發送來的消息,在注解方法
對消息處理后,再將消息轉發到@SendTo定義的路徑上,兒@SendTo路徑是
一個前綴為"/topic"的路徑,所以該消息將被交給消息代理的broker, 之后再
由broker進行廣播(上一段代碼講解的第三步)。
快捷聊天界面構建(chat.html):
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>群聊</title> 6 <script src="/webjars/jquery/jquery.min.js"></script> //外部JS,添加依賴是有添加 7 <script src="/webjars/sockjs-client/sockjs.min.js"></script> //外部JS 8 <script src="/webjars/stomp-websocket/stomp.min.js"></script> //外部JS 9 <script src="/app.js"></script> 10 </head> 11 <body> 12 <div> 13 <label for="name">請輸入用戶名:</label> 14 <input type="text" id="name" placeholder="用戶名"> 15 </div> 16 <div> 17 <button id="connect" type="button">連接</button> 18 <button id="disconnect" type="button" disabled="disabled">斷開連接</button> 19 </div> 20 <div id="chat" style="display: none;"> 21 <div> 22 <label for="name">請輸入聊天內容:</label> 23 <input type="text" id="content" placeholder="聊天內容"> 24 </div> 25 <button id="send" type="button">發送</button> 26 <div id="greetings"> 27 <div id="conversation" style="display: none">群聊進行中...</div> 28 </div> 29 </div> 30 </body> 31 </html>
app.js:
1 var stompClient = null; 2 function setConnected(connected) { 3 $("#connect").prop("disabled", connected); 4 $("#disconnect").prop("disabled", !connected); 5 if (connected) { 6 $("#conversation").show(); 7 $("#chat").show(); 8 } 9 else { 10 $("#conversation").hide(); 11 $("#chat").hide(); 12 } 13 $("#greetings").html(""); 14 } 15 function connect() { 16 if (!$("#name").val()) { 17 return; 18 } 19 // registry.addEndpoint("/chat").withSockJS()中的那個"/chat" 20 var socket = new SockJS('/chat'); 21 stompClient = Stomp.over(socket); 22 stompClient.connect({}, function (frame) { 23 setConnected(true); 24 // 第一個參數就是目的地地址 25 stompClient.subscribe('/topic/greetings', function (greeting) { 26 showGreeting(JSON.parse(greeting.body)); 27 }); 28 }); 29 } 30 function disconnect() { 31 if (stompClient !== null) { 32 stompClient.disconnect(); 33 } 34 setConnected(false); 35 } 36 function sendName() { 37 // 發送,第一個參數就是GreetingController中的發送源地址 38 stompClient.send("/app/hello", {}, JSON.stringify({'name': $("#name").val(),'content':$("#content").val()})); 39 } 40 function showGreeting(message) { 41 $("#greetings").append("<div>" + message.name+":"+message.content + "</div>"); 42 } 43 44 $(function () { 45 //分別是點擊連接、斷開連接、發送三個事件,以及對應出發的函數 46 $( "#connect" ).click(function() { connect(); }); 47 $( "#disconnect" ).click(function() { disconnect(); }); 48 $( "#send" ).click(function() { sendName(); }); 49 });
運行(打開兩個頁面,進入chat.html,並輸入兩個用戶名,然后點擊連接,就可以輸入內容了
,輸入好就可點擊發送了):
(五) 接下來讓我們看一下點對點的傳送吧!點對點,所以有了用戶,因此用到了spring security,故
先要添加Spring Security的依賴;
(六)配置Spring Security(WebSecurityConfig):
1 package org.sang.wschat.config; 2 3 import org.springframework.context.annotation.Bean; 4 import org.springframework.context.annotation.Configuration; 5 import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 6 import org.springframework.security.config.annotation.web.builders.HttpSecurity; 7 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 8 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 9 import org.springframework.security.crypto.password.PasswordEncoder; 10 11 @Configuration 12 public class WebSecurityConfig extends WebSecurityConfigurerAdapter { 13 @Bean 14 PasswordEncoder passwordEncoder() { 15 return new BCryptPasswordEncoder(); 16 } 17 @Override 18 protected void configure(AuthenticationManagerBuilder auth) throws Exception { 19 auth.inMemoryAuthentication() 20 .withUser("admin") 21 .password("$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq") //即123 22 .roles("admin") 23 .and() 24 .withUser("sang") 25 .password("$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq") //即123 26 .roles("user"); 27 } 28 @Override 29 protected void configure(HttpSecurity http) throws Exception { 30 http.authorizeRequests() 31 .anyRequest().authenticated() 32 .and() 33 .formLogin().permitAll(); 34 } 35 }
(七)改造WebSocket(WebSocketConfig.java):
1 package org.sang.wschat.config; 2 3 import org.springframework.context.annotation.Configuration; 4 import org.springframework.messaging.simp.config.MessageBrokerRegistry; 5 import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; 6 import org.springframework.web.socket.config.annotation.StompEndpointRegistry; 7 import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; 8 9 @Configuration 10 @EnableWebSocketMessageBroker 11 public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { 12 @Override 13 public void configureMessageBroker(MessageBrokerRegistry config) { 14 config.enableSimpleBroker("/topic","/queue"); //就這里多加了個"/queue" 15 config.setApplicationDestinationPrefixes("/app"); 16 } 17 @Override 18 public void registerStompEndpoints(StompEndpointRegistry registry) { 19 registry.addEndpoint("/chat").withSockJS(); 20 } 21 }
(八)配置Controller:
1 package org.sang.wschat.controller; 2 3 import org.sang.wschat.model.Chat; 4 import org.sang.wschat.model.Message; 5 import org.springframework.beans.factory.annotation.Autowired; 6 import org.springframework.messaging.handler.annotation.MessageMapping; 7 import org.springframework.messaging.handler.annotation.SendTo; 8 import org.springframework.messaging.simp.SimpMessagingTemplate; 9 import org.springframework.scheduling.annotation.Scheduled; 10 import org.springframework.stereotype.Controller; 11 12 import java.security.Principal; 13 14 @Controller 15 public class GreetingController { 16 @Autowired 17 SimpMessagingTemplate messagingTemplate; 18 19 @MessageMapping("/hello") 20 @SendTo("/topic/greetings") 21 public Message greeting(Message message) throws Exception { 22 return message; 23 } 24 @MessageMapping("/chat") 25 public void chat(Principal principal, Chat chat) { 26 String from = principal.getName(); 27 chat.setFrom(from); 28 messagingTemplate.convertAndSendToUser(chat.getTo(), "/queue/chat", chat); 29 } 30 }
(九)創建聊天界面(類似於chat.html,此處是onlinechat.html):
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>單聊</title> 6 <script src="/webjars/jquery/jquery.min.js"></script> 7 <script src="/webjars/sockjs-client/sockjs.min.js"></script> 8 <script src="/webjars/stomp-websocket/stomp.min.js"></script> 9 <script src="/chat.js"></script> 10 </head> 11 <body> 12 <div id="chat"> 13 <div id="chatsContent"> 14 </div> 15 <div> 16 請輸入聊天內容: 17 <input type="text" id="content" placeholder="聊天內容"> 18 目標用戶: 19 <input type="text" id="to" placeholder="目標用戶"> 20 <button id="send" type="button">發送</button> 21 </div> 22 </div> 23 </body> 24 </html>
(十)該輪到chat.js了,類似於點對點之前的那個項目的app.js:
1 var stompClient = null; 2 function connect() { 3 var socket = new SockJS('/chat'); 4 stompClient = Stomp.over(socket); 5 stompClient.connect({}, function (frame) { 6 stompClient.subscribe('/user/queue/chat', function (chat) { 7 showGreeting(JSON.parse(chat.body)); 8 }); 9 }); 10 } 11 function sendMsg() { 12 stompClient.send("/app/chat", {}, JSON.stringify({'content':$("#content").val(), 'to':$("#to").val()})); 13 } 14 function showGreeting(message) { 15 $("#chatsContent").append("<div>" + message.from+":"+message.content + "</div>"); 16 } 17 $(function () { 18 connect(); 19 $( "#send" ).click(function() { sendMsg(); }); 20 });
注:點對點這里要注意,第6行,路徑是"/user/queue/chat",因為這個destinationPrefix默認值是
"/user",也就是說消息的最終發送路徑是"/user/用戶名/queue.chat"
結果如下:
ADD:其實這個里面還有兩個Mode,一個是Message的,另一個是Chat的,讀者可以下載完整源代碼自
行研究,這個項目的完整源代碼地址:https://github.com/Stray-Kite/WsChat