diff --git a/core/pva/src/main/java/org/epics/pva/client/ClientTCPHandler.java b/core/pva/src/main/java/org/epics/pva/client/ClientTCPHandler.java index 1f721b93a2..6b44a584cf 100644 --- a/core/pva/src/main/java/org/epics/pva/client/ClientTCPHandler.java +++ b/core/pva/src/main/java/org/epics/pva/client/ClientTCPHandler.java @@ -98,7 +98,8 @@ class ClientTCPHandler extends TCPHandler /** Time [ms] when this client sent last message to server */ private volatile long last_message_sent; - private static final RequestEncoder echo_request = new EchoRequest(); + /** Creates echo requests, tracks the counter for this TCP connection */ + private final EchoRequest echo_request = new EchoRequest(); /** Indicates completion of the connection validation: * Server sent connection validation request, @@ -112,7 +113,7 @@ class ClientTCPHandler extends TCPHandler public ClientTCPHandler(final PVAClient client, final InetSocketAddress address, final Guid guid, final boolean tls) throws Exception { super(createSocket(address, tls), true); - logger.log(Level.FINE, () -> "TCPHandler " + guid + " for " + address + " created ============================"); + logger.log(Level.FINE, () -> "TCPHandler " + (tls ? "(TLS) " : "") + guid + " for " + address + " created ============================"); this.client = client; this.guid = guid; @@ -248,7 +249,7 @@ private void checkResponsiveness() if (idle > PVASettings.EPICS_PVA_CONN_TMO * 1000) { // If silent for full EPICS_CA_CONN_TMO, disconnect and start over - logger.log(Level.FINE, () -> this + " silent for " + idle + "ms, closing"); + logger.log(Level.FINE, () -> this + " idle for " + idle + "ms, closing"); client.shutdownConnection(this); return; } @@ -268,13 +269,13 @@ private void checkResponsiveness() request_echo = true; } - // How long have we been silent, which could case the server to close connection? + // How long have we been silent, which could cause the server to close connection? final long silent = now - last_message_sent; if (! request_echo && silent >= PVASettings.EPICS_PVA_CONN_TMO * 1000 / 2) { // With default EPICS_CA_CONN_TMO of 30 seconds, - // Echo requested every 15 seconds. - logger.log(Level.FINE, () -> "Client to " + this + " silent for " + silent + "ms, requesting echo"); + // Echo sent every 15 seconds to inform server that this client is still alive. + logger.log(Level.FINE, () -> "Client to " + this + " silent for " + silent + "ms, sending echo"); request_echo = true; } @@ -289,6 +290,12 @@ private void checkResponsiveness() } } + /** @return Most recently sent echo request */ + String getActiveEchoRequest() + { + return echo_request.getActiveRequest(); + } + /** Called whenever e.g. value is received and server is thus alive */ void markAlive() { diff --git a/core/pva/src/main/java/org/epics/pva/client/ClientUDPHandler.java b/core/pva/src/main/java/org/epics/pva/client/ClientUDPHandler.java index bc7051caa5..e5180128d1 100644 --- a/core/pva/src/main/java/org/epics/pva/client/ClientUDPHandler.java +++ b/core/pva/src/main/java/org/epics/pva/client/ClientUDPHandler.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2019-2023 Oak Ridge National Laboratory. + * Copyright (c) 2019-2025 Oak Ridge National Laboratory. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -16,6 +16,7 @@ import java.net.StandardSocketOptions; import java.nio.ByteBuffer; import java.nio.channels.DatagramChannel; +import java.util.Arrays; import java.util.logging.Level; import org.epics.pva.PVASettings; @@ -337,7 +338,15 @@ private boolean handleSearchReply(final InetSocketAddress from, final byte versi // Server may reply with list of PVs that it does _not_ have... if (! response.found) - search_response.handleSearchResponse(-1, server, version, response.guid, response.tls); + { + // Did server provide list of channels that it _doesn't_ know?! + if (response.cid.length > 0) + logger.log(Level.FINE, + "Server " + from + " sent search reply for not-found channels " + + Arrays.toString(response.cid)); + else // Server simply indicates its presence, no channel detail + search_response.handleSearchResponse(-1, server, version, response.guid, response.tls); + } else for (int cid : response.cid) search_response.handleSearchResponse(cid, server, version, response.guid, response.tls); diff --git a/core/pva/src/main/java/org/epics/pva/client/EchoHandler.java b/core/pva/src/main/java/org/epics/pva/client/EchoHandler.java index 1c1fc19469..521715db11 100644 --- a/core/pva/src/main/java/org/epics/pva/client/EchoHandler.java +++ b/core/pva/src/main/java/org/epics/pva/client/EchoHandler.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2019 Oak Ridge National Laboratory. + * Copyright (c) 2019-2025 Oak Ridge National Laboratory. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -37,11 +37,12 @@ public void handleCommand(final ClientTCPHandler tcp, final ByteBuffer buffer) t { final byte[] payload = new byte[payload_size]; buffer.get(payload); - if (Arrays.equals(payload, EchoRequest.CHECK)) + final String expected = tcp.getActiveEchoRequest(); + if (Arrays.equals(payload, expected.getBytes())) logger.log(Level.FINE, () -> "Received ECHO:\n" + Hexdump.toHexdump(payload)); else { - logger.log(Level.WARNING, this + " received invalid echo reply:\n" + + logger.log(Level.WARNING, this + " received invalid echo reply, expected " + expected + ":\n" + Hexdump.toHexdump(payload)); return; } diff --git a/core/pva/src/main/java/org/epics/pva/client/EchoRequest.java b/core/pva/src/main/java/org/epics/pva/client/EchoRequest.java index 6b0169ffc2..abd296f170 100644 --- a/core/pva/src/main/java/org/epics/pva/client/EchoRequest.java +++ b/core/pva/src/main/java/org/epics/pva/client/EchoRequest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2019 Oak Ridge National Laboratory. + * Copyright (c) 2019-2025 Oak Ridge National Laboratory. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -10,6 +10,7 @@ import static org.epics.pva.PVASettings.logger; import java.nio.ByteBuffer; +import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import org.epics.pva.common.PVAHeader; @@ -21,7 +22,9 @@ @SuppressWarnings("nls") class EchoRequest implements RequestEncoder { - static final byte[] CHECK = new byte[] { 'e', 'c', 'h', 'o' }; + // Random number so replies don't all start with 'echo00' + private final AtomicInteger counter = new AtomicInteger((int)(Math.random() * 100)); + private volatile String active_check = ""; @Override public void encodeRequest(final byte version, final ByteBuffer buffer) throws Exception @@ -33,10 +36,19 @@ public void encodeRequest(final byte version, final ByteBuffer buffer) throws Ex PVAHeader.encodeMessageHeader(buffer, PVAHeader.FLAG_NONE, PVAHeader.CMD_ECHO, 0); } else - { + { // Issue next 'echo12' + final int count = counter.incrementAndGet(); + active_check = String.format("echo%02d", count % 100); + final byte[] check = active_check.getBytes(); logger.log(Level.FINE, () -> "Sending ECHO request (Version " + version + ")"); - PVAHeader.encodeMessageHeader(buffer, PVAHeader.FLAG_NONE, PVAHeader.CMD_ECHO, CHECK.length); - buffer.put(CHECK); + PVAHeader.encodeMessageHeader(buffer, PVAHeader.FLAG_NONE, PVAHeader.CMD_ECHO, check.length); + buffer.put(check); } } + + /** @return Most recently sent echo request */ + public String getActiveRequest() + { + return active_check; + } } diff --git a/core/pva/src/main/java/org/epics/pva/client/PVAClient.java b/core/pva/src/main/java/org/epics/pva/client/PVAClient.java index 4f22ad140a..a51a711342 100644 --- a/core/pva/src/main/java/org/epics/pva/client/PVAClient.java +++ b/core/pva/src/main/java/org/epics/pva/client/PVAClient.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2019-2023 Oak Ridge National Laboratory. + * Copyright (c) 2019-2025 Oak Ridge National Laboratory. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -124,9 +124,9 @@ public Collection list(final TimeUnit unit, final long duration) thr return list_replies.values(); } - private void handleListResponse(final InetSocketAddress server, final int version, final Guid guid) + private void handleListResponse(final InetSocketAddress server, final int version, final Guid guid, final boolean tls) { - logger.log(Level.FINE, () -> guid + " version " + version + ": tcp@" + server); + logger.log(Level.FINE, () -> "Server list response: " + guid + " version " + version + ", tcp@" + server + (tls ? " (TLS)" : "")); final ServerInfo info = list_replies.computeIfAbsent(guid, g -> new ServerInfo(g)); info.version = version; info.addresses.add(server); @@ -221,7 +221,7 @@ void handleSearchResponse(final int channel_id, final InetSocketAddress server, // Generic server 'list' response? if (channel_id < 0) { - handleListResponse(server, version, guid); + handleListResponse(server, version, guid, tls); return; }