Spring Boot的web开发(3)

本文介绍如何使用Spring Boot实现WebSocket的广播式与点对点式通信。包括搭建配置、消息传递流程及具体代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

WebSocket
1 WebSocket是什么
WebSocket为浏览器和服务器提供双工异步通信(浏览器可以向服务器发送消息,服务器也可以向浏览器发送消息)。
WebSocket需要浏览器支持。
WebSocket是通过一个socket来实现双工异步通信能力的。
直接使用WebSocket过于麻烦,使用它的子协议STOMP,它是一个更高级别的协议,STOMP协议使用基于帧(frame)的格式来定义消息,与HTTP的request和response类似。
2 Spring Boot提供的自动配置
Spring Boot对内嵌的Tomcat(7、8)、Jetty9和Undertow使用WebSocket提供了支持。
配置源码存于org.springframework.boot.autoconfigure.websocket下.
Spring Boot为WebSocket提供的starter pom是spring-boot-starter-websocket.
3 实战
3.1 准备
新建Spring Boot项目,选择Thymeleaf和Websocket依赖
3.2广播式
广播式即服务端有消息时,会将消息发送给所有连接了当前endpoint的浏览器
(1)配置WebSocket,需要在配置类上使用@EnableWebSocketMessageBroker开启WebSocket支持,并通过集成AbstractWebSocketMessageBrokerConfigurer类,重写其方法来配置WebSocket.
packagecom.hand;

importorg.springframework.context.annotation.Configuration;
importorg.springframework.messaging.simp.config.MessageBrokerRegistry;
importorg.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
importorg.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
importorg.springframework.web.socket.config.annotation.StompEndpointRegistry;

/**
* Created by lqy on 2017-11-28.
*/
@Configuration
@EnableWebSocketMessageBroker//通过注解开启使用STOMP协议来传输基于代理(message broker)的消息
//这时控制器支持使用@MessageMapping,就像使用@RequestMapping一样
public classWebSocketConfigextendsAbstractWebSocketMessageBrokerConfigurer{

@Override
public voidregisterStompEndpoints(StompEndpointRegistry registry){
//注册STOMP协议的节点(endpoint),并映射到指定URL
registry.addEndpoint("/endpointHand").withSockJS();//注册一个STOMP的endpoint,并指定使用SockJS协议
}
@Override
public voidconfigureMessageBroker(MessageBrokerRegistry registry){//配置消息代理(Message Broker)
registry.enableSimpleBroker("/topic");//广播式应配置一个/topic消息代理
}
}
(2)浏览器向服务端发送的消息用此类接受
packagecom.hand;

/**
* Created by lqy on 2017-11-28.
*/
public classHandMessage {
privateString name;
publicString getName(){
returnname;
}
}
(3)服务端向浏览器发送的此类的消息
packagecom.hand;

/**
* Created by lqy on 2017-11-28.
*/
public classHandResponse {
privateString responseMessage;
publicHandResponse(String responseMessage){
this.responseMessage=responseMessage;
}
publicString getResponseMessage(){
returnresponseMessage;
}
}
(4)演示控制器:
packagecom.hand;

importorg.springframework.messaging.handler.annotation.MessageMapping;
importorg.springframework.messaging.handler.annotation.SendTo;
importorg.springframework.stereotype.Controller;

/**
* Created by lqy on 2017-11-28.
*/
@Controller
public classHandController {
@MessageMapping("/welcome")//当浏览器向服务端发送请求时,通过该注解映射/welcome这个地址,类似@RequestMapping
@SendTo("/topic/getResponse")//当服务端有消息时,会对订阅了@Sendto中的路径的浏览器发送消息
publicHandResponse say(HandMessage message)throwsException{
Thread.sleep(3000);
return newHandResponse("welcome,"+message.getName()+"!");
}
}
(5)添加脚本。
将stomp.min.js(STOMP协议的客户端脚本)、sockjs.min.js(SockJS的客户端脚本)以及jQuery放置在src/main/resources/static下。
(6)演示页面,在src/main/resources/templates下 新建hand.html
<!DOCTYPE html>
<htmlxmlns:th="http://www.thymeleaf.org">
<head>
<metacharset="UTF-8"/>
<title>Spring Boot+WebSocket+广播式</title>

</head>
<bodyonload="disconnect()">
<noscript><h2style="color:#ff0000">貌似你的浏览器不支持websocket</h2></noscript>
<div>
<div>
<buttonid="connect"onclick="connect();">连接</button>
<buttonid="disconnect"disabled="disabled"onclick="disconnect();">断开连接</button>
</div>
<divid="conversationDiv">
<label>输入你的名字</label><inputtype="text"id="name"/>
<buttonid="sendName"onclick="sendName();">发送</button>
<pid="response"></p>
</div>
</div>
<scriptth:src="@{sockjs.min.js}"></script>
<scriptth:src="@{stomp.min.js}"></script>
<scriptth:src="@{jquery.js}"></script>
<scripttype="text/javascript">
varstompClient= null;

functionsetConnected(connected) {
document.getElementById('connect').disabled= connected;
document.getElementById('disconnect').disabled= !connected;
document.getElementById('conversationDiv').style.visibility= connected ?'visible': 'hidden';
$('#response').html();
}

functionconnect() {
varsocket= newSockJS('/endpointHand');//连接SockJS的endpoint的名字为“/endpointHand”
stompClient= Stomp.over(socket);//使用STOMP子协议的WebSocker客户端
stompClient.connect({},function(frame) {//连接WebSocker服务端
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/getResponse',function(respnose){//通过stompClient.subscribe订阅/topic/getResponse目标(destination)发送的消息,这个是在控制器的@Sento中定义的
showResponse(JSON.parse(respnose.body).responseMessage);
});
});
}


functiondisconnect() {
if(stompClient!= null) {
stompClient.disconnect();
}
setConnected(false);
console.log("Disconnected");
}

functionsendName() {
varname= $('#name').val();
//通过stompClient.send向/welcome目标(destination)发送消息,这个是在控制器的MessageMapping中定义的
stompClient.send("/welcome", {}, JSON.stringify({'name':name}));
}

functionshowResponse(message) {
varresponse= $("#response");
response.html(message);
}
</script>
</body>
</html>

(7)配置viewController,为Hand.html提供便捷的路径映射
packagecom.hand;

importorg.springframework.context.annotation.Configuration;
importorg.springframework.web.servlet.config.annotation.ViewControllerRegistry;
importorg.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

/**
* Created by lqy on 2017-11-28.
*/
@Configuration
public classWebmvcConfigextendsWebMvcConfigurerAdapter{
@Override
public voidaddViewControllers(ViewControllerRegistry registry){
registry.addViewController("/hand").setViewName("/Hand");
}
}
(8)运行程序。
预期效果:一个浏览器发送一个消息到服务端时,其他浏览器也能接收到从服务端发送的这个消息。
开启三个浏览器,访问http://localhost:8080/hand,分别连接服务器,然后再浏览器发送一条消息,其他浏览器接收
连接服务端格式:
连接成功的返回:

订阅目标

向目标发送消息的格式

从目标接收的格式

3.3 点对点式
一对一聊天室:
需要用户相关的内容,引入最简单的Spring Security相关内容
(1)添加Spring Security的starter pom;
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
(2)Spring Security的简单配置.
packagecom.hand;

importorg.springframework.context.annotation.Configuration;
importorg.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
importorg.springframework.security.config.annotation.web.builders.HttpSecurity;
importorg.springframework.security.config.annotation.web.builders.WebSecurity;
importorg.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
importorg.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

/**
* Created by lqy on 2017-11-28.
*/
@Configuration
@EnableWebSecurity
public classWebSecurityConfigextendsWebSecurityConfigurerAdapter{
@Override
protected voidconfigure(HttpSecurity http)throwsException{
http
.authorizeRequests()
.antMatchers("/","/login").permitAll()//设置Spring Security对/和/"login"路径不拦截
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")//设置Spring Security的登陆页面访问的路径为/login
.defaultSuccessUrl("/chat")//登陆成功后转向/chat路径
.permitAll()
.and()
.logout()
.permitAll();
}
//在内存中分别配置两个用户lqy和lqy01,密码与用户名一致,角色是USER
@Override
protected voidconfigure(AuthenticationManagerBuilder auth)throwsException{
auth
.inMemoryAuthentication()
.withUser("lqy").password("lqy").roles("USER")
.and()
.withUser("lqy01").password("lqy01").roles("USER");
}
///resources/static目录下的静态资源,Spring Security不拦截
@Override
public voidconfigure(WebSecurity web)throwsException{
web.ignoring().antMatchers("/resources/static/**");
}

}
(3)WebSocket;
packagecom.hand;

importorg.springframework.context.annotation.Configuration;
importorg.springframework.messaging.simp.config.MessageBrokerRegistry;
importorg.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
importorg.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
importorg.springframework.web.socket.config.annotation.StompEndpointRegistry;

