Zum Inhalt springen

Anleitung

Anfragen überwachen

Anfrage- und Antwort-Nachrichten des Servers überwachen, etwa zur Protokollierung oder für eigene Prüfungen, und Kennzahlen des Servers abfragen.

Anleitung

Anfragen überwachen ​

Beteiligte TypenOpcServerOpcRequestValidatingEventArgsOpcRequestValidatingEventHandlerOpcRequestProcessingEventArgsOpcRequestProcessingEventHandlerOpcRequestProcessedEventArgsOpcRequestProcessedEventHandlerOpcRequestValidatedEventArgsOpcRequestValidatedEventHandlerIOpcServiceRequestIOpcServiceResponse

Ablauf der Anfrageverarbeitung ​

Der Server verarbeitet und validiert die Anfragen der Clients als Instanzen der Schnittstelle IOpcServiceRequest. Er beantwortet sie mit Instanzen der Schnittstelle IOpcServiceResponse. Über die Ereignisse RequestProcessing, RequestValidating, RequestValidated und RequestProcessed können Sie die empfangenen Anfragen mit eigenen Methoden zusätzlich überwachen, protokollieren, lenken oder verweigern. Das ist besonders dann sinnvoll, wenn die Mechanismen des OPC UA .NET SDK den Anforderungen des Projekts nicht genügen, vor allem bei bestimmten Einschränkungen. Von der Anfrage (engl. Request) bis zur Antwort (engl. Response) durchläuft die Verarbeitung diese Schritte:

  1. Empfang der Rohdaten einer Anfrage (Protokollebene des SDK)
  2. Deserialisierung der Rohdaten zu einer Anfrage (Nachrichtenebene des SDK)
  3. Vorverarbeitung der Anfrage: Ereignis RequestProcessing
  4. Zuteilung der Anfrage zum zuständigen Dienst (Dienstebene des SDK)
  5. Validierung der Anfrage
    1. Standardvalidierungen (Sitzung, Identität usw.)
    2. benutzerdefinierte Validierung: Ereignis RequestValidating
    3. abschließende Validierung (Prüfung der benutzerdefinierten Validierung)
    4. benutzerdefinierter Abschluss der Validierung: Ereignis RequestValidated
  6. Verarbeitung der Anfrage (Anwendungsebene des SDK)
  7. Erzeugen der Antwort über den zuständigen Dienst (Dienstebene des SDK)
  8. Nachverarbeitung der Anfrage und ihrer Antwort: Ereignis RequestProcessed
  9. Serialisierung der Antwort zu Rohdaten (Nachrichtenebene des SDK)
  10. Senden der Rohdaten der Antwort (Protokollebene des SDK)

Anfragen vorverarbeiten ​

Mit den Ereignissen aus den Schritten 3, 5.2, 5.4 und 8 überwachen oder beeinflussen Sie den Ablauf der Anfrageverarbeitung zum jeweiligen Zeitpunkt mit eigenem Code. Direkt nachdem der Server die Nutzdaten empfangen und als Instanz von IOpcServiceRequest aufbereitet hat, führt er den Code des Ereignisses RequestProcessing aus. Die Informationen dieses Ereignisses dienen vor allem der Diagnose des Nachrichtenverkehrs zwischen Client und Server.

Ein Eventhandler dieses Ereignisses sollte keine Exception auslösen:

csharp
private static void HandleRequestProcessing(object sender, OpcRequestProcessingEventArgs e)
{
    Console.Write("Processing: " + e.Request.ToString());
}

// ...

server.RequestProcessing += HandleRequestProcessing;
vbnet
Private Shared Sub HandleRequestProcessing(sender As Object, e As OpcRequestProcessingEventArgs)
    Console.Write("Processing: " & e.Request.ToString())
End Sub

' ...

AddHandler server.RequestProcessing, AddressOf HandleRequestProcessing

Anfragen validieren ​

Der Kontext in den OpcRequestProcessingEventArgs ist stets eine Instanz der Klasse OpcContext. Sie beschreibt nur die allgemeine Umgebung der Nachrichtenverarbeitung. Im darauf folgenden Ereignis RequestValidating ergänzt der Server diese Informationen um Angaben zur Sitzung (engl. Session) und zur Identität (engl. Identity). Bei Anfragen, die eine Sitzung voraussetzen, ist der bereitgestellte OpcContext die Spezialisierung OpcOperationContext.

Über den OpcOperationContext können Sie dann zusätzlich sitzungsbezogen validieren:

