我们首先要知道WebSocket的应用场景:
①在线股票网站
②即时聊天
③多人在线游戏
④应用集群通信
⑤系统性能及时监控
......
下面让我们开始从项目中学习WebSocket:
(一)首先创建一个Spring Boot项目,如下图,博主用的是IDEA:
后续过程不太难,如果还是不太会的话,请看:
(二)添加依赖:
(三)配置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"
结果如下: