浅入浅出的Spring+Websocket的探索

OverView

  • Spring + WebSocket 简单配置
  • 前端SockJS 的简单配置
  • 一点我自己没有用的讨论

Spring + WebSocket 简单配置

mean 依赖

1
2
3
4
5
<!--spring websocket库-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
</dependency>

修改web.xml配置

使用Spring Mvc,要在servlet的spring配置里面加上<async-supported>true</async-supported>的配置,下方仅仅是一个示例。

1
2
3
4
<servlet>
<servlet-name>spring</servlet-name>
<async-supported>true</async-supported>
</servlet>

编写配置类

Websocket暴露的地址是怎么注册的呢?有两种方法一种是写配置文件(在xml里面),一种是写配置类。这里我使用的是配置类的方法。
这里要注册websocket的地址,并且要让项目启动的时候生效。所以我们要自己写一个类,继承WebMvcConfigurerAdaptermvc的配置适配器,并且实现WebSocketConfigurer接口,来完成websocket使用地址的注册。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
/**
* 配置webSocket
* Created by An alone-banner
*/
@Configuration
@EnableWebMvc
@EnableWebSocket
public class webSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
//todo 注册的websocket的代码就写在这里 项目启动时这里就会生效的
registry
.addHandler(new systemWebSocketHandler(),"/ws/chat")
.addInterceptors(new SystemHandshakeInterceptor())
.setAllowedOrigins("*")
.withSockJS()
.setClientLibraryUrl("https://cdn.jsdelivr.net/sockjs/1.1.2/sockjs.min.js");
}
}

顺便一提上面的注解是必须的。
明显的注册器允许你注册多个地址,并且每个地址有自己处理器(Handler)和拦截器(Interceptor)。其中一个用来处理链接后的消息的行为。另外一个拦截器择在网络层直接处理请求,于是可以获取请求中的许多多东西,并且可以给每个webScoeket会话(Session 注意区别Http 中的session 这里指建立的一个链接)加上属性,以方便管理。
当然这里两个东西都要你自己实现。就像上文中的 systemWebSocketHandler,SystemHandshakeInterceptor一样,你都需要继承或实现接口官方的东西,然后加上你自己的代码。先不要管他们,我一会说到的,现在你只要对这两个东西有一个大体的功能有个认识就好。
在往下看. setAllowedOrigins是设置支持的域名的,如果你发现报错是403的问题,那么你可能要注意一下这里的值是什么。
最后是设置支持SockJS和它的第三方库地址。直接用cdn加速的地址就好了。SockJS是个好东西,他可以在浏览器不支持Websocket的情况下,对服务进行降级。

编写Handler类

编写自己的Handler类的要求并不高,只需要实现官方的接口即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;
/**
*
* Created by An AloneBanner
*/
public class MyWebSocketHandler implements WebSocketHandler {
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
//todo 成功建立的会话后的操作
}
@Override
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
//这里是写收到消息后逻辑代码的地方
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
//这里是处理链接异常的地方
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
//这里是处理链接关闭的地方
}
@Override
public boolean supportsPartialMessages() {
return false;
}
}

这里就要写处理消息的代码了。一般的做法是在建立链接的时候把session都放在一个统一的地方进行管理保证,实现更加复杂的逻辑。一般是建立一个管理链接的链接池。一般的处理思路有两种,一种是缓存在内存里面,还有一种是使用redis对会话进行缓存。我个人倾向于使用内存进行管理。但是这是后话了。
其他地方的操作也简单,处理处理消息的地方需要你用一些必要的技巧,剩下的断开连接的地方释放连接池里的链接就可以了。

便一提我这里的类名和上面的类名是不一致的。请你在自己学习的时候是理解我在做什么,而不是抄代码。:)

编写拦截器

同理编写链接器,需要你自己的类继承官方的类HttpSessionHandshakeInterceptor。注意你也可以的什么都不做在注册的时候直接使用这个类。但这就意味着你什么也没有做。其实这里可以做很多的事。让我们先看一下代码吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
import java.util.Map;
/**
* http会话的过滤器
* Created by Prince of Wind
*/
public class MyHandshakeInterceptor extends HttpSessionHandshakeInterceptor{
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
return super.beforeHandshake(request, response, wsHandler, attributes);
//todo 可以在这里进行一些处理
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception ex) {
super.afterHandshake(request, response, wsHandler, ex);
}
}

我这里仅仅介绍两个你有可能用到的需要覆盖的方法。我就是觉得好的代码就是这样,只要你看看名字就知道他们分别是握手前,和握手后的操纵。注意在这里你就可以取到java标准的Http请求对象。这里你便可以获取到请求中的URL参数,请求头,协议等等信息。并且你可以为你的这次会话加上属性,这个太方便了,使用这个方法你可以有效的管理以后你要处理的WebSocket链接们。
当然具体怎么处理还要依赖对业务逻辑的设计。如果是简单的群发,就遍历所有的链接就可以了,如果是分组发生,要知道发生给哪些用户,可以在这里对WebSocket Session打上标签,在遍历的时候判断就好了。当然还有其他很多的可能我们一会再讨论。

前端SockJS 的简单配

首先送上SockJS的传送门。我们这里是SockJS的客户端,这个组件还有个node的服务端,由于我们用java提供了服务端,于是我们便可以直接使用它的服务端,这也说明我们并不关心websocket是那种语言提供的服务端,这也是协议的特点。
在前端我们可以用npm下载依赖,也可以使用cdn加速。<script src="https://cdn.jsdelivr.net/sockjs/1/sockjs.min.js"></script>。现在我们看看官方给的资料中的demo。

1
2
3
4
5
6
7
8
9
10
11
12
var sock = new SockJS('https://mydomain.com/myendpoint');
sock.onopen = function() {
console.log('open');
};
sock.onmessage = function(e) {
console.log('message', e.data);
};
sock.onclose = function() {
console.log('close');
};
sock.send('test');
sock.close();

这里在建立链接的时候必须是http或者https。也就是直接你后端注册的地址。比如我刚才写的的’ws/chat’使用这种方法建立链接。这里不用担心,他开始会建立原生的Websocket链接,如果浏览器不知道便会降级为浏览器可以使用的方法,前端感知的就是一个抽象的websocket对象。(具体是长链接还轮询就不得而知了)

Under the hood SockJS tries to use native WebSockets first. If that fails it can use a variety of browser-specific transport protocols and presents them through WebSocket-like abstractions.

在建立链接后就可以像监听器一样的发生消息给服务端,也可以随时的接受到服务端的消息了。这里实现聊天功能或者是实现对某个状态值的绑定更是不在话下了。只需要在onmessage里对页面中的某个元素进行刷新就好了。这些对js来说没有什么难度的。

一点我自己没有用的讨论

怎么区分用户

这里讨论一下之前遗留下来的,关于区别用用户的问题。我们前面说道要在拦截器的时候在握手建立之前可以获取用户的session,用这个方法可以知道用户的登录信息,也可以包括一些其他信息(比如用户所在的讨论组的房间id,用户隐身状态等)。除此之外还有别的办法,比如在建立链接后再让用户发送自己的登录信息,这种方法也是可行的,这不过不是给Websocket 会话加上属性,而是自己管理一个HashMap(也可以是特殊的数据结构)管理链接池。这样当然没有前一种方法显得的优雅,但是可以很好处理一下和用户登录绑定的其他附加属性。所以我的建议只在拦截器一层处理一些必要的信息,比如用户的id而已,而其他状态再连接池中管理。
但即便如此,我还没有思考怎么管理效率才最好。

怎么实现统一的消息平台

我们知道很多的所谓的云推送的平台,只要你交钱就可以使用api实现云推送的能力。于是我思考怎么去定义一套可以给全公司所有部门介入的开发组件。可以让接入方通过自定义的方法去实现云推送的效果,而不是一个新的业务逻辑,就要新写一个websocket服务(或者其他消息推送的方案)。
想象一下我们要知道这个链接是给那个app的。或者说那个域名独享的。还要知道它是和登录状态有关的还是无关的。其次我们要用户自定义不同处理的数据结构。但是还需要他们遵循同一套接口标准。
我没有时间和运气思考清楚具体的技术方案。欢迎讨论。

我将一直的迷惑和无知,我是黄油香蕉君,谢谢,再见。

给作者买杯咖啡吧。喵~