SpringBootWebSocketSTOMP⼴播配置
⽬录
1. 前⾔
WebSocket是⼀种在单个TCP连接上进⾏全双⼯通信的协议,常⽤于实时通信的场景。在没有使⽤⾼层级线路协议的情况下,直接使⽤WebSocket是很难实现发布订阅的功能。⽽STOMP是在WebSocket之上提供了⼀个基于帧的线路格式层,STOMP客户端可以同时作为⽣产者和消费者两种模式。为发布订阅的功能提供了基础。
2. STOMP协议
is a simple text-orientated messaging protocol. It defines an so that any of the available STOMP clients can communicate with any STOMP message broker to provide easy and widespread messaging interoperability among languages and platforms (the STOMP web site has a .
⽂档地址:
3. SpringBoot WebSocket集成
SpringBoot集成WebSocket⾮常⽅便,只需要简单的三个步骤:导包、配置、提供接⼝
3.1 导⼊websocket包
compile('org.springframework.boot:spring-boot-starter-websocket')
3.2 配置WebSocket
第⼀步:创建WebSocketConfig类,通过@EnableWebSocketMessageBroker 启⽤代理⽀持的消息传递。
第⼆步:重写registerStompEndpoints和configureMessageBroker⽅法。
第三步:注册对外可访问的stomp端点、访问⽅式和连接跨域设置。
第四步:配置消息代理。可设置⼴播模式和点对点通讯。也可以添加订阅通道的前缀。
package com.fig
import t.annotation.Configuration
import fig.MessageBrokerRegistry
import org.springframework.fig.annotation.EnableWebSocketMessageBroker
import org.springframework.fig.annotation.StompEndpointRegistry
import org.springframework.fig.annotation.WebSocketMessageBrokerConfigurer
@Configuration
@EnableWebSocketMessageBroker
class WebSocketConfig : WebSocketMessageBrokerConfigurer {
override fun configureMessageBroker(config: MessageBrokerRegistry) {
// 设置订阅Broker名称,/topic为⼴播模式
// 设置应⽤程序全局⽬标前缀
config.setApplicationDestinationPrefixes("/itdragon")
}
override fun registerStompEndpoints(registry: StompEndpointRegistry) {
// 允许使⽤socketJs⽅式访问,访问端点为socket,并允许跨域
registry.addEndpoint("/socket").setAllowedOrigins("*").withSockJS()
}
}
注意:
若使⽤了setApplicationDestinationPrefixes⽅法,则作⽤主要体现在@SubscribeMapping和@MessageMapping上。如控制层配置@MessageMapping("/sendToServer"),则客户端发送的地址是 /itdragon/sendToServer
3.3 对外暴露接⼝
第⼀步:创建WebSocket的控制层类,并注⼊⽤于发送消息的SimpMessagingTemplate。
第⼆步:配置通过@MessageMapping注解修饰的⽅法来接收客户端SEND的操作。
第三步:配置通过@SubscribeMapping注解修饰的⽅法来接收客户端SUBSCRIBE的操作。
第四步:配置通过@SendTo注解的⽅法来直接将消息推送的指定地址上。
package com.itdragon.st
import org.springframework.beans.factory.annotation.Autowired
import ssaging.handler.annotation.MessageMapping
import ssaging.handler.annotation.SendTo
import ssaging.simp.SimpMessagingTemplate
import ssaging.simp.annotation.SubscribeMapping
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.RequestMapping
import java.time.Instant
@Controller
class WebSocketController {
@Autowired
lateinit var simpMessagingTemplate: SimpMessagingTemplate
/**
* 订阅⼴播,服务器主动推给连接的客户端
* 通过Http请求的⽅式触发订阅操作
*/
@RequestMapping("/subscribeTopic")
fun subscribeTopicByHttp() {
while (true) {
// 可以灵活设置成通道地址,实现发布订阅的功能
val channel = "/topic/subscribeTopic"
Thread.sleep(10*1000)
}
}
/**
* 订阅⼴播,服务器主动推给连接的客户端
* 通过Websocket的subscribe操作触发订阅操作
*/
@SubscribeMapping("/subscribeTopic")
fun subscribeTopicByWebSocket(): Long {
w().toEpochMilli()
}
/**
* 服务端接收客户端发送的消息,类似OnMessage⽅法
*/
@MessageMapping("/sendToServer")
fun handleMessage(message: String) {
println("message:{$message}")
}
/
**
* 将客户端发送的消息⼴播出去
*/
@MessageMapping("/sendToTopic")
@SendTo("/topic/subscribeTopic")
fun sendToTopic(message: String): String {
return message
}
}
WebSocket的订阅功能,可以⽤@SubscribeMapping注解,也可以⽤HTTP的⽅式触发。⽐较倾向HTTP的⽅式,因为在实现⾝份验证的功能上会⽐较⽅便。在客户端发送订阅操作之前,先发送HTTP请求做⾝份验证,验证成功后再返回指定的订阅通道地址。
4. 前端对接测试
在做消息通道对接的测试中,最常见的对话就是:连上了吗?没连上;收到了吗?没收到;收到了吗?收到了,后端报错....... 作为技术⼈员,我们有必要对各个领域的知识都有⼀定的了解。只有清楚明⽩了前端和移动端的开发思维,我们才能提供更合适的接⼝。
4.1 前端代码
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>WebSocket 发布订阅</title>
<link rel="stylesheet" type="text/css" href="/antd/3.23.6/antd.min.css">
<script src="cdn.bootcss/sockjs-client/1.1.4/sockjs.min.js"></script>
<script src="cdn.bootcss/stomp.js/2.3.3/stomp.min.js"></script>
<script src="cdn.bootcss/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>
<h2 id="connStatus"></h2>
<div >
<p>/topic/subscribeTopic 订阅⼴播通道;/sendToServer 向服务端推送消息;/sendToTopic 将消息⼴播出去</p>
<div>
<label>订阅通道: </label> <input type="text" id="subscribeChannel" value=""/><br>
<label>推送通道: </label> <input type="text" id="sendChannel" value=""/><br>
<button id="subscribe" onclick="subscribe()">订阅</button>
<button id="connect" onclick="connect()">连接</button>
<button id="disconnect" onclick="disconnect();">断开连接</button>
</div>
<br>
<div>
<label>⽤户名称: </label> <input type="text" id="name"/><br>
<label>输⼊消息: </label> <input type="text" id="message"/><br>
<button id="send" onclick="sendMsg();">发送</button>
<p id="response"></p>
</div>
</div>
<script type="text/javascript">
var stompClient = null;
var host="localhost:8809";
function subscribe() {
$.get(host+'/subscribeTopic');
}
function connect() {
var socket = new SockJS(host+'/socket');
stompClient = Stomp.over(socket);
$('#connStatus').html('Connected:' + frame)
stompClient.subscribe($('#subscribeChannel').val(), function(response) {
$('#response').html(response.body);
});
},function(err){
console.log(err)
});
}
function disconnect() {
if (stompClient != null) {
stompClient.disconnect();
}
$('#connStatus').html('Disconnected')
}
function sendMsg() {
var message = $('#message').val();
stompClient.send($('#sendChannel').val(), {}, message);
}
</script>
</body>
</html>
4.2 测试效果
简单测试了发布和订阅功能
5. 原⽣WebSocket配置
有的特殊场景需要检测WebSocket的⽣命周期,还是会⽤到原⽣的WebSocket配置,这⾥记录⼀下对应的坑。
5.1 配置类注册Bean
在任意⼀个配置类中添加ServerEndpointExporter的Bean配置
@Bean
fun serverEndpointExporter(): ServerEndpointExporter {
return ServerEndpointExporter()
}
问题:
1)添加后单元测试启动失败,服务可以正常启动。⽹上说可以移除代码,由SpringBoot管理。可是移除后websocket链接会出现问题。解决⽅法⽬前未到。
5.2 创建WebSocketServer
第⼀步:通过@ServerEndpoint注解修饰类,表⽰该类是WebSocket的Server,并对外暴露连接地址。
第⼆步:通过@OnOpen、@OnClose、@OnMessage、@OnError注解修饰⽅法,监控WebSocket的⽣命周期。第三步:通过静态、私有、ConcurrentHashMap 修饰的变量管理客户端。
第四步:为程序其他类提供发送消息的⽅法。
package com.fig
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Component
import java.io.IOException
import urrent.ConcurrentHashMap
import javax.websocket.*
import javax.websocket.server.PathParam
import javax.websocket.server.ServerEndpoint
@ServerEndpoint("/nativeSocket/{clientKey}")
@Service
class WebSocketServer {
private var logger = Logger(WebSocketServer::class.java)
private var session: Session? = null
private var clientKey = ""
@OnOpen
fun onOpen(session: Session, @PathParam("clientKey") clientKey: String) {
this.session = session
this.clientKey = clientKey
if (ainsKey(clientKey)) {
webSocketMap[clientKey] = this
} else {
webSocketMap[clientKey] = this
}
logger.info("客户端:$clientKey 连接成功")
}
@OnClose
fun onClose() {
if (ainsKey(clientKey)) {
}
logger.warn("客户端:$clientKey 连接关闭")
}
@OnMessage
fun onMessage(message: String, session: Session) {
logger.info("客户端:$clientKey 收到消息:$message")
}
@OnError
spring boot原理和生命周期fun onError(session: Session, error: Throwable) {
<("WebSocket客户端(${this.clientKey})错误: ${ssage}")
}
@Throws(IOException::class)
fun sendMessage(message: String) {
this.session!!.basicRemote.sendText(message)
}
companion object {
private val webSocketMap = ConcurrentHashMap<String, WebSocketServer>()
@Throws(IOException::class)
fun sendMessage(clientKey: String, message: String) {
webSocketMap[clientKey]?.sendMessage(message)
}
fun getStatus(clientKey: String): Boolean? {
return webSocketMap[clientKey]?.session?.isOpen
}
}
}
问题:
1)WebSocketServer类的 Bean注⼊会报错,
5.3 前端测试
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>WebSocket 简单通讯</title>
<link rel="stylesheet" type="text/css" href="/antd/3.23.6/antd.min.css"> <script src="cdn.bootcss/jquery/3.3.1/jquery.js"></script>
</head>
<body>
<div>
<label>订阅通道: </label> <input type="text" id="clientId" value=""/><br>
<label>推送消息: </label> <input type="text" id="message" value=""/><br>
<button id="subscribe" onclick="openSocket()">开启socket</button>
<button id="connect" onclick="sendMessage()">发送消息</button>
<button id="disconnect" onclick="closeSocket();">关闭socket</button>
</div>
<script>
var socket;
function openSocket() {
if(typeof(WebSocket) == "undefined") {
console.log("您的浏览器不⽀持WebSocket");
}else{
console.log("您的浏览器⽀持WebSocket");
var socketUrl="ws://localhost:8809/nativeSocket/"+$("#clientId").val();
if(socket!=null){
socket.close();
socket=null;
}
socket = new WebSocket(socketUrl);
console.log("websocket已打开");
};
console.log(msg.data);
};
console.log("websocket已关闭");
};
console.log("websocket发⽣了错误");
}
}
}
function sendMessage() {
if(socket!=null){
socket.send($("#message").val());
}
}
function closeSocket() {
socket.close();
}
</script>
</body>
</html>
通过stomp客户端发起的认证操作可以看⼀下这篇⽂章:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论