繁体   English   中英

如何检查 Websocket 连接是否存在

[英]How to check is a Websocket connection is alive

我有一个到服务器的 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 方法。

例如,如果我在最后五分钟内没有收到任何消息,我希望有一种至少会记录错误(并且最好尝试重新连接)的计时器。 当我收到一条新消息时,计时器将被重置。

我怎样才能做到这一点?

这就是我做的。 我通过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方法中的重新连接

你应该通过实现心跳系统解决这个问题,一方发送ping并且一方用pong回答。 几乎每个websocket客户端和服务器(据我所知)都在内部支持此功能。 这个ping / pong帧可以从双方发送。 我通常在服务器端实现它,因为我通常知道它比客户更有机会保持活力(我的意见)。 如果客户长时间不发回乒乓球,我知道连接已经死了。 在客户端,我检查相同:如果服务器已经很长时间没有发送ping消息,我知道连接已经死了。

如果ping / pong没有在您使用的库中实现(我认为javax websocket有它),您可以为此制作自己的协议。

接受的答案使用 Jetty 特定的 API。 有一个标准的 API:

发送 ping: session.getAsyncRemote().sendPing(data)

发送乒乓球(只是保持活动,没有答案) session.getAsyncRemote().sendPong(data)

对 pong 做出反应,或者session.addMessageHandler(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...
}

例如,可以像接受的答案一样使用ScheduledExecutorService来安排定期 ping/keep-alive 发送,但必须适当注意同步:如果使用session.getBasicRemote() ,则所有对远程的调用都需要同步。 session.getAsyncRemote()的情况下,可能除了 Tomcat 之外的所有容器都会自动处理同步:请参阅此错误报告中的讨论。
最后,在onClose(...)中取消 ping 任务(从executor.scheduleAtFixedRate(...)获得的ScheduledFuture )很重要。

我开发了一个简单的WebsocketPingerService来简化事情(在maven central中可用)。 创建一个实例并将其作为静态变量存储在某处:

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
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM