炫技亮点 Spring Websocket idle check原理


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时继续处理

扫描二维码关注公众号,回复: 16108707 查看本文章

即等于每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);
          }
        }
    }

猜你喜欢

转载自blog.csdn.net/abu935009066/article/details/131432435