前言
说到实时通讯,就不得不提 WebSocket 技术。WebSocket 建立持久、双向的通信通道,大幅降低了延迟,非常适合即时互动应用,如在线聊天、实时监控等。
WebSocket 依靠持久性 TCP 连接实现高效通信,同时支持灵活的数据格式。因此 WebSocket 为实时通讯应用提供了高效可靠的解决方案,广泛应用于各类互联网应用中。
Stomp方式
后端代码
暴露对外的监听topic
代码语言:javascript复制package com.example.messagingstompwebsocket;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;
import org.springframework.web.util.HtmlUtils;
@Controller
public class GreetingController {
@MessageMapping("/hello")
@SendTo("/topic/greetings")
public Greeting greeting(HelloMessage message) throws Exception {
Thread.sleep(1000); // simulated delay
return new Greeting("Hello, " HtmlUtils.htmlEscape(message.getName()) "!");
}
}
配置拦截器和对外的URL
代码语言:javascript复制import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/gs-guide-websocket");
}
@Bean
public AuthenticationInterceptor authenticationInterceptor() {
return new AuthenticationInterceptor();
}
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
// 添加拦截器,处理客户端发来的请求
registration.interceptors(authenticationInterceptor());
}
}
前端代码
静态页面
代码语言:javascript复制<!DOCTYPE html>
<html>
<head>
<title>Hello WebSocket</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<link href="/main.css" rel="stylesheet">
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@stomp/stompjs@7.0.0/bundles/stomp.umd.min.js"></script>
<script src="/app.js"></script>
</head>
<body>
<noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websocket relies on Javascript being
enabled. Please enable
Javascript and reload this page!</h2></noscript>
<div id="main-content" class="container">
<div class="row">
<div class="col-md-6">
<form class="form-inline">
<div class="form-group">
<label for="connect">WebSocket connection:</label>
<button id="connect" class="btn btn-default" type="submit">Connect</button>
<button id="disconnect" class="btn btn-default" type="submit" disabled="disabled">Disconnect
</button>
</div>
</form>
</div>
<div class="col-md-6">
<form class="form-inline">
<div class="form-group">
<label for="name">What is your name?</label>
<input type="text" id="name" class="form-control" placeholder="Your name here...">
</div>
<button id="send" class="btn btn-default" type="submit">Send</button>
</form>
</div>
</div>
<div class="row">
<div class="col-md-12">
<table id="conversation" class="table table-striped">
<thead>
<tr>
<th>Greetings</th>
</tr>
</thead>
<tbody id="greetings">
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>
js代码
代码语言:javascript复制const stompClient = new StompJs.Client({
brokerURL: 'ws://localhost:8080/gs-guide-websocket'
});
stompClient.onConnect = (frame) => {
setConnected(true);
console.log('Connected: ' frame);
stompClient.subscribe('/topic/greetings', (greeting) => {
showGreeting(JSON.parse(greeting.body).content);
});
};
stompClient.onWebSocketError = (error) => {
console.error('Error with websocket', error);
};
stompClient.onStompError = (frame) => {
console.error('Broker reported error: ' frame.headers['message']);
console.error('Additional details: ' frame.body);
};
function setConnected(connected) {
$("#connect").prop("disabled", connected);
$("#disconnect").prop("disabled", !connected);
if (connected) {
$("#conversation").show();
}
else {
$("#conversation").hide();
}
$("#greetings").html("");
}
function connect() {
stompClient.activate();
}
function disconnect() {
stompClient.deactivate();
setConnected(false);
console.log("Disconnected");
}
function sendName() {
stompClient.publish({
destination: "/app/hello",
body: JSON.stringify({'name': $("#name").val()})
});
}
function showGreeting(message) {
$("#greetings").append("<tr><td>" message "</td></tr>");
}
$(function () {
$("form").on('submit', (e) => e.preventDefault());
$( "#connect" ).click(() => connect());
$( "#disconnect" ).click(() => disconnect());
$( "#send" ).click(() => sendName());
});
操作演示
小结
ws stomp方式适合需要快速、实时地交流信息的场景,比如聊天室、股票行情等。它可以让一个服务器同时与多个客户端进行双向沟通,实现信息的快速传递和共享。这种方式还可以搭配消息中间件,提高系统的可靠性和负载均衡能力,非常适合处理大量实时数据的应用。
Websocket原生方式
后端代码
实现回调处理
继承TextWebSocketHandler
实现回调方式
import cn.hutool.core.util.StrUtil;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author steven
*/
@Log4j2
@Component
public class WebSocketHandler extends TextWebSocketHandler {
@Autowired
private MqService mqService;
@Autowired
private WsSessionManager wsSessionManager;
@Autowired
private InvokeWsCmd invokeWsCmd;
public static final int coreSize = Runtime.getRuntime().availableProcessors();
private ThreadPoolExecutor executor = new ThreadPoolExecutor(2 * coreSize, 2 * coreSize,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
/**
* socket 建立成功事件
*
* @param session
* @throws Exception
*/
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
wsSessionManager.wsOpen(session);
}
/**
* 接收消息事件
*
* @param session
* @param message
* @throws Exception
*/
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
executor.execute(()->{
handleMessage(session, message);
});
}
private void handleMessage(WebSocketSession session, TextMessage message) {
// 获得客户端传来的消息
String token = (String) session.getAttributes().get("token");
log.info("server 接收到 {} 发送的 {}", token, message.getPayload());
if (StrUtil.isBlankIfStr(message.getPayload())) {
wsSessionManager.validateParameterFailed(session);
return;
}
invokeWsCmd.run(session.getId(), message.getPayload());
}
/**
* socket 断开连接时
*
* @param session
* @param status
* @throws Exception
*/
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
/**
* 用户退出,移除缓存
*/
wsSessionManager.wsClose(session.getId());
String token = (String) session.getAttributes().get("token");
log.info( "websocket id:{} 关闭, token:{}", session.getId(), token);
}
}
配置对外的URL
代码语言:javascript复制import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
/**
* @author Steven
*/
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Autowired
private WebSocketHandler websocketHandle;
@Autowired
private WebSocketInterceptor webSocketInterceptor;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler( websocketHandle, "/api/ws/market")
.addInterceptors( webSocketInterceptor )
.setAllowedOrigins("*");
}
}
操作演示
小结
ws 原生方式非常适合需要实时、双向通信的应用场景,如聊天室、在线游戏等。它可以持续保持连接,大幅降低通信延迟,非常适合对实时性有要求的应用。同时 ws 在资源消耗上更高效,适合用于手机、物联网等资源受限设备。此外,ws 原生方式允许自定义消息格式,灵活性强。总的来说,ws 原生方式非常适合各种实时互动类应用。
总结
ws stomp 方式的优点是支持发布-订阅模式,适合一对多通信场景,并可搭配消息中间件实现负载均衡和容错,非常适合大数据实时处理等需要高效消息队列的场景。但它也增加了系统复杂度,可能会有一定性能损失。
相比之,ws 原生方式更加简单直接,实现了真正的实时双向通信,延迟低,资源消耗小,非常适合要求高实时性和资源受限的应用,如聊天室、在线游戏等。不过它不支持发布-订阅模式,需自行实现负载均衡和容错。
总的来说,选择时需权衡具体需求。ws stomp 方式适合需要消息队列、负载均衡等高级特性的场景,而 ws 原生方式更适合追求极致实时性和资源效率的应用。
引用
https://spring.io/guides/gs/messaging-stomp-websocket/