WebSocket协议是基于TCP的一种网络协议。它实现了浏览器与服务器全双工通信——允许服务器主动发送信息给客户端。
WebSocket是通过一个socket来实现双工异步通信的。直接使用WebSocket或者SockJS协议显得特别繁琐。使用它的子协议STOMP,它是一个更高级别的协议,STMOP协议使用一个基于帧格式来定义消息,与HTTP的Request和Response类似。
SpringBoot对使用WebSocket提供了支持,配置源码在org.springframework.boot.autoconfigure.websocket包下。
案例中使用到的maven依赖:
org.springframework.boot
spring-boot-starter-websocket
org.springframework.boot
spring-boot-starter-thymeleaf
org.springframework.boot
spring-boot-starter-security
广播式即服务端有消息时,会将信息发送给所有连接了当前endpoint的浏览器。
1、 写消息类
/**
* 浏览器向服务端发送的消息
*/
public class AricMessage {
private String name;
public String getName() {
return name;
}
}
/**
* 服务端向浏览器发送的消息
*/
public class AricResponse {
private String responseMessage;
public AricResponse(String responseMessage) {
this.responseMessage = responseMessage;
}
public String getResponseMessage() {
return responseMessage;
}
}
2、配置websocket
/**
* 配置WebSocket
*/
@Configuration
//注解开启使用STOMP协议来传输基于代理(message broker)的消息,这时控制器支持使用@MessageMapping,就像使用@RequestMapping一样
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer{
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {//注册STOMP协议的节点(endpoint),并映射指定的url
//注册一个STOMP的endpoint,并指定使用SockJS协议
registry.addEndpoint("/endpointAric").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {//配置消息代理(Message Broker)
//广播式应配置一个/topic消息代理
registry.enableSimpleBroker("/topic");
}
}
3、写Controller
/**
* webSocket控制器
*/
@Controller
public class WebSocketController {
@MessageMapping("/welcome") //当浏览器向服务端发送请求时,通过@MessageMapping映射/welcome这个地址,类似于@ResponseMapping
@SendTo("/topic/getResponse")//当服务器有消息时,会对订阅了@SendTo中的路径的浏览器发送消息
public AricResponse say(AricMessage message) {
try {
//睡眠1秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return new AricResponse("welcome," + message.getName() + "!");
}
}
4、在src\main\resource\templates目录下编写webSocket.html,使用thymeleaf模板引擎
SpringBoot实现广播式WebSocket
5、配置viewController,为webSocket.html提供路径映射。
/**
* 配置viewController,为页面提供路径映射
*/
@Controller
public class WebMvcConfig extends WebMvcConfigurerAdapter{
/**
* 配置viewController,提供映射路径
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/webSocket").setViewName("/webSocket");
}
}
6、打开多个浏览器,访问地址,其中一个发送消息,其它浏览器能接收消息。
点对点方式解决消息由谁发送,由谁接收的问题。
使用到Spring Security,简单配置。
/**
* Spring Security配置
*/
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/", "login").permitAll() //设置Spring Security对/和"/login"路径不拦截
.anyRequest().authenticated()
.and().formLogin().loginPage("/login")//设置Spring Security的登陆页面访问路径为login
.defaultSuccessUrl("/chat") //登陆成功后转向/chat路径
.permitAll().and().logout()
.permitAll();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//配置两个用户,角色是user
auth.inMemoryAuthentication()
.withUser("james").password("james").roles("user")
.and()
.withUser("curry").password("curry").roles("user");
}
@Override
public void configure(WebSecurity web) throws Exception {
// 设置Spring Secutiry不拦截/resources/static/目录下的静态资源
web.ignoring().antMatchers("/resources/static/**");
}
}
配置WebSocket
/**
* 配置WebSocket
*/
@Configuration
//注解开启使用STOMP协议来传输基于代理(message broker)的消息,这时控制器支持使用@MessageMapping,就像使用@RequestMapping一样
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer{
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {//注册STOMP协议的节点(endpoint),并映射指定的url
//注册一个STOMP的endpoint,并指定使用SockJS协议
registry.addEndpoint("/endpointAric").withSockJS();
//注册名为"endpointChat"的endpoint
registry.addEndpoint("/endpointChat").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {//配置消息代理(Message Broker)
//广播式应配置一个/topic消息代理
// registry.enableSimpleBroker("/topic");
//点对点式应配置/queue和/topic消息代理
registry.enableSimpleBroker("/queue","/topic");
}
}
编写Controller
/**
* webSocket控制器
*/
@Controller
public class WebSocketController {
//通过simpMessagingTemplate向浏览器发送消息
@Autowired
private SimpMessagingTemplate simpMessagingTemplate;
@MessageMapping("/chat")
//在springmvc中,可以直接在参数中获得principal,pinciple中包含当前用户信息
public void handleChat(Principal principal,String msg){
if ("james".equals(principal.getName())) {//硬编码,对用户姓名进行判断
//向用户发送消息,第一个参数:接收消息的用户,第二个参数:浏览器订阅地址,第三个参数:消息
simpMessagingTemplate.convertAndSendToUser("curry",
"/queue/notifications", principal.getName() + "-send: " + msg);
} else {
simpMessagingTemplate.convertAndSendToUser("james",
"/queue/notifications", principal.getName() + "-send: " + msg);
}
}
@MessageMapping("/welcome") //当浏览器向服务端发送请求时,通过@MessageMapping映射/welcome这个地址,类似于@ResponseMapping
@SendTo("/topic/getResponse")//当服务器有消息时,会对订阅了@SendTo中的路径的浏览器发送消息
public AricResponse say(AricMessage message) {
try {
//睡眠3秒
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return new AricResponse("welcome," + message.getName() + "!");
}
}
在src\main\resource\templates目录下编写login.html和chat.html
首页登陆
用户名或密码错误!
已退出!
聊天页面
聊天室
聊天室
配置viewController
/**
* 配置viewController,为页面提供路径映射
*/
@Controller
public class WebMvcConfig extends WebMvcConfigurerAdapter{
/**
* 配置viewController,提供映射路径
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/webSocket").setViewName("/webSocket");
registry.addViewController("/login").setViewName("/login");
registry.addViewController("/chat").setViewName("/chat");
}
}