Anleitung
Clients für den Dauerbetrieb
Clients für den Dauerbetrieb: wie das SDK Verbindungsabbrüche erkennt, Sitzungen und Subscriptions wiederherstellt und was die Anwendung selbst übernimmt.
Anleitung
Clients für den Dauerbetrieb
Ein Client, der dauerhaft mit einer Anlage verbunden ist, muss Netzwerkausfälle, Neustarts des Servers und Wartungsfenster überstehen. Den größten Teil davon übernimmt das SDK: Es erkennt den Abbruch, verbindet sich selbstständig neu und stellt Subscriptions wieder her. Diese Seite fasst zusammen, was dabei automatisch geschieht und welche Aufgaben bei der Anwendung bleiben. Die einzelnen Einstellungen beschreibt der Abschnitt Verbindungsüberwachung der Konfiguration.
Was das SDK bei einem Abbruch übernimmt
Beteiligte TypenOpcClientOpcClientStateOpcClientKeepAlive
Solange UseBreakDetection aktiviert ist (Standard), prüft der Client die Verbindung mit KeepAlive-Anfragen im Abstand von KeepAlive.Interval (Standard 5000 ms). Wenn eine KeepAlive-Anfrage fehlschlägt oder ein Service-Aufruf einen Verbindungsverlust meldet (zum Beispiel mit BadConnectionClosed oder BadSessionIdInvalid), läuft sofort der folgende Ablauf ab:
| Schritt | Was das SDK tut | Zustand und Ereignis |
|---|---|---|
| 1 | Der Client erkennt den Abbruch. | Reconnecting, Ereignis Reconnecting |
| 2 | Im Abstand von Reconnect (Standard 10000 ms) versucht der Client, die bestehende Sitzung wiederherzustellen, längstens für Max (Standard 0, unbegrenzt). | bleibt Reconnecting |
| 3a | Die Sitzung lässt sich wiederherstellen: Der Client fordert die ausstehenden Benachrichtigungen seiner Subscriptions erneut an. | Reconnected, Ereignis Reconnected |
| 3b | Die Sitzung ist auf dem Server abgelaufen: Der Client erzeugt eine neue Sitzung und legt alle Subscriptions mit ihren MonitoredItems neu an. | Connected, Ereignis Connected |
| 4 | In beiden Fällen lädt der Client die Namespace-Tabelle des Servers neu. Wenn UseDynamic oder die Typauflösung aktiv ist, lädt er auch das Typsystem neu. |
Für die Anwendung heißt das: Subscriptions, MonitoredItems und ihre Ereignishandler bleiben erhalten. Sie müssen nach einem Abbruch nichts neu abonnieren.
Verbindungsereignisse im Überblick
Beteiligte TypenOpcClientOpcClientState
Ob die Verbindung zum Server besteht, ermittelt der Client über die KeepAlive-Logik und über die Ergebnisse seiner Service-Aufrufe. Wenn ein Aufruf wie ReadNode als Ganzes mit einem Status fehlschlägt, der auf eine verlorene Verbindung hinweist (zum Beispiel BadConnectionClosed oder BadSessionIdInvalid), ändert der Client seinen Zustand sofort und wartet nicht auf das nächste KeepAlive. Andere Fehler einzelner Aufrufe und ein schlechter Status einzelner Werte im Ergebnis ändern den Zustand nicht. Jede Zustandsänderung meldet der Client über die folgenden Ereignisse der Klasse OpcClient:
| Ereignis | Wann der Client es auslöst | Bedeutung |
|---|---|---|
Connecting, Connected | beim Aufruf von Connect und nach einem Verbindungsverlust, wenn der Client dabei eine neue Sitzung erstellt hat | Connecting meldet den Beginn eines Verbindungsversuchs, Connected seinen erfolgreichen Abschluss. |
Reconnecting, Reconnected | nach einem Verbindungsverlust, wenn Use aktiviert und Reconnect größer als 0 ist | Nach Reconnecting versucht der Client im Abstand von Reconnect, die Verbindung wiederherzustellen. Reconnected meldet, dass er die bestehende Sitzung weiterverwenden konnte; musste er eine neue Sitzung erstellen, löst er stattdessen Connected aus. |
Disconnecting, Disconnected | beim Aufruf von Disconnect oder implizit von Dispose; außerdem nach einem Verbindungsverlust, wenn Reconnect 0 ist oder Max abgelaufen ist (dann nur Disconnected, ohne vorheriges Disconnecting) | Die Verbindung ist manuell getrennt, oder der Client hat einen Verbindungsverlust festgestellt, den er nicht selbst behebt. |
BreakDetected | nach einem Verbindungsverlust, wenn Use aktiviert und Reconnect 0 ist oder Max abgelaufen ist, im Anschluss an Disconnected | Anlass, die Verbindung selbst wiederherzustellen (siehe Selbst wiederverbinden). |
StateChanged | bei jeder Änderung der Eigenschaft State, jeweils vor den Ereignissen oben | allgemeine Überwachung aller Zustandswechsel (siehe Zustandswechsel protokollieren) |
Wenn UseBreakDetection deaktiviert ist, stellt der Client die Verbindung nicht automatisch wieder her. Solche Situationen muss dann Ihre Anwendung selbst behandeln.
Beim Start verbinden
Beteiligte TypenOpcClientOpcException
Das automatische Wiederverbinden greift erst, wenn eine Verbindung einmal bestanden hat. Wenn der Server beim Start der Anwendung nicht erreichbar ist, löst Connect eine OpcException aus, und der Client kehrt in seinen vorherigen Zustand zurück. Ein Client für den Dauerbetrieb wiederholt den Verbindungsaufbau deshalb selbst:
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
LoopSubscriptions einmal anlegen
Beteiligte TypenOpcClientOpcSubscriptionOpcDataChangeReceivedEventArgs
Legen Sie Subscriptions einmal nach dem ersten erfolgreichen Connect an. Weil das SDK sie bei einer neuen Sitzung selbst wiederherstellt, würde ein erneutes Abonnieren im Handler des Ereignisses Connected jede Subscription nach einem Abbruch verdoppeln:
// 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 SubWenn Benachrichtigungen ankommen, die sich nicht dekodieren lassen, meldet das SDK das über das Ereignis NotificationFailed, siehe Benachrichtigungen behandeln.
Zustandswechsel protokollieren
Beteiligte TypenOpcClientOpcClientStateOpcClientStateChangedEventArgs
Für Betrieb und Fehlersuche ist es hilfreich, jeden Zustandswechsel zu protokollieren. Das Ereignis StateChanged tritt vor dem jeweiligen Einzelereignis auf und nennt alten und neuen Zustand. Der Wechsel von Reconnecting nach Connected zeigt an, dass der Client eine neue Sitzung erstellt hat:
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 SubWas nach einer neuen Sitzung zu tun bleibt
Beteiligte TypenOpcClientOpcNodeIdCollection
Alles, was der Server einer einzelnen Sitzung zuordnet, gilt nach einer neuen Sitzung nicht mehr. Das betrifft vor allem registrierte Nodes (siehe Häufig gelesene Nodes registrieren). Die von RegisterNodes gelieferten Identifier gelten nur in der Sitzung, in der der Client sie registriert hat. Registrieren Sie die Nodes deshalb im Handler des Ereignisses Connected. Das Ereignis tritt beim ersten Verbinden und nach jeder neuen Sitzung auf:
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 SubVerwenden Sie für Nodes fremder Namespaces außerdem die Namespace-URI statt eines fest eingetragenen Index, siehe Nodes über die Namespace-URI adressieren. Nach einem Neustart des Servers kann sich die Reihenfolge seiner Namespaces ändern.
Selbst wiederverbinden
Wenn ReconnectInterval 0 ist, verbindet sich der Client nicht selbst neu. Er wechselt in den Zustand Disconnected und löst das Ereignis BreakDetected aus. Die Anwendung entscheidet dann selbst, wann und wie sie die Verbindung wieder aufbaut, zum Beispiel nach einer eigenen Wartezeit:
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 SubAus mehreren Threads zugreifen
Beteiligte TypenOpcClientOpcSubscriptionOpcTaskSchedule
Eine Instanz von OpcClient können Sie aus mehreren Threads verwenden. Ihre Service-Aufrufe führt der Client dabei nacheinander aus: Jeder Aufruf sperrt den Client, bis die Antwort des Servers vorliegt oder die Zeit abgelaufen ist. Das betrifft die Services Read, Write, Browse, BrowseNext, Call und TranslateBrowsePathsToNodeIds, den Zugriff auf historische Daten, das Hinzufügen und Löschen von Nodes und Referenzen sowie das Registrieren von Nodes. Ein langsamer Aufruf hält damit alle anderen Aufrufe desselben Clients auf. Connect und Disconnect verwenden dieselbe Sperre.
Die Methode BrowseNodes läuft dagegen ohne diese Sperre. Sie liest über den Node-Cache des Stacks, und dieser Cache sperrt gleichzeitige Zugriffe nicht. Wir empfehlen deshalb, BrowseNodes eines Clients nicht aus mehreren Threads gleichzeitig aufzurufen.
Subscriptions arbeiten parallel zu den Service-Aufrufen. Ihre Benachrichtigungen verteilt das SDK standardmäßig auf Threads des .NET-Thread-Pools; Einzelheiten beschreibt die Seite Abläufe im SDK.
Solange der Client die Verbindung wiederherstellt, scheitern neue Aufrufe sofort mit dem Status BadServerNotConnected. Aufrufe, die zu diesem Zeitpunkt schon auf eine Antwort warten, enden mit BadConnectionClosed oder BadRequestInterrupted. Behandeln Sie diese Fälle wie andere fehlgeschlagene Aufrufe (siehe Fehler behandeln).
Checkliste
| Thema | Empfehlung |
|---|---|
| Start | Connect in einer Schleife wiederholen, bis der Server erreichbar ist. |
| Abbrucherkennung | Use aktiviert lassen, KeepAlive. und Reconnect an die Anlage anpassen, bei Bedarf Max begrenzen. |
| Subscriptions | einmal nach dem ersten Connect anlegen, nicht im Handler von Connected. |
| Registrierte Nodes | im Handler von Connected registrieren. |
| Threads | Service-Aufrufe eines Clients laufen nacheinander; BrowseNodes eines Clients nicht aus mehreren Threads gleichzeitig aufrufen. |
| NodeIds | Namespaces über die URI adressieren (nsu=). |
| Fehlerhafte Benachrichtigungen | Notification behandeln. |
| Fehler einzelner Operationen | Status auswerten, siehe Fehler behandeln. |

