Lecture/écriture/appel asynchrones dans open62541
Une fonctionnalité à découvrir en cinq minutes

Exemple de lecture asynchrone
Un serveur OPC UA représente souvent un équipement physique, dont les réponses peuvent être lentes. La lecture d’un capteur de température peut par exemple prendre un certain temps. L’attente du résultat ne doit toutefois pas bloquer le flux de contrôle de tout le serveur OPC UA. C’est pourquoi nous avons considérablement amélioré la prise en charge des opérations asynchrones dans open62541 v1.5. Une méthode de callback propre à l’application est associée au nœud Variable du capteur de température dans le modèle d’information. Son interface est présentée ci-dessous.
/* 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);Dans la méthode de callback, l’implémentation peut commencer par vérifier si une mesure récente est disponible. Si c’est le cas, elle renvoie cette valeur. Sinon, elle lance une nouvelle mesure plus longue, par exemple dans un thread dédié ou en enregistrant un minuteur pour traiter le résultat ultérieurement. Le callback rend ensuite le contrôle au serveur. Le code de retour UA_STATUSCODE_GOODCOMPLETESASYNCHRONOUSLY indique que les résultats de lecture seront disponibles plus tard. Le serveur conserve la réponse en attente jusqu’à la fin de toutes les opérations de la requête de lecture. Il peut ainsi continuer à traiter d’autres requêtes sans blocage.
/* 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);L’implémentation de la méthode de callback qui a signalé le statut asynchrone doit finalement terminer l’opération au moyen de UA_Server_setAsyncReadResult. Elle utilise pour cela le même pointeur vers UA_DataValue que celui transmis au callback de lecture. Ce pointeur de résultat reste valide pendant toute la durée de l’opération asynchrone.
Annulation des opérations asynchrones
Un serveur peut non seulement attendre indéfiniment le résultat, mais aussi annuler une opération asynchrone. L’annulation peut provenir du client demandeur, c’est-à-dire de sa Session, ou du serveur lui-même, par exemple lors de son arrêt ou après un délai d’expiration. L’application en est informée par une méthode de callback définie dans la configuration du serveur. Dans l’extrait d’API ci-dessous, l’argument « out » est à nouveau le pointeur de résultat de l’opération de lecture. Le même mécanisme d’annulation est utilisé pour les opérations d’écriture et d’appel. Après la notification, le pointeur n’est plus valide et l’opération asynchrone ne doit plus y accéder.
/* 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);Les écritures asynchrones et les appels de méthodes suivent le même principe de callback. Un exemple complet est disponible dans tutorial_server_method_async.c.
Vous disposez maintenant des informations essentielles pour mettre en œuvre des opérations asynchrones dans votre propre application OPC UA basée sur open62541. La suite présente les détails d’implémentation.
Détails d’implémentation
L’implémentation se trouve principalement dans src/server/ua_server_async.h et src/server/ua_server_async.c. Les implémentations de Service_Read, Service_Write et Service_Call résident dans ces fichiers asynchrones. Elles appellent ensuite Operation_Read et les fonctions correspondantes, situées dans le fichier source de leur Service Set. Les services ont beaucoup de points communs : ils décomposent une requête en un tableau d’opérations de lecture, d’écriture ou d’appel, dont chacune peut produire un résultat asynchrone que le serveur doit attendre. Le « pointeur de résultat » doit rester stable dans ce cas. Comme ces opérations sont très fréquentes, la logique asynchrone ne doit toutefois créer aucun surcoût lorsque leur exécution est synchrone. Le mécanisme est le suivant :
La structure UA_ReadResponse contient un tableau de UA_DataValue, avec une entrée par opération de lecture. La souplesse de la gestion mémoire en C nous permet simplement d’allouer un tableau plus grand. Celui-ci contient les UA_DataValue, immédiatement suivies d’une UA_AsyncOperation pour chaque opération. Lorsque toutes les opérations sont synchrones, un peu plus de mémoire est réservé, mais le surcoût reste négligeable. Cette mémoire supplémentaire est libérée automatiquement, car free() ne dépend pas de la taille de l’allocation. Si au moins une opération est asynchrone, la UA_AsyncOperation correspondante est renseignée et ajoutée à une liste chaînée interne. Le serveur est alors invité à ne pas envoyer immédiatement la UA_ReadResponse au client, mais à attendre la fin de toutes les opérations asynchrones.
Lorsque UA_Server_setAsyncReadResult est appelée, l’implémentation parcourt la liste chaînée interne jusqu’à trouver l’entrée correspondant au « pointeur de résultat ». Le serveur identifie ainsi la requête concernée et sait si les autres opérations sont terminées. La réponse n’est toutefois jamais envoyée immédiatement. Un callback différé est enregistré pour être exécuté à l’itération suivante de l’EventLoop. UA_Server_setAsyncReadResult() peut donc être appelée depuis un thread de travail sans que celui-ci soit bloqué par l’envoi d’une réponse volumineuse, éventuellement chiffrée. Toutes les communications restent prises en charge par le thread principal de l’EventLoop.
Consultez également les paramètres de configuration du serveur asyncOperationTimeout et maxAsyncOperationQueueSize. Ils sont particulièrement utiles lorsque les ressources sont limitées ou lorsque le serveur doit nettoyer automatiquement une opération asynchrone qui ne renvoie aucun résultat.