正巧在做性能优化,由于公司业务有比较多的轮询场景,在这一轮优化中我们想用通过websocket通信来代替之前的ajax轮询,由于公司是toB业务,有客户服务器不支持websocket的情况需要考虑降级方案,所以技术选型我选择使用SocketJs。
为什么选择SocketJS:
社区成熟:javascript的SockJs-client就有8K Star,证明得到很多场景的验证
除了客户端的包之外还支持多种服务端, SpringFramework是其中之一,刚好也符合公司的技术栈
有较全面的回退机制, 对于不支持Websocket的客户可以回退到Streaming和Long Polling等等。
用法基本与原生Websocket一致,学习成本低
看下官方的介绍
SockJS mimics the WebSockets API, but instead of WebSocket there is a SockJS Javascript object.
这也是SockJS的设计初衷
写个demo
技术选型完成了我们需要通过demo验证下,考虑到公司的技术栈,后端选择使用SpringBoot,Spring框架对Websocket支持的非常好,我们直接参考Spring官网所提供的例子:
https://spring.io/guides/gs/messaging-stomp-websocket/
细节上大家直接按照这个例子来学习即可,这里说下比较核心的代码:
引入核心依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
这个包实现了Websocket,包含了原生以及第三方的适配。
WebSocketConfig类
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/gs-guide-websocket").withSockJS();
}
}
这里面涉及到了Stomp, Stomp不是本文说的重点, 大家只用了解Stomp在Websocket的基础上定义了一个子协议,让消息通信变得更易用就行了
重点是这句代码
.withSockJS();
意思是用第三方SocketJS包来实现websocket而不是使用原生的websocket,这里Spring处理的非常微妙,它既实现了原生的,也能在非常简单的配置下就可以使用第三方。
这里可以想到Spring的一个设计原则:
Provide choice at every level
可以参考我之前写的一篇:浅谈Spring框架设计原则
前端用到了SocketJs-client库
<script src="/webjars/sockjs-client/sockjs.min.js"></script>
<script src="/webjars/stomp-websocket/stomp.min.js"></script>
结合后端的controller, 前端调用如下
function connect() {
var socket = new SockJS('/gs-guide-websocket');
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/greetings', function (greeting) {
showGreeting(JSON.parse(greeting.body).content);
});
});
}
重点看这一句:
var socket = new SockJS('/gs-guide-websocket');
看下SocketJS构造函数的用法:https://github.com/sockjs/sockjs-client
这里默认的逻辑是:如果当前环境支持WebSocket,就连接WebSocket,否则就在Streaming、LongPolling等各个实时流中选择最优回退机制。
这里看下运行效果:
支持websocket的场景
因为我用的是SpringBoot3.0,内置的tomcat是支持Websocket的,所以我们直接运行demo抓包看下,端口号是8081
访问localhost:8081
可以看到状态码返回101,成功连接,成功发送消息:
因为已经有Websocket连接,连续发送也不会有任何http请求
2. 不支持websocket的场景
可以通过nginx反向代理tomcat,并且由于nginx的websocket是默认关闭的,我们可以通过这种方法来尝试不支持websocket的情景
访问localhost:8080, 反向代理到8081
我们可以看到websocket没有返回101, SocketJs尝试了以下传输方案:
xhr_streaming, iframe, xhr_polling、htmlfile, 也可以成功发送消息:
以下是SocketJS支持的回退传输:
websocket也可以通过transports参数来指定具体的回退机制:
Sometimes it is useful to disable some fallback transports. This option allows you to supply a list transports that may be used by SockJS. By default all available transports will be used.
比如我们只选择websocket的退回机制为long polling,可以这样写
var socket = new SockJS(wsurl,null, {"transports":["websocket","xhr-polling"]});
这样当websocket无法连接时直接回退到long polling
这个好处可以定制自己需要的场景。
SocketJs还有很多特性,后面可以随着使用来深挖,今天篇幅有限就先验证到这里了。
总结
SocketJs是一个非常优秀的库,学习成本比较低,接入简单,能适应不同技术栈和不同场景,有考虑全面的回退机制, 值得我们投入时间研究。
近期文章: