JavaWeb-WebSocket在Spring+React的实现
背景
先说一下别的轮询方式: ajax轮询:让浏览器隔个几秒就发送一次请求,询问服务器是否有新信息。 long poll:浏览器发起连接后,如果没消息,就一直不返回Response给浏览器。直到有消息才返回,返回完之后,浏览器再次建立连接,周而复始。
ajax轮询 需要服务器有很快的处理速度和资源。 long poll 需要有很高的并发。
再说webSocket,先必须记住,webSocket是一个协议 我们使用的应该说是webSocket API
WebSocket是基于TCP的独立的协议。 和HTTP的唯一关联就是HTTP服务器需要发送一个“Upgrade”请求,即101 Switching Protocol到HTTP服务器,然后由服务器进行协议转换
//简单来说,就是我是从TCP那边继承过来的,干活需要依靠HTTP先帮我连接上
前端部分:
最简单的实现:
const ws = new WebSocket("ws://echo.websocket.org/ws");
ws.onopen = function(evt) {
console.log("Connection open ...");
ws.send("Hello WebSockets!");
};
ws.onmessage = function(evt) {
console.log( "Received Message: " + evt.data);
ws.close();
};
ws.onclose = function(evt) {
console.log("Connection closed.");
};
后端部分
先放官方文档(我用的是spring 4.3.13.RELEASE)
其实基本上就4四个部分: 1. 添加依赖 2. Handler类 3. 拦截器 4. 配置
添加依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>4.3.13.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-websocket</artifactId>
<version>8.0.23</version>
<scope>provided</scope>
</dependency>
Handler类
Handler类一般implementing WebSocketHandler 或者extending TextWebSocketHandler or BinaryWebSocketHandler
public interface WebSocketHandler {
void afterConnectionEstablished(WebSocketSession session) throws Exception;
void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception;
void handleTransportError(WebSocketSession session, Throwable exception) throws Exception;
void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception;
boolean supportsPartialMessages();
}
拦截器
这里你可以选择:
不实现自己的拦截器
使用spring给你的HttpSessionHandshakeInterceptor
Spring官方文档是有写:
easiest way to customize the initial HTTP WebSocket handshake request is through a HandshakeInterceptor, which exposes “before” and “after” the handshake methods. Such an interceptor can be used to preclude the handshake or to make any attributes available to the WebSocketSession.
通俗的说
普通request的session和webSocket的WebSocketSession是不一样的。
HttpSessionHandshakeInterceptor会帮你把session中的attributes拿出来,放到WebSocketSession里面去
实现自己的拦截器
@Component
public class WebSocketHandshakeInterceptor extends BaseController implements HandshakeInterceptor {
private static final Logger logger = LoggerFactory.getLogger(WebSocketHandshakeInterceptor.class);
@Override
public boolean beforeHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map<String, Object> map) throws Exception {
if (serverHttpRequest instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) serverHttpRequest;
HttpSession session = servletRequest.getServletRequest().getSession(false);
User user = getSessionUser(servletRequest.getServletRequest());
if (session != null) {
map.put(WEBSOCKET_USERID, user.getUserId());
}else {
logger.error("session为空");
return false;
}
}
return true;
}
@Override
public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {
}
}
其中,beforeHandshake()方法,在调用 handler 前调用。 常用来注册用户信息,绑定 WebSocketSession,在 handler 里根据用户信息获取WebSocketSession发送消息。 注意:⚠️需要把这个拦截器注册让spring识别
//这里获取的user是之前在用户登录的时候就把user放入了session中。 继承的controller里面有getSessionUser方法。
配置
编写 WebSocketConfig 配置类,实现 WebSocketConfigurer接口
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Autowired
private SystemWebSocketHandler systemWebSocketHandler;
@Autowired
private WebSocketHandshakeInterceptor webSocketHandshakeInterceptor;
/**
* 注册实现类,设置访问WebSocket的地址
* 注册拦截器
*
* @param webSocketHandlerRegistry
*/
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
webSocketHandlerRegistry.addHandler(systemWebSocketHandler, "/ws")
.addInterceptors(webSocketHandshakeInterceptor).setAllowedOrigins("http://localhost:8000");
}
}
实现WebSocketConfigurer 接口,重写 registerWebSocketHandlers 方法,这是一个核心实现方法,配置 websocket 入口,允许访问的域、注册 Handler、SockJs 支持和拦截器。
addHandler()
注册和路由的功能,当客户端发起 websocket 连接,把 /path 交给对应的 handler 处理,而不实现具体的业务逻辑,可以理解为收集和任务分发中心。
addInterceptors()
顾名思义就是为 handler 添加拦截器,
可以用spring给的HttpSessionHandshakeInterceptor,
addInterceptors(new HttpSessionHandshakeInterceptor())
也可以如上文,使用自己写的拦截器
setAllowedOrigins(String[] domains)
允许指定的域名或IP(含端口号)建立长连接,如果只允许自家域名访问,这里轻松设置。如果不限时使用”*“号,如果指定了域名,则必须要以http或https开头。 //我这里是因为我的前端项目通过代理访问后端的接口所以需要设置。
通过 XML 配置文件添加配置
<bean id="chatHandler" class="com.websocket.SystemWebSocketHandler"/>
<websocket:handlers>
<!-- 配置消息处理bean和路径的映射关系 -->
<websocket:mapping path="/ws" handler="chatHandler"/>
<!-- 配置握手拦截器 -->
<websocket:handshake-interceptors>
<bean class="com.websocket.WebSocketHandshakeInterceptor"/>
</websocket:handshake-interceptors>
<!-- 开启sockjs,去掉则关闭sockjs -->
<websocket:sockjs/>
</websocket:handlers>
<!-- 配置websocket消息的最大缓冲区长度,可以不配 -->
<bean class="org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean">
<property name="maxTextMessageBufferSize" value="8192"/>
<property name="maxBinaryMessageBufferSize" value="8192"/>
</bean>
开启和关闭webSocket
1 开启
当运行到前端
const ws = new WebSocket("ws://echo.websocket.org/ws");
这一行代码的时候,前端就发起和后端的webSocket连接,
会先触发拦截器
后端连接上后,会调用afterConnectionEstablished里面的方法
前端会调用.open方法
至此,webSocket开启成功。
2 运行
前端—->后端
前端调用.send()方法发送信息
后端调用handleMessage方法接收并消息
后端—->前端
后端调用webSocketSession.sendMessage(message)方法发送信息
前端调用.onmessage()方法接收信息
3 关闭
前端调用.close()方法