Asynchrones Lesen/Schreiben/Aufrufen in open62541
Fünf-Minuten-Feature-Highlight

Beispiel für asynchrones Lesen
Ein OPC-UA-Server repräsentiert häufig ein physisches Gerät – und solche Geräte können langsam sein. Beim Auslesen eines Temperatursensors kann es beispielsweise dauern, bis die Messung abgeschlossen ist. Das Warten auf das Ergebnis darf jedoch nicht den Kontrollfluss des gesamten OPC-UA-Servers blockieren. Deshalb haben wir die Unterstützung asynchroner Operationen in open62541 v1.5 erheblich verbessert. Eine anwendungsspezifische Callback-Methode wird dem Variablenknoten des Temperatursensors im Informationsmodell zugeordnet. Die Schnittstelle dieser Callback-Methode ist unten dargestellt.
/* Callback for reading the temperature sensor
*
* @param server The server executing the callback.
* @param sessionId The identifier of the session.
* @param sessionContext Additional data attached to the session in the
* access control plugin.
* @param nodeId The identifier of the node which is read.
* @param nodeContext Additional data attached to the node by the application.
* @param includeSourceTimeStamp If true, then the datasource is expected to
* set the source timestamp in the returned value.
* @param range If not null, then the datasource shall return only a
* selection of the (nonscalar) data.
* @param value The (non-null) result that is returned to the client. The
* data source can set the value, the status and optionally a
* source-timestamp.
* @return Returns a status code for the success of the operation. Can be
* UA_STATUSCODE_GOODCOMPLETESASYNCHRONOUSLY to complete later. */
UA_StatusCode
readTemp(UA_Server *server, const UA_NodeId *sessionId,
void *sessionContext, const UA_NodeId *nodeId,
void *nodeContext, UA_Boolean includeSourceTimeStamp,
const UA_NumericRange *range, UA_DataValue *value);Innerhalb der Callback-Methode kann die Implementierung zunächst prüfen, ob ein aktueller Temperaturwert verfügbar ist. Wenn ja, wird dieser zurückgegeben. Andernfalls startet eine neue, zeitaufwendige Messung – beispielsweise in einem eigenen Thread oder über einen Timer zur späteren Verarbeitung des Ergebnisses. Anschließend gibt der Callback den Kontrollfluss an den Server zurück. Der Rückgabecode UA_STATUSCODE_GOODCOMPLETESASYNCHRONOUSLY signalisiert, dass die Leseergebnisse erst später verfügbar sein werden. Der Server hält die Antwort so lange zurück, bis alle Operationen der Leseanfrage abgeschlossen sind. Währenddessen kann er weitere Anfragen verarbeiten, ohne zu blockieren.
/* Asynchronously set read results when they become available.
* The result pointer must be reused from the read callback. */
UA_StatusCode
UA_Server_setAsyncReadResult(UA_Server *server, UA_DataValue *result);Die Implementierung der Callback-Methode, die den asynchronen Status signalisiert hat, muss die Operation schließlich über UA_Server_setAsyncReadResult abschließen. Dabei wird derselbe Zeiger auf UA_DataValue verwendet, der zuvor an den Lese-Callback übergeben wurde. Dieser Ergebniszeiger bleibt während der gesamten asynchronen Operation gültig.
Abbruch asynchroner Operationen
Ein Server kann eine asynchrone Operation nicht nur unbegrenzt abwarten, sondern auch abbrechen. Der Abbruch kann vom anfragenden Client beziehungsweise dessen Session oder vom Server selbst ausgehen, etwa beim Herunterfahren oder nach einem Timeout. Die Anwendung wird über eine Callback-Methode in der Serverkonfiguration informiert. Im folgenden API-Ausschnitt ist das Argument „out“ erneut der Ergebniszeiger der Leseoperation. Derselbe Benachrichtigungsmechanismus wird auch für Schreib- und Aufrufoperationen verwendet. Nach der Abbruchbenachrichtigung ist der Ergebniszeiger nicht mehr gültig und darf von der asynchronen Operation nicht mehr verwendet werden.
/* Cancel callback method in the server configuration.
*
* Notifies the application that an async operation has been canceled. The
* memory for setting the output value is then freed internally and should
* not be touched afterwards. */
void (*asyncOperationCancelCallback)(UA_Server *server, const void *out);Für asynchrone Schreiboperationen und Methodenaufrufe wird derselbe grundlegende Callback-Ansatz verwendet. Ein vollständiges Beispiel finden Sie in tutorial_server_method_async.c.
Damit verfügen Sie über alle grundlegenden Informationen, um asynchrone Operationen in Ihrer eigenen, mit open62541 entwickelten OPC-UA-Anwendung zu implementieren. Im Folgenden erläutern wir die Implementierungsdetails.
Implementierungsdetails
Die Implementierung befindet sich hauptsächlich in src/server/ua_server_async.h und src/server/ua_server_async.c. Die Implementierungen von Service_Read, Service_Write und Service_Call liegen in diesen Async-Dateien. Von dort werden Operation_Read und die entsprechenden Funktionen aufgerufen, die sich jeweils in der Quelldatei ihres Service-Sets befinden. Die Service-Implementierungen haben viel gemeinsam: Sie zerlegen eine Anfrage in ein Array von Lese-, Schreib- oder Aufrufoperationen, von denen jede unerwartet ein asynchrones Ergebnis liefern kann, auf das der Server warten muss. Der „Ergebniszeiger“ muss bei einer asynchronen Operation stabil bleiben. Da diese Operationen sehr häufig vorkommen, soll die Async-Logik im synchronen Fall zugleich keinen Overhead verursachen. Dies wird wie folgt erreicht:
Die Datenstruktur UA_ReadResponse enthält ein Array aus UA_DataValue – einen Eintrag für jede Leseoperation. Da C einen flexiblen Umgang mit der Speicherverwaltung erlaubt, reservieren wir einfach ein größeres Array. Es enthält nicht nur den Speicher für die UA_DataValue-Einträge, sondern direkt anschließend auch je eine UA_AsyncOperation pro Operation. Sind alle Operationen synchron, wird zwar etwas mehr Speicher reserviert, der zusätzliche Aufwand ist jedoch vernachlässigbar. Auch die Freigabe erfolgt automatisch, da free() nicht von der Größe des reservierten Speichers abhängt. Sobald mindestens eine Operation asynchron ist, wird die zugehörige UA_AsyncOperation ausgefüllt und einer internen verketteten Liste hinzugefügt. Der Server sendet die UA_ReadResponse dann nicht sofort an den Client, sondern wartet, bis alle asynchronen Operationen abgeschlossen sind.
Beim Aufruf von UA_Server_setAsyncReadResult durchsucht die Implementierung die interne verkettete Liste nach dem Eintrag mit dem passenden „Ergebniszeiger“. Dadurch kennt der Server sowohl die zugehörige Anfrage als auch den Status der übrigen Operationen. Die Antwort wird jedoch nie unmittelbar versendet. Stattdessen registriert der Server einen verzögerten Callback, der in der nächsten Iteration des EventLoops ausgeführt wird. So kann UA_Server_setAsyncReadResult() aus einem Worker-Thread aufgerufen werden, ohne dass dieser beim Versand einer aufwendigen, möglicherweise verschlüsselten Antwort blockiert. Die Kommunikation bleibt vollständig im Hauptthread des EventLoops.
Beachten Sie außerdem die Serverkonfigurationsparameter asyncOperationTimeout und maxAsyncOperationQueueSize. Sie sind besonders hilfreich bei begrenzten Ressourcen oder wenn der Server asynchrone Operationen automatisch bereinigen soll, die kein Ergebnis zurückgeben.