Class UltiPanelWebSocketClient
- All Implemented Interfaces:
Runnable,org.java_websocket.WebSocket,org.java_websocket.WebSocketListener
New WebSocket client based on Java-WebSocket library
-
Field Summary
FieldsModifier and TypeFieldDescriptionprivate LongSupplierClock.private final com.google.gson.Gsonprivate static final longHeartbeat interval, in seconds.private final ScheduledExecutorServiceprivate ScheduledFuture<?> private static final longprivate booleanprivate booleanprivate longTimestamp of the most recent ping sent.private longTimestamp of the most recent pong received.private longMost recently measured round-trip latency, in milliseconds.private static final intprivate Consumer<com.google.gson.JsonObject> private Runnableprivate Runnableprivate Runnableprivate static final longThreshold for declaring a connection "silently dead": no pong received across two heartbeat intervals.private intprivate final Stringprivate final StringFields inherited from class org.java_websocket.client.WebSocketClient
uri -
Constructor Summary
ConstructorsConstructorDescriptionUltiPanelWebSocketClient(String url, String serverId, String token) Constructor. -
Method Summary
Modifier and TypeMethodDescriptionvoidconnect()Connects to the WebSocket server.voidDisconnects the WebSocket connection.getHeaders(String token) longTimestamp of the most recent pong received; 0 if none has ever been received.longMost recently measured round-trip latency, in milliseconds; -1 if not yet measured.(package private) voidOne heartbeat: check liveness first, then send a ping.booleanisAlive(long timeoutMs) Determines whether the connection is still alive, based on pong responses.voidvoidvoidvoidonOpen(org.java_websocket.handshake.ServerHandshake handshakedata) private voidCalled when a pong is received: records the time and computes the round-trip latency.voidsendMessage(com.google.gson.JsonObject message) Sends a message to the server.voidsendPing()Sends a ping message.(package private) voidsetClock(LongSupplier clock) Test-only clock substitution.voidsetMessageHandler(Consumer<com.google.gson.JsonObject> handler) Sets the message handler.voidsetOnConnectHandler(Runnable handler) Sets the on-connect handler.voidsetOnDisconnectHandler(Runnable handler) Sets the on-disconnect handler.voidsetOnErrorHandler(Consumer<String> handler) Sets the error handler.voidsetOnReconnectExhaustedHandler(Runnable handler) Sets the reconnect-exhausted handler (invoked once every reconnection attempt has failed).private voidStarts the heartbeat task.voidsubscribeToServer(String serverId) Subscribes to a server's status.voidunsubscribeFromServer(String serverId) Unsubscribes from a server's status.Methods inherited from class org.java_websocket.client.WebSocketClient
addHeader, clearHeaders, close, close, close, closeBlocking, closeConnection, connectBlocking, connectBlocking, getAttachment, getConnection, getConnections, getDraft, getLocalSocketAddress, getLocalSocketAddress, getProtocol, getReadyState, getRemoteSocketAddress, getRemoteSocketAddress, getResourceDescriptor, getSocket, getSSLSession, getURI, hasBufferedData, hasSSLSupport, isClosed, isClosing, isFlushAndClose, isOpen, onCloseInitiated, onClosing, onMessage, onSetSSLParameters, onWebsocketClose, onWebsocketCloseInitiated, onWebsocketClosing, onWebsocketError, onWebsocketMessage, onWebsocketMessage, onWebsocketOpen, onWriteDemand, reconnect, reconnectBlocking, removeHeader, run, send, send, send, sendFragmentedFrame, sendFrame, sendFrame, setAttachment, setDnsResolver, setProxy, setSocket, setSocketFactoryMethods inherited from class org.java_websocket.AbstractWebSocket
getConnectionLostTimeout, isReuseAddr, isTcpNoDelay, setConnectionLostTimeout, setReuseAddr, setTcpNoDelay, startConnectionLostTimer, stopConnectionLostTimerMethods inherited from class org.java_websocket.WebSocketAdapter
onPreparePing, onWebsocketHandshakeReceivedAsClient, onWebsocketHandshakeReceivedAsServer, onWebsocketHandshakeSentAsClient, onWebsocketPing, onWebsocketPong
-
Field Details
-
serverId
-
token
-
heartbeatExecutor
-
isConnected
private boolean isConnected -
heartbeatTask
-
gson
private final com.google.gson.Gson gson -
messageHandler
-
onConnectHandler
-
onDisconnectHandler
-
onErrorHandler
-
onReconnectExhaustedHandler
-
MAX_RECONNECT_ATTEMPTS
private static final int MAX_RECONNECT_ATTEMPTS- See Also:
-
INITIAL_RECONNECT_DELAY_MS
private static final long INITIAL_RECONNECT_DELAY_MS- See Also:
-
reconnectAttempts
private int reconnectAttempts -
intentionalDisconnect
private boolean intentionalDisconnect -
HEARTBEAT_INTERVAL_SECONDS
private static final long HEARTBEAT_INTERVAL_SECONDSHeartbeat interval, in seconds.- See Also:
-
PONG_TIMEOUT_MS
private static final long PONG_TIMEOUT_MSThreshold for declaring a connection "silently dead": no pong received across two heartbeat intervals.Two intervals rather than one, to tolerate a single dropped packet or one scheduling jitter - the cost of a false positive is kicking a good connection into reconnecting, while a false negative only delays detection by one interval.
- See Also:
-
lastPingTime
private volatile long lastPingTimeTimestamp of the most recent ping sent. -
lastPongTime
private volatile long lastPongTimeTimestamp of the most recent pong received. 0 means none has ever been received. -
latencyMs
private volatile long latencyMsMost recently measured round-trip latency, in milliseconds. -
clock
Clock. In production this is simplySystem::currentTimeMillis; tests can substitute a fake clock.The only reason this exists is so the "no pong received past the threshold" check can be covered by a unit test without
Thread.sleep- actually waiting out two heartbeat intervals is 120 seconds, and nobody keeps a test like that around.
-
-
Constructor Details
-
UltiPanelWebSocketClient
public UltiPanelWebSocketClient(String url, String serverId, String token) throws URISyntaxException Constructor.- Parameters:
url- the WebSocket server URLserverId- the server IDtoken- the auth token- Throws:
URISyntaxException- if the URL is malformed
-
-
Method Details
-
getHeaders
-
connect
public void connect()Connects to the WebSocket server.- Overrides:
connectin classorg.java_websocket.client.WebSocketClient
-
disconnect
public void disconnect()Disconnects the WebSocket connection. -
sendMessage
public void sendMessage(com.google.gson.JsonObject message) Sends a message to the server.- Parameters:
message- the JSON message object
-
sendPing
public void sendPing()Sends a ping message.- Specified by:
sendPingin interfaceorg.java_websocket.WebSocket- Overrides:
sendPingin classorg.java_websocket.client.WebSocketClient
-
subscribeToServer
Subscribes to a server's status.- Parameters:
serverId- the server ID to subscribe to
-
unsubscribeFromServer
Unsubscribes from a server's status.- Parameters:
serverId- the server ID to unsubscribe from
-
setMessageHandler
Sets the message handler.- Parameters:
handler- the message handler
-
setOnConnectHandler
Sets the on-connect handler.- Parameters:
handler- the on-connect handler
-
setOnDisconnectHandler
Sets the on-disconnect handler.- Parameters:
handler- the on-disconnect handler
-
setOnErrorHandler
Sets the error handler.- Parameters:
handler- the error handler
-
setOnReconnectExhaustedHandler
Sets the reconnect-exhausted handler (invoked once every reconnection attempt has failed).- Parameters:
handler- the reconnect-exhausted handler
-
startHeartbeat
private void startHeartbeat()Starts the heartbeat task. -
heartbeatTick
void heartbeatTick()One heartbeat: check liveness first, then send a ping."Check liveness first" is the reason this method exists. Before it, the only signal of connection health was whether the socket had closed - and a TCP connection can go silently dead without ever producing
onClose(an intermediate device timing out, the peer process hanging, a NAT table entry expiring). In that state pings still go out,onClosenever fires, and reconnection logic never starts - the connection looks fine but is actually dead.When a silent failure is declared, this method **routes through
close()rather than starting a second reconnection path of its own**: closing triggersonClose, reusing the existing reconnect state machine (including the global budget #181 added). Starting a second path would become a second place deciding whether to reconnect - exactly what caused #181. -
recordPong
private void recordPong()Called when a pong is received: records the time and computes the round-trip latency.Called by
onMessage(String)before dispatching to messageHandler - a pong is a link-layer fact and should not depend on whether an upper-layer handler happens to be wired up. -
isAlive
public boolean isAlive(long timeoutMs) Determines whether the connection is still alive, based on pong responses.Returns true when no pong has ever been received. This is deliberate: otherwise a newly established connection would be declared dead within its first heartbeat interval. The cost is that this mechanism cannot detect "the peer never answers pong at all" - but that is the safe direction: if the panel simply never implemented pong, a good connection should not be repeatedly kicked. What it can detect is "used to answer, and stopped" - that is, a silent failure.
- Parameters:
timeoutMs- the liveness threshold, in milliseconds- Returns:
- true if the most recent pong is within the threshold, or none has ever been received
-
getLatencyMs
public long getLatencyMs()Most recently measured round-trip latency, in milliseconds; -1 if not yet measured.- Returns:
- the latency in milliseconds, or -1
-
getLastPongTime
public long getLastPongTime()Timestamp of the most recent pong received; 0 if none has ever been received.- Returns:
- the timestamp, in milliseconds
-
setClock
Test-only clock substitution. -
onOpen
public void onOpen(org.java_websocket.handshake.ServerHandshake handshakedata) - Specified by:
onOpenin classorg.java_websocket.client.WebSocketClient
-
onMessage
- Specified by:
onMessagein classorg.java_websocket.client.WebSocketClient
-
onClose
- Specified by:
onClosein classorg.java_websocket.client.WebSocketClient
-
onError
- Specified by:
onErrorin classorg.java_websocket.client.WebSocketClient
-