Как быстро закрыть невосприимчивый websocket в Java Spring Tomcat?

У меня есть приложение реального времени с клиентами, использующее websockets для подключения к серверу Spring Framework, на котором запущен Spring Boot Tomcat. Я хочу, чтобы сервер быстро (в течение 5 секунд) обнаружил, когда клиент перестает отвечать из-за отключения сети или другой проблемы и закрывает веб-узел.

Я пробовал

  • Установка максимального тайм-аута ожидания сеанса, как описано в документации как "Настройка механизма WebSocket", http://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html

    @Bean
    public WebSocketHandler clientHandler() {
        return new PerConnectionWebSocketHandler(ClientHandler.class);
    }
    @Bean
    public ServletServerContainerFactoryBean createWebSocketContainer() {
        ServletServerContainerFactoryBean container = 
            new ServletServerContainerFactoryBean();
        container.setMaxSessionIdleTimeout(5000);
        container.setAsyncSendTimeout(5000);
        return container;
    }
    

Я не уверен, что это реализовано правильно, потому что я не вижу связи между ServletServerContainerFactoryBean и моим поколением ClientHandlers.

  1. Отправка сообщений ping с сервера каждые 2,5 секунды. После того, как я вручную отключу клиента, нарушив сетевое подключение, сервер с радостью отправит пинги еще на 30 секунд, пока не появится ошибка транспорта.

  2. 1 и 2 одновременно

  3. 1 и 2 и установка server.session-timeout = 5 в application.properties

Моя методология для тестирования:

  • Подключите веб-узел с ноутбука к серверу Tomcat.
  • Отключите сетевое подключение на ноутбуке с помощью физического переключателя
  • Ожидание событий сервера Tomcat

Как сервер Spring быстро обнаруживает, что клиент был отключен или не отвечает на закрытие websocket?

Ответ 1

Подход, который я в конечном итоге принял, заключался в реализации протокола ping-pong на уровне приложения.

  • Сервер отправляет клиенту сообщение ping с периодом p.
  • Клиент отвечает на каждое сообщение ping сообщением pong.
  • Если сервер отправляет более n пинговые сообщения без получения ответа на понг, он генерирует событие таймаута.
  • Клиент также может генерировать событие таймаута, если он не получает сообщение ping в n*p.

Должен быть гораздо более простой способ реализовать это с использованием тайм-аутов в базовом TCP-соединении.

Ответ 3

ServletServerContainerFactoryBean просто настраивает базовый JSR-356 WebSocketContainer через конфигурацию Spring при запуске. Если вы заглянете внутрь, вы увидите его тривиальным.

Из того, что я могу видеть в коде Tomcat о обработке maxSessionIdleTimeout, метод WFWWWWFWW запускается каждые 10 секунд по умолчанию, чтобы узнать, есть ли истекшие сеансы.

Я подозреваю, что пинги, которые вы отправляете с сервера, заставляют сеанс работать активным, следовательно, не помогают в отношении конфигурации тайм-аута в режиме ожидания.

Что касается того, почему Tomcat не понимает, что клиент отключен раньше, я не могу сказать. По моему опыту, если клиент закрывает соединение WebSocket или если я убил браузер, он сразу обнаружил. В любом случае это больше связано с Tomcat не Spring.