| | |
| | | import java.net.URI; |
| | | import java.net.URISyntaxException; |
| | | import java.util.concurrent.*; |
| | | import java.util.concurrent.atomic.AtomicBoolean; |
| | | import java.util.concurrent.atomic.AtomicReference; |
| | | |
| | | /** |
| | |
| | | private ScheduledExecutorService heartbeatExecutor; |
| | | private volatile ScheduledFuture<?> pongTimeoutFuture; |
| | | private final AtomicReference<Long> lastMessageTime = new AtomicReference<>(System.currentTimeMillis()); |
| | | |
| | | // 连接状态标志 |
| | | private final AtomicBoolean isConnected = new AtomicBoolean(false); |
| | | private final AtomicBoolean isConnecting = new AtomicBoolean(false); |
| | | |
| | | private static final String WS_URL_MONIPAN = "wss://wspap.okx.com:8443/ws/v5/private"; |
| | | private static final String WS_URL_SHIPAN = "wss://ws.okx.com:8443/ws/v5/private"; |
| | |
| | | public void init() { |
| | | connect(); |
| | | startHeartbeat(); |
| | | |
| | | // 添加每小时重连的定时任务 |
| | | schedulePeriodicReconnect(); |
| | | } |
| | | |
| | | /** |
| | |
| | | } |
| | | shutdownExecutorGracefully(sharedExecutor); |
| | | |
| | | // if (reconnectScheduler != null) { |
| | | // reconnectScheduler.shutdownNow(); |
| | | // } |
| | | if (reconnectScheduler != null) { |
| | | reconnectScheduler.shutdownNow(); |
| | | } |
| | | } |
| | | |
| | | private void shutdownExecutorGracefully(ExecutorService executor) { |
| | |
| | | * 设置回调函数以监听连接打开、接收消息、关闭和错误事件。 |
| | | */ |
| | | private void connect() { |
| | | // 避免重复连接 |
| | | if (isConnecting.get()) { |
| | | log.info("连接已在进行中,跳过重复连接请求"); |
| | | return; |
| | | } |
| | | |
| | | if (!isConnecting.compareAndSet(false, true)) { |
| | | log.info("连接已在进行中,跳过重复连接请求"); |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | InstrumentsWs.handleEvent(redisUtils); |
| | | wangGeService.initWangGe(); |
| | |
| | | WS_URL = WS_URL_SHIPAN; |
| | | } |
| | | URI uri = new URI(WS_URL); |
| | | |
| | | // 关闭之前的连接(如果存在) |
| | | if (webSocketClient != null) { |
| | | try { |
| | | webSocketClient.closeBlocking(); |
| | | } catch (InterruptedException e) { |
| | | Thread.currentThread().interrupt(); |
| | | log.warn("关闭之前连接时被中断"); |
| | | } |
| | | } |
| | | |
| | | webSocketClient = new WebSocketClient(uri) { |
| | | @Override |
| | | public void onOpen(ServerHandshake handshake) { |
| | | log.info("OKX account-order WebSocket连接成功"); |
| | | // 检查应用是否正在关闭 |
| | | isConnected.set(true); |
| | | isConnecting.set(false); |
| | | |
| | | // 棜查应用是否正在关闭 |
| | | if (!sharedExecutor.isShutdown()) { |
| | | resetHeartbeatTimer(); |
| | | websocketLogin(); |
| | |
| | | @Override |
| | | public void onClose(int code, String reason, boolean remote) { |
| | | log.warn("OKX account-order WebSocket连接关闭: code={}, reason={}", code, reason); |
| | | isConnected.set(false); |
| | | isConnecting.set(false); |
| | | cancelPongTimeout(); |
| | | |
| | | if (sharedExecutor != null && !sharedExecutor.isShutdown() && !sharedExecutor.isTerminated()) { |
| | |
| | | @Override |
| | | public void onError(Exception ex) { |
| | | log.error("OKX account-order WebSocket发生错误", ex); |
| | | isConnected.set(false); |
| | | } |
| | | }; |
| | | |
| | | webSocketClient.connect(); |
| | | } catch (URISyntaxException e) { |
| | | log.error("WebSocket URI格式错误", e); |
| | | isConnecting.set(false); |
| | | } |
| | | } |
| | | |
| | |
| | | private void handleWebSocketMessage(String message) { |
| | | try { |
| | | if ("pong".equals(message)) { |
| | | log.info("收到心跳响应"); |
| | | log.debug("收到心跳响应"); |
| | | cancelPongTimeout(); |
| | | return; |
| | | } |
| | |
| | | return t; |
| | | }); |
| | | |
| | | heartbeatExecutor.scheduleWithFixedDelay(this::checkHeartbeatTimeout, 25, 25, TimeUnit.SECONDS); |
| | | // // 添加每小时重连的定时任务 |
| | | // if (reconnectScheduler != null && !reconnectScheduler.isTerminated()) { |
| | | // reconnectScheduler.shutdownNow(); |
| | | // } |
| | | // |
| | | // reconnectScheduler = Executors.newSingleThreadScheduledExecutor(r -> { |
| | | // Thread t = new Thread(r, "okx-scheduled-reconnect"); |
| | | // t.setDaemon(true); |
| | | // return t; |
| | | // }); |
| | | // |
| | | // // 每小时执行一次重连 |
| | | // reconnectScheduler.scheduleWithFixedDelay(this::performScheduledReconnect, 60, 60, TimeUnit.MINUTES); |
| | | heartbeatExecutor.scheduleWithFixedDelay(this::checkHeartbeatTimeout, |
| | | HEARTBEAT_TIMEOUT, HEARTBEAT_TIMEOUT, TimeUnit.SECONDS); |
| | | } |
| | | |
| | | /** |
| | | * 安排定期重连任务 |
| | | * 每小时执行一次重连以保持连接新鲜度 |
| | | */ |
| | | private void schedulePeriodicReconnect() { |
| | | if (reconnectScheduler != null && !reconnectScheduler.isTerminated()) { |
| | | reconnectScheduler.shutdownNow(); |
| | | } |
| | | |
| | | reconnectScheduler = Executors.newSingleThreadScheduledExecutor(r -> { |
| | | Thread t = new Thread(r, "okx-scheduled-reconnect"); |
| | | t.setDaemon(true); |
| | | return t; |
| | | }); |
| | | |
| | | // 每小时执行一次重连 |
| | | reconnectScheduler.scheduleWithFixedDelay(this::performScheduledReconnect, 60, 60, TimeUnit.MINUTES); |
| | | } |
| | | |
| | | /** |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 执行定时重连任务 |
| | | * 每小时强制重连一次以确保连接的新鲜度 |
| | | */ |
| | | private void performScheduledReconnect() { |
| | | log.info("执行定时重连任务"); |
| | | if (webSocketClient != null && webSocketClient.isOpen()) { |
| | | log.info("关闭当前连接准备重连"); |
| | | webSocketClient.close(); |
| | |
| | | * 若长时间未收到任何消息则主动发送 ping 请求保持连接活跃。 |
| | | */ |
| | | private void checkHeartbeatTimeout() { |
| | | // 只有在连接状态下才检查心跳 |
| | | if (!isConnected.get()) { |
| | | return; |
| | | } |
| | | |
| | | long currentTime = System.currentTimeMillis(); |
| | | long lastTime = lastMessageTime.get(); |
| | | |
| | |
| | | * 在连接意外中断后尝试重新建立连接。 |
| | | */ |
| | | private void reconnectWithBackoff() throws InterruptedException { |
| | | // 如果正在连接,则不重复发起重连 |
| | | if (isConnecting.get()) { |
| | | log.info("连接已在进行中,跳过重连请求"); |
| | | return; |
| | | } |
| | | |
| | | int attempt = 0; |
| | | int maxAttempts = 5; |
| | | long delayMs = 1000; |
| | | |
| | | while (attempt < maxAttempts) { |
| | | while (attempt < maxAttempts && !isConnected.get()) { |
| | | try { |
| | | Thread.sleep(delayMs); |
| | | connect(); |
| | | return; |
| | | |
| | | // 等待连接建立 |
| | | for (int i = 0; i < 10 && isConnecting.get(); i++) { |
| | | Thread.sleep(500); |
| | | } |
| | | |
| | | if (isConnected.get()) { |
| | | log.info("重连成功"); |
| | | return; |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("第{}次重连失败", attempt + 1, e); |
| | | delayMs *= 2; |
| | |
| | | |
| | | log.error("超过最大重试次数({})仍未连接成功", maxAttempts); |
| | | } |
| | | } |
| | | } |