Search code examples
javawebsocketjava-websocket

How to check is a Websocket connection is alive


I have a websocket connection to a server:

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);
    }
}

After some time, it seems I don't receive any more messages from the server but the onClose method has not been called.

I would like to have a sort of timer that would at least log an error (and at best try to reconnect) if I did not receive any message during the last five minutes for instance. The timer would be reset when I receive a new message.

How can I do this?


Solution

  • Here is what I did. I changed javax.websocket by jetty and implemented a ping call:

    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);
        }
    }
    

    Edit: This is just a ping example... I don't know if all servers are supposed to answer by a pong...

    Edit2: Here is how to deal with the pong message. The trick was not to listen for String messages, but to Frame messages:

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

    To manage server time out, I modified the scheduled task as follows:

    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));
    

    ... and handle the reconnection in the onClose method