node-opcua v2.14.0客户端重连服务器过慢问题求助
I’ve worked with node-opcua extensively, and the slow reconnection you’re seeing stems mostly from suboptimal connection strategy settings and overly lenient default timeouts. Let’s break down the fixes step by step:
Key Issues in Your Current Setup
- Limited Retry Attempts: Your
connectionStrategysetsmaxRetry: 1, meaning the client only tries to reconnect once after the first failure. After that, it waits for an internal 5-minute timeout before attempting to reconnect again. - Slow Failure Detection: Default TCP keepalive and request timeout settings take too long to detect a dead connection after a hard network cut (like turning off WiFi).
- Subscription Settings Don’t Accelerate Detection: Your subscription’s lifetime and keepalive counts are configured in a way that delays the client’s awareness of server unresponsiveness.
Step-by-Step Fixes
1. Optimize the Connection Strategy
Update your connection strategy to enable infinite retries with reasonable backoff times. This ensures the client keeps trying to reconnect as soon as the network comes back up:
const connectionStrategy = { initialDelay: 2000, // Wait 2s before first retry maxDelay: 10000, // Cap retry delay at 10s to avoid excessive waiting maxRetry: 0, // 0 = infinite retries (critical for quick reconnection) retryOnFailure: true // Ensure retries trigger on connection failures };
maxRetry: 0is the biggest fix here—your original setting of1only allowed one retry, which caused the 5-minute delay before the next reconnection attempt.- The backoff range prevents overwhelming the server when it comes back online.
2. Reduce Request Timeout to Speed Up Failure Detection
Add a requestTimeout to your client options. This tells the client how long to wait for a response before marking a request as timed out, triggering reconnection attempts sooner:
const options: OPCUAClientOptions = { applicationName: 'AppName', connectionStrategy: connectionStrategy, securityMode: MessageSecurityMode.None, securityPolicy: SecurityPolicy.None, endpoint_must_exist: false, requestTimeout: 10000 // Timeout requests after 10s instead of the default 50s };
Your original request timeout hint was 50s, which is why you saw the timed_out_request event only after ~60s. Lowering this makes the client detect unresponsiveness much faster.
3. Adjust Subscription Parameters for Faster Termination
Tweak your subscription settings to make the server terminate the subscription sooner if it loses connection, helping the client realize the issue faster:
const subscription = ClientSubscription.create(session, { requestedPublishingInterval: 10, requestedLifetimeCount: 5, // Terminate if no keepalive for 5 intervals (50ms) requestedMaxKeepAliveCount: 1,// Send keepalive every 1 interval (10ms) maxNotificationsPerPublish: 10, publishingEnabled: true, priority: 10, });
requestedMaxKeepAliveCount: 1ensures the server sends frequent keepalives, so the client gets regular confirmation the connection is alive.requestedLifetimeCount: 5triggers subscription termination quickly if keepalives stop, alerting the client to the disconnect.
4. Reinitialize Subscriptions After Reconnection
Your current code only creates subscriptions once during initial connection. You need to recreate them when the connection is restored. Update your event listeners to handle this:
function attachEventListeners(client) { client.on("connection_reestablished", async () => { console.log(`> Connection reestablished !`); try { currentSession = await client.createSession(); console.log(`> Recreated session !`); initSubscription(client, currentSession); } catch (e) { console.log(`Failed to recreate session: ${e}`); } }) // Keep your other event listeners here... }
Add a currentSession variable at the top of your code to track the active session across reconnections.
Why the close Event Isn’t Triggered
When you force a network cut, the TCP connection enters a "half-open" state—your client doesn’t receive a proper FIN packet from the server, so the socket doesn’t trigger the close event immediately. It takes time for the TCP stack to detect the dead connection via keepalive probes (which have long default timeouts). The timed_out_request event is the client’s way of detecting this unresponsiveness since it doesn’t get a response to its requests.
Full Modified Code
Here’s the complete adjusted code incorporating all fixes:
import { OPCUAClient, MessageSecurityMode, SecurityPolicy, OPCUAClientOptions, ClientSubscription, DataValue, ClientMonitoredItem, ReadValueIdLike, MonitoringParametersOptions, AttributeIds, TimestampsToReturn } from 'node-opcua'; // Server connection const connectionStrategy = { initialDelay: 2000, maxDelay: 10000, maxRetry: 0, retryOnFailure: true }; const options: OPCUAClientOptions = { applicationName: 'AppName', connectionStrategy: connectionStrategy, securityMode: MessageSecurityMode.None, securityPolicy: SecurityPolicy.None, endpoint_must_exist: false, requestTimeout: 10000 }; const url = 'opc.tcp://opcuaserver.com:48010'; let currentSession: any = null; async function main() { console.log('> Connecting...'); const client = OPCUAClient.create(options); await client.connect(url); console.log('> Connected !'); try { console.log('> Creating session...'); currentSession = await client.createSession(); console.log('> Session created !'); attachEventListeners(client); initSubscription(client, currentSession); } catch (e) { console.log(`couldn't create opcua session. error: ${e}`); } } main().catch(err => console.log('Main error:', err)).then(() => console.log('process finished')); function attachEventListeners(client) { client.on("start_reconnection", () => { console.log(`> Starting reconnection...`); }) .on("connection_reestablished", async () => { console.log(`> Connection reestablished !`); try { currentSession = await client.createSession(); console.log(`> Recreated session !`); initSubscription(client, currentSession); } catch (e) { console.log(`Failed to recreate session: ${e}`); } }) .on("connection_failed", (e) => { console.log(`> Connection failed !`, e); }) .on("reconnection_attempt_has_failed", (e) => { console.log(`> Re-connection attempt has failed !`, e); }) .on("close", () => { console.log(`> Connection closed !`); }) .on("backoff", () => { console.log(`> Connection backoff !`); }) .on("after_reconnection", (err) => { console.log("> Reconnection process has been completed: ", err); }) .on("timed_out_request", (request) => { console.log(`> Request has timed out without receiving a response:`); console.log(` Request handle: ${request.requestHandle}, Subscription ID: ${request.subscriptionAcknowledgements?.[0]?.subscriptionId}`); }); } function initSubscription(client, session) { const subscription = ClientSubscription.create(session, { requestedPublishingInterval: 10, requestedLifetimeCount: 5, requestedMaxKeepAliveCount: 1, maxNotificationsPerPublish: 10, publishingEnabled: true, priority: 10, }); subscription.on("keepalive", (x) => { console.log("> Subscription > Keepalive", x); }) .on("terminated", () => { console.log("> Subscription > Terminated"); }); // Subscribe to "Air Conditioner Humidity" sensor const itemToMonitor: ReadValueIdLike = { nodeId: "ns=3;s=AirConditioner_6.Humidity", attributeId: AttributeIds.Value }; const parameters: MonitoringParametersOptions = { samplingInterval: 10, discardOldest: true, queueSize: 10 }; const monitoredItem = ClientMonitoredItem.create( subscription, itemToMonitor, parameters, TimestampsToReturn.Both ); monitoredItem.on("err", (err) => { console.log(`subscribed resource error: ${err}`); }); monitoredItem.on("changed", (value: DataValue) => { console.log(`subscribed resource changed value: ${value.value.value}`); }); }
With these changes, your client will detect connection failures within 10 seconds, start reconnection attempts immediately, and restore subscriptions as soon as the network is back up—no more 5-minute waits.
内容的提问来源于stack exchange,提问作者Eduardo G.R.

