How-to guide
Long-running clients
Clients for continuous operation: how the SDK detects connection breaks, restores sessions and subscriptions, and what the application handles itself.
How-to guide
Long-running clients
A client that stays connected to a plant permanently must survive network outages, server restarts, and maintenance windows. The OPC UA .NET SDK handles most of this: it detects the break, reconnects on its own, and restores subscriptions. This page summarizes what happens automatically and which tasks remain with the application. The section Connection monitoring of the configuration page describes the individual settings.
What the SDK handles on a connection break
Types usedOpcClientOpcClientStateOpcClientKeepAlive
As long as UseBreakDetection is enabled (default), the client checks the connection with KeepAlive requests at intervals of KeepAlive.Interval (default 5000 ms). If a KeepAlive request fails, or a service call reports a lost connection (for example with BadConnectionClosed or BadSessionIdInvalid), the following sequence runs at once:
| Step | What the SDK does | State and event |
|---|---|---|
| 1 | The client detects the break. | Reconnecting, event Reconnecting |
| 2 | At intervals of Reconnect (default 10000 ms), the client tries to restore the existing session, for at most Max (default 0, unlimited). | remains Reconnecting |
| 3a | The session can be restored: the client requests the pending notifications of its subscriptions again. | Reconnected, event Reconnected |
| 3b | The session has expired on the server: the client creates a new session and re-creates all subscriptions with their MonitoredItems. | Connected, event Connected |
| 4 | In both cases, the client reloads the namespace table of the server. If UseDynamic or type resolution is active, it also reloads the type system. |
For the application, this means that subscriptions, MonitoredItems, and their event handlers are preserved. You do not need to subscribe again after a connection break.
Connection events at a glance
Types usedOpcClientOpcClientState
The client determines whether the connection to the server exists through its KeepAlive logic and through the results of its service calls. If a call such as ReadNode as a whole fails with a status that indicates a lost connection (for example BadConnectionClosed or BadSessionIdInvalid), the client changes its state at once and does not wait for the next KeepAlive. Other errors of single calls and a bad status of single values in the result do not change the state. The client reports every state change through the following events of the OpcClient class:
| Event | When the client raises it | Meaning |
|---|---|---|
Connecting, Connected | when you call Connect, and after a connection loss if the client created a new session | Connecting reports the start of a connection attempt, Connected its successful completion. |
Reconnecting, Reconnected | after a connection loss, if Use is enabled and Reconnect is greater than 0 | After Reconnecting, the client tries to restore the connection at intervals of Reconnect. Reconnected reports that it was able to keep using the existing session. If it had to create a new session, it raises Connected instead. |
Disconnecting, Disconnected | when you call Disconnect, or implicitly Dispose; also after a connection loss if Reconnect is 0 or Max has elapsed (then only Disconnected, without a preceding Disconnecting) | The connection was closed manually, or the client detected a connection loss that it does not resolve itself. |
BreakDetected | after a connection loss, if Use is enabled and Reconnect is 0 or Max has elapsed, following Disconnected | Signal to restore the connection yourself (see Reconnect yourself). |
StateChanged | on every change of the State property, each time before the events above | general monitoring of all state changes (see Log state changes) |
If UseBreakDetection is disabled, the client does not restore the connection automatically. Your application must then handle such situations itself.
Connect at startup
Types usedOpcClientOpcException
Automatic reconnection only takes effect once a connection has existed. If the server is not reachable when the application starts, Connect throws an OpcException, and the client returns to its previous state. A long-running client therefore retries the connection itself:
var client = new OpcClient("opc.tcp://localhost:4840/");
client.ReconnectInterval = 5000;
while (true) {
try {
client.Connect();
break;
}
catch (OpcException ex) {
Console.WriteLine("Connect failed: {0}", ex.Code);
Thread.Sleep(TimeSpan.FromSeconds(5));
}
}Dim client = New OpcClient("opc.tcp://localhost:4840/")
client.ReconnectInterval = 5000
Do
Try
client.Connect()
Exit Do
Catch ex As OpcException
Console.WriteLine("Connect failed: {0}", ex.Code)
Thread.Sleep(TimeSpan.FromSeconds(5))
End Try
LoopCreate subscriptions once
Types usedOpcClientOpcSubscriptionOpcDataChangeReceivedEventArgs
Create subscriptions once, after the first successful Connect. The SDK restores them itself for a new session, so subscribing again in the handler of the Connected event would duplicate every subscription after a connection break:
// Subscribe once after Connect: the client recreates the subscription for a new session.
OpcSubscription subscription = client.SubscribeDataChange(
"ns=2;s=Machine/Speed",
HandleDataChanged);
...
private static void HandleDataChanged(
object sender,
OpcDataChangeReceivedEventArgs e)
{
Console.WriteLine("Speed: {0}", e.Item.Value);
}'Subscribe once after Connect: the client recreates the subscription for a new session.
Dim subscription As OpcSubscription = client.SubscribeDataChange( _
"ns=2;s=Machine/Speed", _
AddressOf HandleDataChanged)
...
Private Shared Sub HandleDataChanged( _
ByVal sender As Object, _
ByVal e As OpcDataChangeReceivedEventArgs)
Console.WriteLine("Speed: {0}", e.Item.Value)
End SubIf notifications arrive that cannot be decoded, the SDK reports this through the NotificationFailed event, see Handle notifications.
Log state changes
Types usedOpcClientOpcClientStateOpcClientStateChangedEventArgs
For operation and troubleshooting, it helps to log every state change. The StateChanged event occurs before the corresponding individual event and states the old and the new state. A change from Reconnecting to Connected indicates that the client created a new session:
client.StateChanged += HandleStateChanged;
...
private static void HandleStateChanged(
object sender,
OpcClientStateChangedEventArgs e)
{
Console.WriteLine("{0} -> {1}", e.OldState, e.NewState);
if (e.OldState == OpcClientState.Reconnecting && e.NewState == OpcClientState.Connected)
Console.WriteLine("New session, the subscriptions have been recreated.");
}AddHandler client.StateChanged, AddressOf HandleStateChanged
...
Private Shared Sub HandleStateChanged( _
ByVal sender As Object, _
ByVal e As OpcClientStateChangedEventArgs)
Console.WriteLine("{0} -> {1}", e.OldState, e.NewState)
If e.OldState = OpcClientState.Reconnecting AndAlso e.NewState = OpcClientState.Connected Then
Console.WriteLine("New session, the subscriptions have been recreated.")
End If
End SubWhat to do after a new session
Types usedOpcClientOpcNodeIdCollection
Everything the server assigns to a single session is no longer valid after a new session. This mainly affects registered nodes (see Register frequently read nodes). The identifiers returned by RegisterNodes are only valid in the session in which the client registered them. Therefore, register the nodes in the handler of the Connected event. The event occurs on the first connection and after every new session:
client.Connected += HandleConnected;
...
private OpcNodeId[] nodeIds = { "ns=2;s=Machine/Speed", "ns=2;s=Machine/Temperature" };
private OpcNodeIdCollection registeredNodeIds;
private void HandleConnected(object sender, EventArgs e)
{
// Registered node ids are only valid in the session they have been registered in.
this.registeredNodeIds = ((OpcClient)sender).RegisterNodes(this.nodeIds);
}AddHandler client.Connected, AddressOf HandleConnected
...
Private nodeIds As OpcNodeId() = {"ns=2;s=Machine/Speed", "ns=2;s=Machine/Temperature"}
Private registeredNodeIds As OpcNodeIdCollection
Private Sub HandleConnected(ByVal sender As Object, ByVal e As EventArgs)
'Registered node ids are only valid in the session they have been registered in.
Me.registeredNodeIds = CType(sender, OpcClient).RegisterNodes(Me.nodeIds)
End SubFor nodes in other namespaces, also use the namespace URI instead of a hard-coded index, see Address nodes by their namespace URI. After a server restart, the order of its namespaces can change.
Reconnect yourself
If ReconnectInterval is 0, the client does not reconnect on its own. It changes to the Disconnected state and raises the BreakDetected event. The application then decides itself when and how to re-establish the connection, for example after a delay of its own:
client.ReconnectInterval = 0;
client.BreakDetected += HandleBreakDetected;
...
private static void HandleBreakDetected(object sender, EventArgs e)
{
OpcClient client = (OpcClient)sender;
// Decide on your own when and how to connect again.
Task.Run(() => {
Thread.Sleep(TimeSpan.FromSeconds(10));
client.Connect();
});
}client.ReconnectInterval = 0
AddHandler client.BreakDetected, AddressOf HandleBreakDetected
...
Private Shared Sub HandleBreakDetected(ByVal sender As Object, ByVal e As EventArgs)
Dim client As OpcClient = CType(sender, OpcClient)
'Decide on your own when and how to connect again.
Task.Run( _
Sub()
Thread.Sleep(TimeSpan.FromSeconds(10))
client.Connect()
End Sub)
End SubAccess from multiple threads
Types usedOpcClientOpcSubscriptionOpcTaskSchedule
You can use an instance of OpcClient from multiple threads. The client runs its service calls one after another: each call locks the client until the server's response arrives or the timeout expires. This applies to the services Read, Write, Browse, BrowseNext, Call, and TranslateBrowsePathsToNodeIds, to access to historical data, to adding and deleting nodes and references, and to registering nodes. A slow call therefore holds up all other calls of the same client. Connect and Disconnect use the same lock.
The BrowseNodes method, in contrast, runs without this lock. It reads through the node cache of the stack, and this cache does not lock concurrent access. We therefore recommend that you do not call BrowseNodes of one client from multiple threads at the same time.
Subscriptions work in parallel to the service calls. By default, the SDK dispatches their notifications on threads of the .NET thread pool; the SDK internals page describes the details.
While the client re-establishes the connection, new calls fail immediately with the status BadServerNotConnected. Calls that are already waiting for a response at that time end with BadConnectionClosed or BadRequestInterrupted. Handle these cases like other failed calls (see Handle errors).
Checklist
| Topic | Recommendation |
|---|---|
| Startup | Retry Connect in a loop until the server is reachable. |
| Break detection | Keep Use enabled, adjust KeepAlive. and Reconnect to the plant, and limit Max if needed. |
| Subscriptions | Create once after the first Connect, not in the handler of Connected. |
| Registered nodes | Register in the handler of Connected. |
| Threads | Service calls of one client run one after another; do not call BrowseNodes of one client from multiple threads at the same time. |
| NodeIds | Address namespaces through the URI (nsu=). |
| Faulty notifications | Handle Notification. |
| Errors of single operations | Evaluate the status, see Handle errors. |

