Skip to content

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.

This content was machine translated and may contain errors. Read in German

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:

StepWhat the SDK doesState and event
1The client detects the break.Reconnecting, event Reconnecting
2At intervals of ReconnectInterval (default 10000 ms), the client tries to restore the existing session, for at most MaxReconnectDuration (default 0, unlimited).remains Reconnecting
3aThe session can be restored: the client requests the pending notifications of its subscriptions again.Reconnected, event Reconnected
3bThe session has expired on the server: the client creates a new session and re-creates all subscriptions with their MonitoredItems.Connected, event Connected
4In 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:

EventWhen the client raises itMeaning
Connecting, Connectedwhen you call Connect, and after a connection loss if the client created a new sessionConnecting reports the start of a connection attempt, Connected its successful completion.
Reconnecting, Reconnectedafter a connection loss, if UseBreakDetection is enabled and ReconnectInterval is greater than 0After Reconnecting, the client tries to restore the connection at intervals of ReconnectInterval. 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, Disconnectedwhen you call Disconnect, or implicitly Dispose; also after a connection loss if ReconnectInterval is 0 or MaxReconnectDuration 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.
BreakDetectedafter a connection loss, if UseBreakDetection is enabled and ReconnectInterval is 0 or MaxReconnectDuration has elapsed, following DisconnectedSignal to restore the connection yourself (see Reconnect yourself).
StateChangedon every change of the State property, each time before the events abovegeneral 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:

csharp
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));
    }
}
vbnet
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
Loop

Create 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:

csharp
// 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);
}
vbnet
'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 Sub

If 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:

csharp
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.");
}
vbnet
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 Sub

What 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:

csharp
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);
}
vbnet
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 Sub

For 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 ​

Types usedOpcClient

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:

csharp
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();
    });
}
vbnet
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 Sub

Access 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 ​

TopicRecommendation
StartupRetry Connect in a loop until the server is reachable.
Break detectionKeep UseBreakDetection enabled, adjust KeepAlive.Interval and ReconnectInterval to the plant, and limit MaxReconnectDuration if needed.
SubscriptionsCreate once after the first Connect, not in the handler of Connected.
Registered nodesRegister in the handler of Connected.
ThreadsService calls of one client run one after another; do not call BrowseNodes of one client from multiple threads at the same time.
NodeIdsAddress namespaces through the URI (nsu=).
Faulty notificationsHandle NotificationFailed.
Errors of single operationsEvaluate the status, see Handle errors.