csharp
server.RequestValidating += HandleRequestValidating;
...
private static readonly Dictionary<OpcNodeId, int> nodesPerSession
        = new Dictionary<OpcNodeId, int>();
 
private static void HandleRequestValidating(
        object sender,
        OpcRequestValidatingEventArgs e)
{
    Console.Write(" -> Validating: " + e.Request.ToString());
 
    if (e.RequestType == OpcRequestType.AddNodes) {
        var sessionId = e.Context.SessionId;
        var request = (OpcAddNodesRequest)e.Request;
 
        lock (nodesPerSession) {
            int count;
            nodesPerSession.TryGetValue(sessionId, out count);
            count += request.Commands.Count;
 
            // Reject the request in case it would exceed 100 AddNode commands per session.
            if (count > 100)
                e.Cancel = true;
            else
                nodesPerSession[sessionId] = count;
        }
    }
}
vbnet
AddHandler server.RequestValidating, AddressOf HandleRequestValidating
...
Private Shared ReadOnly nodesPerSession As New Dictionary(Of OpcNodeId, Integer)()

Private Shared Sub HandleRequestValidating( _
        ByVal sender As Object, _
        ByVal e As OpcRequestValidatingEventArgs)
    Console.Write(" -> Validating: " & e.Request.ToString())

    If e.RequestType = OpcRequestType.AddNodes Then
        Dim sessionId = e.Context.SessionId
        Dim request = CType(e.Request, OpcAddNodesRequest)

        SyncLock nodesPerSession
            Dim count As Integer
            nodesPerSession.TryGetValue(sessionId, count)
            count += request.Commands.Count

            'Reject the request in case it would exceed 100 AddNode commands per session.
            If count > 100 Then
                e.Cancel = True
            Else
                nodesPerSession(sessionId) = count
            End If
        End SyncLock
    End If
End Sub

Das gezeigte Beispiel beschränkt die Anzahl der Aufrufe von AddNode je Sitzung auf 100. Eine Anfrage, die die Beschränkung überschreiten würde, verweigert der Eventhandler. Dazu setzt er die Eigenschaft Cancel der Ereignisargumente auf true. Damit setzt das SDK den Code der Eigenschaft Result der Ereignisargumente automatisch auf BadNotSupported. Sie können die Anfrage auch abbrechen, indem Sie (zusätzlich) einen „Bad“-Code setzen. Ein Eventhandler des Ereignisses RequestValidating darf eine Exception auslösen.

Wenn der Eventhandler eine Exception auslöst, Cancel auf true setzt oder Result auf einen „Bad“-Code setzt, führt der Server die Eventhandler des Ereignisses RequestValidated nicht aus. Das entspricht dem Validating-Validated-Muster aus dem .NET Framework. Wenn kein Eventhandler die Anfrage abbricht, führt der Server die Eventhandler des Ereignisses RequestValidated aus:

csharp
void HandleRequestValidated(object sender, OpcRequestValidatedEventArgs e)
{
    Console.Write(" -> Validated");
}

// ...

server.RequestValidated += HandleRequestValidated;
vbnet
Sub HandleRequestValidated(sender As Object, e As OpcRequestValidatedEventArgs)
    Console.Write(" -> Validated")
End Sub

' ...

AddHandler server.RequestValidated, AddressOf HandleRequestValidated

Wie beim Ereignis RequestProcessing dienen die Informationen vor allem der Diagnose des Nachrichtenverkehrs zwischen Client und Server. Das Ereignis ist nützlich, weil der Server erst nach seinem Aufruf versucht, die Anfrage zu bearbeiten und zu beantworten. Ein Eventhandler dieses Ereignisses sollte keine Exception auslösen. Nachdem der Server die Anfrage verarbeitet hat, beantwortet er sie mit den resultierenden Ergebnissen.

Anfragen nachverarbeiten ​

Die dabei erzeugte Antwort werten Sie zusammen mit der Anfrage im Ereignis RequestProcessed aus:

csharp
private static void HandleRequestProcessed(object sender, OpcRequestProcessedEventArgs e)
{
    if (e.Response.Success)
        Console.WriteLine(" -> Processed!");
    else
        Console.WriteLine(" -> FAILED: {0}!", e.Exception?.Message ?? e.Response.ToString());
}

// ...

server.RequestProcessed += HandleRequestProcessed;
vbnet
Private Shared Sub HandleRequestProcessed(sender As Object, e As OpcRequestProcessedEventArgs)
    If e.Response.Success Then
        Console.WriteLine(" -> Processed!")
    Else
        Console.WriteLine(" -> FAILED: {0}!", If(e.Exception?.Message, e.Response.ToString()))
    End If