/**
* Created by lqy on 2017-11-28.
*/
@Configuration
@EnableWebSocketMessageBroker//通过注解开启使用STOMP协议来传输基于代理(message broker)的消息
//这时控制器支持使用@MessageMapping,就像使用@RequestMapping一样
public classWebSocketConfigextendsAbstractWebSocketMessageBrokerConfigurer{

@Override
public voidregisterStompEndpoints(StompEndpointRegistry registry){
//注册STOMP协议的节点(endpoint),并映射到指定URL
//registry.addEndpoint("/endpointHand").withSockJS();//注册一个STOMP的endpoint,并指定使用SockJS协议
registry.addEndpoint("/endpointChat").withSockJS();//注册一个STOMP的endpoint,并指定使用SockJS协议
}
@Override
public voidconfigureMessageBroker(MessageBrokerRegistry registry){//配置消息代理(Message Broker)
//registry.enableSimpleBroker("/topic");//广播式应配置一个/topic消息代理
registry.enableSimpleBroker("/queue","/topic");//点对点式新增一个/queue消息代理
}
}
(4)控制器
packagecom.hand;


importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.messaging.handler.annotation.MessageMapping;
importorg.springframework.messaging.simp.SimpMessagingTemplate;
importorg.springframework.stereotype.Controller;

importjava.security.Principal;

/**
* Created by lqy on 2017-11-28.
*/
@Controller
public classWebSecurityController {
@Autowired
privateSimpMessagingTemplatemessagingTemplate;//通过SimpMessagingTemplate向浏览器发送消息
@MessageMapping("/chat")
public voidhandleChat(Principal principal, String msg){//在Spring MVC中,可以直接在参数中获取principal,principal中包含当前用户的信息
if(principal.getName().equals("lqy")){//这是一段硬编码,如果发送人是Lqy,则发送给lqy01,反之。。
//通过messagingTemplate.convertAndSendToUser向用户发送消息,第一个参数是接收消息的用户,第二个是浏览器订阅的地址,第三个是消息本身
messagingTemplate.convertAndSendToUser("lqy01","/queue/notifications",principal.getName()+"-send:"+msg);
}else{
messagingTemplate.convertAndSendToUser("lqy","/queue/notifications",principal.getName()+"-send:"+msg);
}
}

}
(5)登录页面,src/main/resources/templates下新建login.html
<!DOCTYPEhtml>
<htmlxmlns="http://www.w3.org/1999/xhtml"xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<metacharset="UTF-8"/>
<head>
<title>登陆页面</title>
</head>
<body>
<divth:if="${param.error}">
无效的账号和密码
</div>
<divth:if="${param.logout}">
你已注销
</div>
<formth:action="@{/login}"method="post">
<div><label> 账号 : <inputtype="text"name="username"/></label></div>
<div><label> 密码 : <inputtype="password"name="password"/></label></div>
<div><inputtype="submit"value="登陆"/></div>
</form>
</body>
</html>
(6)聊天页面
<!DOCTYPEhtml>

<htmlxmlns:th="http://www.thymeleaf.org">
<metacharset="UTF-8"/>
<head>
<title>Home</title>
<scriptth:src="@{sockjs.min.js}"></script>
<scriptth:src="@{stomp.min.js}"></script>
<scriptth:src="@{jquery.js}"></script>
</head>
<body>
<p>
聊天室
</p>

<formid="handForm">
<textarearows="4"cols="60"name="text"></textarea>
<inputtype="submit"/>
</form>

<scriptth:inline="javascript">
$('#handForm').submit(function(e){
e.preventDefault();
vartext= $('#handForm').find('textarea[name="text"]').val();
sendSpittle(text);
});

varsock= newSockJS("/endpointChat");//连接endpoint名为endpointChat的endpoint
varstomp= Stomp.over(sock);
stomp.connect('guest','guest',function(frame) {
stomp.subscribe("/user/queue/notifications",handleNotification);//订阅/user/queue/notifications发送的消息,这里与在控制器的messagingTemplate.convertAndSendToUser中定义的订阅地址保持一致。
//此处多一个/user,且是必须的,使用/user之后才能发送消息到指定用户
});



functionhandleNotification(message) {
$('#output').append("<b>Received: " + message.body+ "</b><br/>")
}

functionsendSpittle(text) {
stomp.send("/chat", {}, text);//3
}
$('#stop').click(function() {sock.close()});
</script>

<divid="output"></div>
</body>
</html>
(7)增加页面的viewController;
packagecom.hand;

importorg.springframework.context.annotation.Configuration;
importorg.springframework.web.servlet.config.annotation.ViewControllerRegistry;
importorg.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

/**
* Created by lqy on 2017-11-28.
*/
@Configuration
public classWebmvcConfigextendsWebMvcConfigurerAdapter{
@Override
public voidaddViewControllers(ViewControllerRegistry registry){
//registry.addViewController("/hand").setViewName("/Hand");
registry.addViewController("/login").setViewName("/login");
registry.addViewController("/chat").setViewName("/chat");
}
}
(8)运行
预期效果:两个用户登录系统,可以互发消息。一个浏览器用户会话session是共享的,在浏览器设置两个独立的用户,从而实现用户会话session隔离。http://localhos:8080/login

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值