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:
- Empfang der Rohdaten einer Anfrage (Protokollebene des SDK)
- Deserialisierung der Rohdaten zu einer Anfrage (Nachrichtenebene des SDK)
- Vorverarbeitung der Anfrage: Ereignis
RequestProcessing - Zuteilung der Anfrage zum zuständigen Dienst (Dienstebene des SDK)
- Validierung der Anfrage
- Standardvalidierungen (Sitzung, Identität usw.)
- benutzerdefinierte Validierung: Ereignis
RequestValidating - abschließende Validierung (Prüfung der benutzerdefinierten Validierung)
- benutzerdefinierter Abschluss der Validierung: Ereignis
RequestValidated
- Verarbeitung der Anfrage (Anwendungsebene des SDK)
- Erzeugen der Antwort über den zuständigen Dienst (Dienstebene des SDK)
- Nachverarbeitung der Anfrage und ihrer Antwort: Ereignis
RequestProcessed - Serialisierung der Antwort zu Rohdaten (Nachrichtenebene des SDK)
- 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:
private static void HandleRequestProcessing(object sender, OpcRequestProcessingEventArgs e)
{
Console.Write("Processing: " + e.Request.ToString());
}
// ...
server.RequestProcessing += HandleRequestProcessing;Private Shared Sub HandleRequestProcessing(sender As Object, e As OpcRequestProcessingEventArgs)
Console.Write("Processing: " & e.Request.ToString())
End Sub
' ...
AddHandler server.RequestProcessing, AddressOf HandleRequestProcessingAnfragen 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:
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;
}
}
}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 SubDas 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:
void HandleRequestValidated(object sender, OpcRequestValidatedEventArgs e)
{
Console.Write(" -> Validated");
}
// ...
server.RequestValidated += HandleRequestValidated;Sub HandleRequestValidated(sender As Object, e As OpcRequestValidatedEventArgs)
Console.Write(" -> Validated")
End Sub
' ...
AddHandler server.RequestValidated, AddressOf HandleRequestValidatedWie 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:
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;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 HandleRequestProcessedWie 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:
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;
}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 SubSitzungen ü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:
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);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
ReadVariableValueCallbackundWriteVariableValueCallbacklaufen 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
UseAsyncMethodCallsfü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.
OpcNodeHistorianundOpcNodeHistory<T>sperren sich selbst. Ein eigenerIOpcNodeHistoryProvidermuss 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:
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);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:
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);
}
}
}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
NextDiagnose-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:
server.Diagnostics.IsEnabled = true;
server.Start();server.Diagnostics.IsEnabled = True
server.Start()
