文章目录
Spring Websocket 是基于 WebSocket 协议的实现,它提供了一种在客户端和服务器之间实时双向通信的方式。其中,idle check(空闲检查)是一种机制,用于检测 WebSocket 连接的空闲状态。
Spring 框架本身并没有提供内置的主动发送 Ping 消息的机制。WebSocket 协议本身定义了 Ping 和 Pong 消息用于心跳检测,但在 Spring 中,默认情况下并不会主动发送 Ping 消息。
其仅仅提供了定时的读写check,例如120秒内是否有读或者写。
Java_websocket提供了主动发送Ping消息的机制,原理在下面有。
原理
1.在第一次websocket 连接时,启动一个后台定时任务
org.apache.tomcat.websocket.BackgroundProcessManager.register
if (processes.size() == 0){
wsBackgroundThread = new WsBackgroundThread();
wsBackgroundThread.start();
}
2.其每秒执行一次,尝试去处理
org.apache.tomcat.websocket.BackgroundProcessManager
private static class WsBackgroundThread extends Thread {
@Override
public void run() {
while (running) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// Ignore
}
manager.process();
}
}
3.每秒调用一次backgroundProcess,当backgroundProcessCount>=10时继续处理
即等于每10秒check一次
org.apache.tomcat.websocket.WsWebSocketContainer.backgroundProcess
public void backgroundProcess() {
backgroundProcessCount++;
if (backgroundProcessCount >= processPeriod) {
backgroundProcessCount = 0;
for (WsSession wsSession : sessions.keySet()) {
wsSession.checkExpiration();
}
}
4.检查是否过期
org.apache.tomcat.websocket.WsSession.checkExpiration
protected void checkExpiration() {
// Local copies to ensure consistent behaviour during method execution
long timeout = maxIdleTimeout;
long timeoutRead = getMaxIdleTimeoutRead();
long timeoutWrite = getMaxIdleTimeoutWrite();
long currentTime = System.currentTimeMillis();
String key = null;
if (timeoutRead > 0 && (currentTime - lastActiveRead) > timeoutRead) {
key = "wsSession.timeoutRead";
} else if (timeoutWrite > 0 && (currentTime - lastActiveWrite) > timeoutWrite) {
key = "wsSession.timeoutWrite";
} else if (timeout > 0 && (currentTime - lastActiveRead) > timeout &&
(currentTime - lastActiveWrite) > timeout) {
key = "wsSession.timeout";
}
if (key != null) {
String msg = sm.getString(key, getId());
if (log.isDebugEnabled()) {
log.debug(msg);
}
doClose(new CloseReason(CloseCodes.GOING_AWAY, msg), new CloseReason(CloseCodes.CLOSED_ABNORMALLY, msg));
}
}
配置
配置代码为:container.setMaxSessionIdleTimeout(10 * 1000L);
整体示例如下:
@Configuration
@EnableWebSocket // 启动Websocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/websocket/**")
// 添加拦截器,可以获取连接的param和 header 用作认证鉴权
.addInterceptors(new LakerSessionHandshakeInterceptor())
// 设置运行跨域
.setAllowedOrigins("*");
}
@Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
// 设置默认会话空闲超时 以毫秒为单位 非正值意味着无限超时,默认值 0 ,默认没10s检查一次空闲就关闭
container.setMaxSessionIdleTimeout(10 * 1000L);
// 设置异步发送消息的默认超时时间 以毫秒为单位 非正值意味着无限超时 ,默认值-1,还没看到作用
// container.setAsyncSendTimeout(10 * 1000L);
// 设置文本消息的默认最大缓冲区大小 以字符为单位,默认值 8 * 1024
container.setMaxTextMessageBufferSize(8 * 1024);
// 设置二进制消息的默认最大缓冲区大小 以字节为单位,默认值 8 * 1024
container.setMaxBinaryMessageBufferSize(8 * 1024);
return container;
}
附件
Java_websocket空闲检测原理
连接丢失检查是一种检测与另一个端点的连接是否丢失的功能,例如由于wifi或移动数据信号丢失。
为了检测丢失的连接,我们使用心跳实现。
检测以指定的时间间隔(例如:60 秒)运行,并对所有连接的端点执行以下操作:
- 如果端点最近没有发送 pong,则断开端点。端点被给予 1.5 倍的时间间隔来回复PONG。因此,如果间隔为 60 秒,则端点有 90 秒的响应时间。
- 向端点发送 ping。
检测是双向的,因此服务器可以检测到丢失的客户端,而客户端可以检测到与服务器的连接丢失。
端点应该在可行的情况下尽快用 Pong 帧响应 Ping 帧。
代码
// 设置间隔为120秒
server.setConnectionLostTimeout(120);
// 间隔小于或等于 0 的值会导致检查被停用。
server.setConnectionLostTimeout( 0 );
启动定时任务
if (this.connectionLostTimeout <= 0) {
cancelConnectionLostTimer();
return;
}
connectionLostCheckerFuture = connectionLostCheckerService
.scheduleAtFixedRate(connectionLostChecker, connectionLostTimeout, connectionLostTimeout,
TimeUnit.NANOSECONDS);
定时内容,check上传pong时间及发送Ping消息
Runnable connectionLostChecker = new Runnable() {
@Override
public void run() {
// 实现原理 核心代码如下
long minimumPongTime;
synchronized (syncConnectionLost) {
minimumPongTime = (long) (System.nanoTime() - (connectionLostTimeout * 1.5));
}
for (WebSocket webSocket : connections) {
WebSocketImpl webSocketImpl = (WebSocketImpl) webSocket;
if (webSocketImpl.getLastPong() < minimumPongTime) {
// 如果最后一次收到PONG的时间差值 小于了 1.5倍的设置值
// 则关闭连接
log.trace("Closing connection due to no pong received: {}", webSocketImpl);
webSocketImpl.closeConnection(CloseFrame.ABNORMAL_CLOSE)
} else {
// 为客户端发送Ping
if (webSocketImpl.isOpen()) {
webSocketImpl.sendPing();
} else {
log.trace("Trying to ping a non open connection: {}", webSocketImpl);
}
}
}