End Sub

' ...

AddHandler server.RequestProcessed, AddressOf HandleRequestProcessed

Wie das Beispiel zeigt, liefern die Argumente des Ereignisses zusätzlich Informationen über eine gegebenenfalls aufgetretene Ausnahme (engl. Exception). Auch dieser Eventhandler sollte keine Exception auslösen. Welchen Service die Anfrage aufgerufen hat, nennt die Eigenschaft RequestType, auch bei Services, die der Server nicht unterstützt. Solche Anfragen erkennen Sie an der Eigenschaft IsUnsupportedService.

Veraltete und auffällige Anfragen behandeln ​

Beteiligte TypenOpcServerOpcRequestJudgementEventArgsOpcRequestImpeachment

Bevor der Server eine Anfrage verarbeitet, prüft er sie auf Auffälligkeiten (OpcRequestImpeachment), zum Beispiel ob sie veraltet ist, weil ihr Zeitstempel zu weit in der Vergangenheit liegt. Standardmäßig weist der Server eine solche Anfrage zurück. Über das Ereignis RequestJudgement können Sie diese Beurteilung übersteuern: Wenn Sie die Eigenschaft Dismiss der Argumente auf true setzen, verarbeitet der Server die Anfrage trotzdem. Das ist zum Beispiel hilfreich, wenn die Uhren von Client und Server voneinander abweichen:

csharp
server.RequestJudgement += HandleRequestJudgement;
...
private static void HandleRequestJudgement(
        object sender,
        OpcRequestJudgementEventArgs e)
{
    // Accept outdated requests, e.g. in case the clocks of client and server differ.
    if (e.Impeachment == OpcRequestImpeachment.Outdated)
        e.Dismiss = true;
}
vbnet
AddHandler server.RequestJudgement, AddressOf HandleRequestJudgement
...
Private Shared Sub HandleRequestJudgement( _
        ByVal sender As Object, _
        ByVal e As OpcRequestJudgementEventArgs)
    'Accept outdated requests, e.g. in case the clocks of client and server differ.
    If e.Impeachment = OpcRequestImpeachment.Outdated Then
        e.Dismiss = True
    End If
End Sub

Sitzungen überwachen ​

Beteiligte TypenOpcServerOpcSessionOpcSessionEventArgsOpcSessionActivationFailedEventArgsOpcSubscriptionEventArgs

Neben den Anfragen lassen sich auch die Sitzungen und Subscriptions der Clients überwachen. Dazu stellt der Server unter anderem die Ereignisse SessionCreated, SessionActivated, SessionActivationFailed, SessionClosing, SubscriptionCreated und SubscriptionDeleted bereit. SessionActivationFailed tritt bei jeder abgelehnten Anmeldung ein (siehe Audit-Ereignisse). Die aktuell bestehenden Sitzungen liefert zudem die Methode GetSessions des Servers:

csharp
server.SessionCreated += (sender, e) => Console.WriteLine("Session created: {0}", e.Session.Name);
server.SessionClosing += (sender, e) => Console.WriteLine("Session closing: {0}", e.Session.Name);
 
server.SubscriptionCreated += (sender, e) => Console.WriteLine(
        "Subscription {0} created in session {1}",
        e.Subscription.Id,
        e.Subscription.Session.Name);
vbnet
AddHandler server.SessionCreated, Sub(sender, e) Console.WriteLine("Session created: {0}", e.Session.Name)
AddHandler server.SessionClosing, Sub(sender, e) Console.WriteLine("Session closing: {0}", e.Session.Name)

AddHandler server.SubscriptionCreated, Sub(sender, e) Console.WriteLine( _
        "Subscription {0} created in session {1}", _
        e.Subscription.Id, _
        e.Subscription.Session.Name)

Gleichzeitige Anfragen behandeln ​

Beteiligte TypenOpcServerOpcNodeManagerOpcReadVariableValueCallbackOpcWriteVariableValueCallbackIOpcNodeHistoryProviderOpcNodeHistorian

Der Server verarbeitet die Anfragen seiner Clients parallel, jede Anfrage in einem eigenen Task. Die Ereignisse RequestProcessing, RequestValidating, RequestValidated und RequestProcessed treten im Thread der jeweiligen Anfrage auf. Ihre Handler laufen deshalb für mehrere Anfragen gleichzeitig. Schützen Sie Daten, die die Handler gemeinsam nutzen, vor gleichzeitigen Änderungen.

