如何检查WebSocket连接是否处于活动状态

xfb7svmp  于 2023-10-20  发布在  其他
关注(0)|答案(3)|浏览(343)

我有一个到服务器的WebSocket连接:

import javax.websocket.*;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;

@ClientEndpoint
public class WebsocketExample {

    private Session userSession;

    private void connect() {

        try {
            WebSocketContainer container = ContainerProvider.getWebSocketContainer();
            container.connectToServer(this, new URI("someaddress"));
        } catch (DeploymentException | URISyntaxException | IOException e) {
            e.printStackTrace();
        }
    }

    @OnOpen
    public void onOpen(Session userSession) {
        // Set the user session
        this.userSession = userSession;
        System.out.println("Open");
    }

    @OnClose
    public void onClose(Session userSession, CloseReason reason) {
        this.userSession = null;
        System.out.println("Close");
    }

    @OnMessage
    public void onMessage(String message) {
        // Do something with the message
        System.out.println(message);
    }
}

一段时间后,我似乎没有从服务器收到任何消息,但onClose方法没有被调用。
我想有一种计时器,至少会记录一个错误(并在最好的尝试重新连接),如果我没有收到任何消息,在过去的五分钟内,例如。当我收到新消息时,计时器会重置。
我该怎么做?

4smxwvx5

4smxwvx51#

这就是我所做的。我通过jetty改变了javax.WebSocket,并实现了一个ping调用:

import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.client.WebSocketClient;

import java.io.IOException;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

@WebSocket
public class WebsocketExample {

    private Session userSession;
    private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);

    private void connect() {
        try {
            SslContextFactory sslContextFactory = new SslContextFactory();
            WebSocketClient client = new WebSocketClient(sslContextFactory);
            client.start();
            client.connect(this, new URI("Someaddress"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @OnWebSocketConnect
    public void onOpen(Session userSession) {
        // Set the user session
        this.userSession = userSession;
        System.out.println("Open");

        executorService.scheduleAtFixedRate(() -> {
                    try {
                        String data = "Ping";
                        ByteBuffer payload = ByteBuffer.wrap(data.getBytes());
                        userSession.getRemote().sendPing(payload);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                },
                5, 5, TimeUnit.MINUTES);
    }

    @OnWebSocketClose
    public void onClose(int code, String reason) {
        this.userSession = null;
        System.out.println("Close");
    }

    @OnWebSocketMessage
    public void onMessage(String message) {
        // Do something with the message
        System.out.println(message);
    }
}

编辑:这只是一个ping示例.我不知道是不是所有的服务器都应该用“砰”的一声来回应...
Edit2:这里是如何处理pong消息。诀窍是不监听String消息,而是监听Frame消息:

@OnWebSocketFrame
@SuppressWarnings("unused")
public void onFrame(Frame pong) {
    if (pong instanceof PongFrame) {
        lastPong = Instant.now();
    }
}

为了管理服务器超时,我对计划任务进行了如下修改:

scheduledFutures.add(executorService.scheduleAtFixedRate(() -> {
                    try {
                        String data = "Ping";
                        ByteBuffer payload = ByteBuffer.wrap(data.getBytes());
                        userSession.getRemote().sendPing(payload);

                        if (lastPong != null
                                && Instant.now().getEpochSecond() - lastPong.getEpochSecond() > 60) {
                            userSession.close(1000, "Timeout manually closing dead connection.");
                        }

                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                },
                10, 10, TimeUnit.SECONDS));

.并在onClose方法中处理重新连接

7hiiyaii

7hiiyaii2#

您应该通过实现一个心跳系统来解决这个问题,其中一方发送ping,另一方使用pong进行应答。几乎每一个WebSocket客户端和服务器(据我所知)都在内部支持这个特性。这种ping/pong帧可以从双方发送。我通常在服务器端实现它,因为我通常知道它比客户端有更好的生存机会(我的意见)。如果客户端不发送回乒乓很长一段时间,我知道连接是死的。在客户端,我检查相同的:如果服务器很长时间没有发送ping消息,我知道连接已断开。
如果ping/pong在你使用的库中没有实现(我认为javax WebSocket有),你可以为它制定自己的协议。

hgtggwj0

hgtggwj03#

接受的答案使用Jetty特定的API。有一个标准的API:
发送ping:session.getAsyncRemote().sendPing(data)
发送pong(只保持活动状态,不应答)session.getAsyncRemote().sendPong(data)
要对pongs做出React,可以使用session. addMessageArray(handler),其中handler实现MessageHandler.Whole,< PongMessage >或者创建一个用@OnMessage注解并具有PongMessage参数的方法:

@OnMessage
public void onMessage(PongMessage pong) {
    // check if the pong has the same payload as ping that was sent etc...
}

周期性的ping/keep-alive发送可以使用ScheduledExecutorService进行调度,就像接受的答案一样,但必须适当注意同步:如果使用session.getBasicRemote(),则需要同步对远程的所有调用。在session.getAsyncRemote()的情况下,除了Tomcat之外,可能所有容器都会自动处理同步:参见this bug report中的讨论。
最后,取消onClose(...)中的ping任务(从executor.scheduleAtFixedRate(...)获得ScheduledFuture)很重要。
我已经开发了一个简单的WebsocketPingerService来缓解问题(在maven central中可用)。创建一个示例,并将其存储为静态var:

public Class WhicheverClassInYourApp {
    public static WebsocketPingerService pingerService = new WebsocketPingerService();

    // more code here...
}

您可以通过向构造函数传递参数来配置ping间隔、ping大小、失败限制(在失败限制之后应关闭会话)等。
之后,在onOpen(...)中注册您的端点以进行ping,并在onClose(...)中注销:

@ClientEndpoint  // or @ServerEndpoint -> pinging can be done from both ends
public class WebsocketExample {
    private Session userSession;

    @OnOpen
    public void onOpen(Session userSession) {
        this.userSession = userSession;
        WhicheverClassInYourApp.pingerService.addConnection(userSession);
    }

    @OnClose
    public void onClose(Session userSession, CloseReason reason) {
        WhicheverClassInYourApp.pingerService.removeConnection(userSession);
    }

    // other methods here
}

相关问题