1.前言
- 目前国内主流的中间件(金蝶、东方通、中创)对spring提供的websocket服务都支持得不好,程序在运行过程中都会让websocket不同程度上的死掉。导致websocket功能无法正常使用,严重情况下,会让自身的中间件卡住(或者死掉)。导致整个应用不能访问。
- 为了解决这类问题,我们必须找到更加完美,更加贴合我们应用的websocket组件。org.java_websocket就是其中的解决方案之一,以下针对org.java_websocket组件实现聊天和消息提醒的替换方案的阐述。
2.java_websocket方案图
- java_websocket:用于创建websocket服务,及websocket的onOpen/onClose/onMessage/onError等方法的维护,对应JavaWebSocketServer和WebSocketConfig类。
- ws集中处理及业务分发:对websocket的onOpen/onClose/onMessage/onError等方法的集中处理及websocket业务分类,实现WebSocketService接口。
- 聊天业务处理:对聊天websocket的session管理及相关业务实现,实现WebSocketBusinessService接口。
- 消息提醒业务处理:对消息提醒websocket的session管理及相关业务实现,实现WebSocketBusinessService接口。
3.java_websocket组件使用
3.1 maven
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>1.37</version>
</dependency>
3.2 JavaWebSocketServer实现
JavaWebSocketServer需要继承java_websocket组件的WebSocketServer类。如下代码。
注意:
- 代码中必须要使用线程将接收到的WebSocket进行分发处理,否则很容易让websocket服务自动挂掉。
- 调用WebSocketService接口,将WebSocket进行集中处理。
package com.lylp.sys.service;
import com.lylp.common.utils.SpringContextUtils;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.java_websocket.WebSocket;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.server.WebSocketServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetSocketAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* WebSocket服务
*
* @author
* @date 2021/7/1 10:53
*/
public class JavaWebSocketServer extends WebSocketServer {
private static Logger logger = LoggerFactory.getLogger(JavaWebSocketServer.class);
/**
* 定义线程池<br>
* websocket的onOpen/onClose/onMessage/onError接收消息后,<br>
* 需要使用线程去处理相关业务逻辑,<br>
* 如果自身处理的业务逻辑复杂,处理时间稍久一点,websocket服务就会自动关闭。
*/
private ExecutorService executorService = new ThreadPoolExecutor(50, 200, 30, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(),
new BasicThreadFactory.Builder().namingPattern("thread-pool-%d").daemon(true).build());
public JavaWebSocketServer(Integer port) {
super(new InetSocketAddress(port));
}
@Override
public void onOpen(WebSocket webSocket, ClientHandshake clientHandshake) {
executorService.execute(() -> {
WebSocketService bean = SpringContextUtils.getBean(WebSocketService.class);
bean.onOpen(webSocket, clientHandshake.getResourceDescriptor());
});
logger.debug("JavaWebSocketServer online " + this.connections().size());
}
@Override
public void onClose(WebSocket webSocket, int i, String s, boolean b) {
if (webSocket == null) {
return;
}
executorService.execute(() -> {
WebSocketService bean = SpringContextUtils.getBean(WebSocketService.class);
bean.onClose(webSocket, i, s, b);
});
}
@Override
public void onMessage(WebSocket webSocket, String s) {
executorService.execute(() -> {
WebSocketService bean = SpringContextUtils.getBean(WebSocketService.class);
bean.onMessage(webSocket, s);
});
}
@Override
public void onError(WebSocket webSocket, Exception e) {
if (webSocket == null) {
logger.info("JavaWebSocketServer on error. websocket is null.");
logger.error(e.getMessage(), e);
return;
}
executorService.execute(() -> {
WebSocketService bean = SpringContextUtils.getBean(WebSocketService.class);
bean.onError(webSocket, e);
});
}
@Override
public void onStart() {
logger.info("JavaWebSocketServer start");
}
}
3.3WebSocketConfig类
WebSocketConfig对websocket端口号、websocket服务器启动进行配置,代码如下
注意:java_websocket组件需要独立占用一个端口号。
package com.lylp.sys.config;
import com.lylp.common.exception.LYLPException;
import com.lylp.sys.service.JavaWebSocketServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class WebSocketConfig {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Value("${core.configuration.websocket.port}")
private Integer websocketPort;
@Bean
public JavaWebSocketServer javaWebSocketServer() {
if (websocketPort == null) {
logger.error("javaWebSocketServer start failed, reason: core.configuration.websocket.port is null");
throw new LYLPException("javaWebSocketServer start failed");
}
JavaWebSocketServer javaWebSocketServer = new JavaWebSocketServer(websocketPort);
javaWebSocketServer.start();
return javaWebSocketServer;
}
}
4.ws集中处理及业务分发
WebSocketService接口用于集中处理WebSocket信息,包括WebSocket消息的分类,token处理,获取当前用户信息等处理。
注意:java_websocket是通过url进行业务分类的。
接口代码如下:
package com.lylp.sys.service;
import org.java_websocket.WebSocket;
/**
* websocket处理
*
* @author
* @date 2021/7/1 10:55
*/
public interface WebSocketService {
/**
* onOpen
*
* @param webSocket session
* @param url url
*/
void onOpen(WebSocket webSocket, String url);
/**
* onClose
*
* @param webSocket session
* @param i
* @param message 消息
* @param b
*/
void onClose(WebSocket webSocket, int i, String message, boolean b);
/**
* 接收消息
*
* @param webSocket session
* @param message 消息
*/
void onMessage(WebSocket webSocket, String message);
/**
* onError
*
* @param webSocket session
* @param e 异常
*/
void onError(WebSocket webSocket, Exception e);
}
实现代码如下:
注意:
-java_websocket的WebSocket类中有一个attachment属性,可以拥有存储附件信息。这里用于存储用户信息。attachment属性会在WebSocket生命周期中都会存在。比如在onOpen设置了此属性,则在onMessage/onClose/onError方法的WebSocket参数会自动携带attachment信息。
-在onOpen/onMessage/onClose/OnError根据解析的WebSocket类型,自动查找相应业务(即WebSocketBusinessService接口对应的实现类),对相关业务进行处理。
package com.lylp.sys.service.impl;
import com.lylp.common.exception.LYLPException;
import com.lylp.common.utils.HttpUtils;
import com.lylp.common.utils.ResponseResult;
import com.lylp.common.utils.SpringContextUtils;
import com.lylp.sys.dao.dto.SysUserAccountDto;
import com.lylp.sys.dao.dto.WsUserDto;
import com