Für die Callbacks Ihrer Anwendung gilt:

  • Lesen und Schreiben: Die Callbacks ReadVariableValueCallback und WriteVariableValueCallback laufen ebenfalls parallel. Der Server sperrt dabei jeweils den einzelnen Node: Zugriffe auf denselben Node laufen nacheinander, Zugriffe auf verschiedene Nodes gleichzeitig. Eine Anfrage über mehrere Nodes ist deshalb nicht atomar. Wenn Werte mehrerer Nodes nur zusammen gültig sind, synchronisieren Sie den Zugriff auf die Datenquelle selbst.
  • Methoden: Für die Callbacks von Methoden-Nodes setzt das SDK keine Sperre. Aufrufe aus verschiedenen Anfragen laufen deshalb gleichzeitig, auch für dieselbe Methode. Nur innerhalb einer Anfrage führt der Server synchrone Methoden nacheinander aus. Mit UseAsyncMethodCalls führt er auch dort jeden Aufruf in einem eigenen Task aus (siehe Asynchrone Methoden). Schreiben Sie die Callbacks threadsicher und schützen Sie Daten, die eine Methode mit anderen Callbacks teilt, selbst.
  • Historische Daten: Einen History Provider ruft der Server ohne Sperre des Node-Managers auf. OpcNodeHistorian und OpcNodeHistory<T> sperren sich selbst. Ein eigener IOpcNodeHistoryProvider muss gleichzeitige Aufrufe selbst absichern (siehe Leseanfragen beantworten).

Die Eigenschaft SyncRoot des Node-Managers verwendet der Server beim Lesen und Schreiben nicht. Eine eigene Sperre auf SyncRoot schützt Callbacks deshalb nicht vor gleichzeitigen Anfragen.

Diagnose ​

Beteiligte TypenOpcServerOpcServerDiagnosticsOpcSessionOpcSessionDiagnosticsOpcServiceCounterOpcSubscriptionOpcSubscriptionDiagnostics

Für Überwachung, Health Checks oder ein Betriebsprotokoll stellt der Server Kennzahlen bereit, die er im Betrieb ohnehin erfasst. Vor dem Start des Servers sind alle Werte 0.

Kennzahlen des Servers ​

Die Eigenschaft Diagnostics des Servers liefert die Zahl der aktuellen und der seit dem Start insgesamt erstellten Sitzungen und Subscriptions. Außerdem liefert sie die Zahl abgelehnter Sitzungen und Anfragen sowie die Zahl abgebrochener und abgelaufener Sitzungen:

csharp
OpcServerDiagnostics diagnostics = server.Diagnostics;
 
Console.WriteLine(
        "Sessions: {0} (total {1})",
        diagnostics.CurrentSessionCount,
        diagnostics.CumulatedSessionCount);
 
Console.WriteLine(
        "Subscriptions: {0}",
        diagnostics.CurrentSubscriptionCount);
 
Console.WriteLine(
        "Rejected sessions: {0}, by security: {1}",
        diagnostics.RejectedSessionCount,
        diagnostics.SecurityRejectedSessionCount);
 
Console.WriteLine(
        "Rejected requests: {0}, by security: {1}",
        diagnostics.RejectedRequestsCount,
        diagnostics.SecurityRejectedRequestsCount);
 
Console.WriteLine(
        "Session timeouts: {0}, aborts: {1}",
        diagnostics.SessionTimeoutCount,
        diagnostics.SessionAbortCount);
vbnet
Dim diagnostics As OpcServerDiagnostics = server.Diagnostics

Console.WriteLine( _
        "Sessions: {0} (total {1})", _
        diagnostics.CurrentSessionCount, _
        diagnostics.CumulatedSessionCount)

Console.WriteLine( _
        "Subscriptions: {0}", _
        diagnostics.CurrentSubscriptionCount)

Console.WriteLine( _
        "Rejected sessions: {0}, by security: {1}", _
        diagnostics.RejectedSessionCount, _
        diagnostics.SecurityRejectedSessionCount)

Console.WriteLine( _
        "Rejected requests: {0}, by security: {1}", _
        diagnostics.RejectedRequestsCount, _
        diagnostics.SecurityRejectedRequestsCount)

Console.WriteLine( _
        "Session timeouts: {0}, aborts: {1}", _
        diagnostics.SessionTimeoutCount, _
        diagnostics.SessionAbortCount)

