在Java Spring Tomcat中如何快速关闭不响应的websocket?

14

我有一个实时应用,客户端使用Websockets连接到运行Spring Boot Tomcat的Spring Framework服务器。我希望服务器能够在客户端由于网络断开或其他问题而停止响应时,在5秒内快速检测并关闭WebSocket。

我尝试过:

  1. 按照文档中“配置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. 从服务器每2.5秒发送一次ping消息。当我手动断开网络连接使客户端断开连接后,服务器会愉快地发送ping消息30多秒,直到出现传输错误。

  2. 同时执行1和2

  3. 同时执行1、2,并在application.properties中设置 server.session-timeout = 5

我测试这个的方法是:

  1. 从笔记本电脑客户端连接到Tomcat服务器的WebSocket
  2. 使用物理开关关闭笔记本电脑上的网络连接
  3. 等待Tomcat服务器事件

Spring FrameworkTomcat服务器如何快速检测到客户端已断开连接或未响应以关闭WebSocket?

4个回答

7

1
提供示例项目非常好,但是GitHub的项目经常会消失。如果发生这种情况,您的答案将不再有用。这就是为什么我说帖子应该是自包含的原因。您可以始终提供外部参考资源,但是StackOverflow帖子应该包含一些内容。 - Artjom B.
1
需要使用STOMP才能使用这些事件吗?目前我只是使用“原始”WebSocket。 - mattm
1
@Hyperion 基本的WebSocketHandler已经有了连接建立或关闭时的事件:http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/socket/WebSocketHandler.html 问题在于这些事件没有及时触发。我不认为在更抽象的层面(STOMP)尝试检测断开连接会有帮助。 - mattm
1
@Hyperion,我已经在更低层次的普通WebSocket上检测到了断开连接。STOMP是否添加了进一步的检测机制,可以在更低层次之前检测到断开连接?据我所知,答案是否定的,因为在您的示例中没有此配置。通过添加STOMP,我看到我正在增加复杂性,但我没有看到解决超时时间过长问题的任何优势。 - mattm
1
@Hyperion WebSocket已经有一个事件来处理WebSocket超时,我正在使用它并最终可以工作。问题在于超时没有使用我在Spring指令中配置的值。 - mattm
显示剩余6条评论

3
我最终采用的方法是实现一个应用层的ping-pong协议。
  • 服务器每隔p时间向客户端发送一条ping消息。
  • 客户端对每个ping消息都会回复一条pong消息。
  • 如果服务器连续发送了超过n条ping消息但没有收到pong响应,则会产生超时事件。
  • 如果客户端在n*p时间内没有收到ping消息,则也会产生超时事件。

使用底层TCP连接中的超时机制应该有更简单的实现方式。


3
ServletServerContainerFactoryBean简单地通过Spring配置在启动时配置基础JSR-356 WebSocketContainer。如果你看一下它的内部,你会发现它很简单。
从我在Tomcat代码中关于maxSessionIdleTimeout处理的观察来看,默认情况下WsWebSocketContainer#backgroundProcess()方法每10秒运行一次,以查看是否有过期会话。
我怀疑您从服务器发送的ping也使会话显得活跃,因此不利于空闲会话超时配置。
至于为什么Tomcat无法更早地意识到客户端已断开连接,我真的说不上来。根据我的经验,如果客户端关闭WebSocket连接或者我关闭浏览器,它会立即被检测到。无论如何,这与Tomcat而不是Spring有关。

Tomcat向客户端发送ping消息。在Tomcat 8.5中,来自客户端的Ping响应保持会话活动状态。一旦服务器没有收到Ping的响应,"maxSessionIdleTimeout"就开始计时。 - bsandhu

0

由于问题和最受欢迎的答案都相当古老,因此希望添加使用Spring WebSocket实现实现相同功能的最简单方法。

请参阅 4.4.8. 简单代理部分。

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    private TaskScheduler messageBrokerTaskScheduler;

    @Autowired
    public void setMessageBrokerTaskScheduler(TaskScheduler taskScheduler) {
        this.messageBrokerTaskScheduler = taskScheduler;
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {

        registry.enableSimpleBroker("/queue/", "/topic/")
                .setHeartbeatValue(new long[] {10000, 20000})
                .setTaskScheduler(this.messageBrokerTaskScheduler);

        // ...
    }
}

在返回连接响应帧时,WebSocket服务器需要发送心跳参数以启用来自客户端的ping。

如果您在客户端使用SOCKJS实现,则无需添加任何其他代码即可对齐PING / PONG实现。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接