119 lines
4.3 KiB
Markdown
119 lines
4.3 KiB
Markdown
|
## Springboot-WebSocket
|
|||
|
|
|||
|
### **问题**:java websocket服务器发生EOFException异常,断开和客户端的连接
|
|||
|
|
|||
|
#### **原因**:使用了 Nginx 反向代理后,默认超过60S没有数据传输的连接会自动断开。
|
|||
|
|
|||
|
#### **解决方案一:**
|
|||
|
|
|||
|
nginx配置:
|
|||
|
|
|||
|
```Shell
|
|||
|
location /finance/ws/ {
|
|||
|
proxy_pass http://192.168.3.47:56667/finance/ws/;
|
|||
|
proxy_http_version 1.1;
|
|||
|
proxy_set_header Upgrade $http_upgrade;
|
|||
|
proxy_set_header Connection "Upgrade";
|
|||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|||
|
proxy_set_header X-Real-IP $remote_addr;
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
可以更新配置
|
|||
|
|
|||
|
/etc/nginx/nginx.conf
|
|||
|
|
|||
|
```Shell
|
|||
|
# 在http模块中添加
|
|||
|
map $http_upgrade $connection_upgrade {
|
|||
|
default upgrade;
|
|||
|
'' close;
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
map指令的作用:该作用主要是根据客户端请求中的值,来构造改变connection_upgrade的值,即根据变量的值创建新的变量connection_upgrade, 创建的规则就是{}里面的东西。其中的规则没有做匹配,因此使用默认的,即 http_upgrade为空字符串的话,那么值就是 close。
|
|||
|
|
|||
|
则代理配置更新为:
|
|||
|
|
|||
|
```Shell
|
|||
|
location /finance/ws/ {
|
|||
|
proxy_pass http://192.168.3.47:56667/finance/ws/;
|
|||
|
proxy_http_version 1.1;
|
|||
|
proxy_set_header Upgrade $http_upgrade;
|
|||
|
proxy_set_header Connection "$connection_upgrade";
|
|||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|||
|
proxy_set_header X-Real-IP $remote_addr;
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
如上配置其实无法解决nginx默认60s没有任何传输请求断开的问题,可以适当延长**proxy_read_timeout的时间**
|
|||
|
|
|||
|
#### 解决方案二:后台判断有连接客户端的情况,通过心跳机制每隔30-50秒和客户端主动建立连接。**
|
|||
|
|
|||
|
1.创建定时任务
|
|||
|
|
|||
|
```Java
|
|||
|
@Slf4j
|
|||
|
//Component 注入到Spring容器
|
|||
|
@Component
|
|||
|
public class HeartbeatWebSocket {
|
|||
|
|
|||
|
//使用@cheduled定义一个方法为计划任务,fixedDelay属性表示表示上一次任务执行完成后多久再执行,参数类型long,单位:ms
|
|||
|
@Scheduled(fixedDelay = 50000)
|
|||
|
private void heartbeat() {
|
|||
|
//这个Map存储了所有连接的会话
|
|||
|
ConcurrentMap<Long, MonthEndCarryForwardServer> sessionPool = MonthEndCarryForwardServer.getSessionPool();
|
|||
|
if (!sessionPool.isEmpty()){
|
|||
|
//循环会话池 对每个会话发送ping指令
|
|||
|
sessionPool.forEach((k,v)->{
|
|||
|
try {
|
|||
|
//发送指令
|
|||
|
v.getSession().getBasicRemote().sendText("ping");
|
|||
|
} catch (IOException e) {
|
|||
|
e.printStackTrace();
|
|||
|
log.error("心跳发送出错:",e);
|
|||
|
}
|
|||
|
});
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
注解`@Scheduled`属性含义:
|
|||
|
|
|||
|
```Java
|
|||
|
cron表达式:指定任务在特定时间执行
|
|||
|
fixedDelay:表示上一次任务执行完成后多久再执行,参数类型long,单位:ms
|
|||
|
fixedDelayString:与fixedDelay一样,只是参数类型是String
|
|||
|
fixedRate:表示按一定的频率执行任务,参数类型long,单位:ms 如: fixedRate(5000),表示这个定时器任务每5秒执行一次
|
|||
|
fixedRateString:与fixedRate一样,只是参数类型变为String
|
|||
|
initialDelay:表示延迟多久再第一次执行任务,参数类型为long ,单位:ms
|
|||
|
initialDelayString:与initialDelay一样,只是参数类型String
|
|||
|
```
|
|||
|
|
|||
|
2.在Springboot Application启动类上添加`@EnableScheduling`注解启用 Spring 的计划任务执行功能
|
|||
|
|
|||
|
```Java
|
|||
|
@EnableScheduling //启用 Spring 的计划任务执行功能 注:不添加此注解无法开启计划任务
|
|||
|
@SpringBootApplication(scanBasePackages = "com.guadou.erp")
|
|||
|
public class FinanceApplication {
|
|||
|
public static void main(String[] args) throws UnknownHostException {
|
|||
|
ConfigurableApplicationContext context = SpringApplication.run(FinanceApplication.class, args);
|
|||
|
AppConfig appConfig = new AppConfig(context);
|
|||
|
appConfig.setAppName("Finance APP");
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
### **问题**:测试方法无法启动 Error creating bean with name 'serverEndpointExporter' defined in class path resource
|
|||
|
|
|||
|
#### 解决方案:设置@SpringBootTest的属性为SpringBootTest.WebEnvironment.RANDOM_PORT
|
|||
|
|
|||
|
```java
|
|||
|
@RunWith(SpringRunner.class)
|
|||
|
@SpringBootTest(classes = AppTest.class,webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
|