Wenn die Werte SecurityRejectedSessionCount und SecurityRejectedRequestsCount steigen, lehnt der Server Sitzungen oder Anfragen aus Sicherheitsgründen ab. Ursachen sind zum Beispiel ein nicht vertrauenswürdiges Zertifikat oder eine fehlgeschlagene Anmeldung. Das kann auf eine fehlerhafte Konfiguration eines Clients oder auf einen Angriff hinweisen. Die Schutzmechanismen des Servers beschreibt die Seite Server absichern.

Sitzungen und Subscriptions untersuchen ​

Die Methode GetSessions liefert die bestehenden Sitzungen. Jede Sitzung nennt unter anderem ihren Namen, den verwendeten Endpunkt (EndpointUrl), die angemeldete Identität (UsedIdentity) und den Zeitpunkt des Verbindungsaufbaus. Ihre Eigenschaft Diagnostics zählt je Dienst die Aufrufe und die fehlgeschlagenen Aufrufe (zum Beispiel ReadCounter, WriteCounter, CallCounter und TotalRequestCounter). Außerdem zählt sie unberechtigte Anfragen (UnauthorizedRequestsCount). Die Eigenschaft Diagnostics einer Subscription zählt unter anderem gesendete Benachrichtigungen und Überläufe ihrer Warteschlangen. Beide Eigenschaften können null sein:

csharp
foreach (OpcSession session in server.GetSessions()) {
    Console.WriteLine(
            "{0} {1} {2} since {3:u}",
            session.Name,
            session.EndpointUrl,
            session.UsedIdentity,
            session.ConnectTime);
 
    OpcSessionDiagnostics sessionDiagnostics = session.Diagnostics;
 
    if (sessionDiagnostics != null) {
        Console.WriteLine(
                "  requests: {0}, failed: {1}",
                sessionDiagnostics.TotalRequestCounter.TotalCalls,
                sessionDiagnostics.TotalRequestCounter.FailedCalls);
 
        Console.WriteLine(
                "  unauthorized: {0}, writes: {1}",
                sessionDiagnostics.UnauthorizedRequestsCount,
                sessionDiagnostics.WriteCounter.TotalCalls);
    }
 
    foreach (OpcSubscription subscription in session.GetSubscriptions()) {
        OpcSubscriptionDiagnostics subscriptionDiagnostics = subscription.Diagnostics;
 
        if (subscriptionDiagnostics != null) {
            Console.WriteLine(
                    "  subscription {0}: queue overflows {1}",
                    subscription.Id,
                    subscriptionDiagnostics.MonitoringQueueOverflowCount);
        }
    }
}
vbnet
For Each session As OpcSession In server.GetSessions()
    Console.WriteLine( _
            "{0} {1} {2} since {3:u}", _
            session.Name, _
            session.EndpointUrl, _
            session.UsedIdentity, _
            session.ConnectTime)

    Dim sessionDiagnostics As OpcSessionDiagnostics = session.Diagnostics

    If sessionDiagnostics IsNot Nothing Then
        Console.WriteLine( _
                "  requests: {0}, failed: {1}", _
                sessionDiagnostics.TotalRequestCounter.TotalCalls, _
                sessionDiagnostics.TotalRequestCounter.FailedCalls)

        Console.WriteLine( _
                "  unauthorized: {0}, writes: {1}", _
                sessionDiagnostics.UnauthorizedRequestsCount, _
                sessionDiagnostics.WriteCounter.TotalCalls)
    End If

    For Each subscription As OpcSubscription In session.GetSubscriptions()
        Dim subscriptionDiagnostics As OpcSubscriptionDiagnostics = subscription.Diagnostics

        If subscriptionDiagnostics IsNot Nothing Then
            Console.WriteLine( _
                    "  subscription {0}: queue overflows {1}", _
                    subscription.Id, _
                    subscriptionDiagnostics.MonitoringQueueOverflowCount)
        End If
    Next
Next

Diagnose-Nodes im Adressraum ​

Clients lesen die Diagnose eines Servers über die Diagnose-Nodes in seinem Adressraum, zum Beispiel ServerDiagnosticsSummary und die Diagnose-Objekte je Sitzung und je Subscription. Diese Nodes pflegt der Server nur, wenn die Eigenschaft Diagnostics.IsEnabled den Wert true hat (standardmäßig false). Auf die Kennzahlen und die Eigenschaften Diagnostics der Sitzungen und Subscriptions in den Abschnitten oben wirkt sich die Einstellung nicht aus. Setzen Sie die Eigenschaft vor dem Start des Servers:

csharp
server.Diagnostics.IsEnabled = true;
server.Start();
vbnet
server.Diagnostics.IsEnabled = True
server.Start()