Learn open62541 --- [35] Server asynchronous execution method

This article mainly describes how to execute the method asynchronously (async) on the server side. The basic principles are as follows:

  1. Client side sends method call request
  2. After the server receives the request, it will add the request to the internal queue
  3. Server takes out the request from the queue in another thread, executes the method, and finally returns the result of the method to the Client

This article uses the open62541 v1.1.1 version, the operating environment is win10, and the ubuntu operation is the same.


One configuration open62541.h

I use cmake-gui for configuration,
Insert picture description here
check UA_ENABLE_AMALGAMATION, set UA_MULTITHREADING to 100, and use the default configuration for others. Then compile, please refer to this article for the specific process .


Two operation process

The code comes from tutorial_server_method_async.c in the example directory, which is simplified.

First, you need to define a method on the Server side, which receives an input parameter of type String and returns a value of type String.

Here is the callback of this method,

static UA_StatusCode helloWorldMethodCallback(UA_Server *server,
	                const UA_NodeId *sessionId, void *sessionHandle,
					const UA_NodeId *methodId, void *methodContext,
					const UA_NodeId *objectId, void *objectContext,
					size_t inputSize, const UA_Variant *input,
					size_t outputSize, UA_Variant *output) 
{
    
    
	// 连接字符串,变成"Hello " + 输入参数
	UA_String *inputStr = (UA_String*)input->data;
	UA_String tmp = UA_STRING_ALLOC("Hello ");
	if (inputStr->length > 0) 
	{
    
    
		tmp.data = (UA_Byte *)UA_realloc(tmp.data, tmp.length + inputStr->length);
		memcpy(&tmp.data[tmp.length], inputStr->data, inputStr->length);
		tmp.length += inputStr->length;
	}

    // 把连接好的字符串拷贝到输出参数里
	UA_Variant_setScalarCopy(output, &tmp, &UA_TYPES[UA_TYPES_STRING]);

	// 添加字符串结束符'\0',其值为0,所以calloc自动就会添加
	char* test = (char*)calloc(1, tmp.length + 1);
	memcpy(test, tmp.data, tmp.length);
	UA_String_clear(&tmp);

	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "'Hello World (async)' was called");
	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "    Data: %s", test);
	free(test);
	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "'Hello World (async)' has ended");
	
	return UA_STATUSCODE_GOOD;
}

Then add the method on the Server side, the method has an input parameter and an output parameter. The most important thing is the last line of statements, which will set the method to asynchronous

static void addHellWorldMethod(UA_Server *server) 
{
    
    
	// 定义输入参数
	UA_Argument inputArgument;
	UA_Argument_init(&inputArgument);
	inputArgument.description = UA_LOCALIZEDTEXT("en-US", "A String");
	inputArgument.name = UA_STRING("MyInput");
	inputArgument.dataType = UA_TYPES[UA_TYPES_STRING].typeId;
	inputArgument.valueRank = UA_VALUERANK_SCALAR;

    // 定义输出参数
	UA_Argument outputArgument;
	UA_Argument_init(&outputArgument);
	outputArgument.description = UA_LOCALIZEDTEXT("en-US", "A String");
	outputArgument.name = UA_STRING("MyOutput");
	outputArgument.dataType = UA_TYPES[UA_TYPES_STRING].typeId;
	outputArgument.valueRank = UA_VALUERANK_SCALAR;

    // 添加方法
	UA_MethodAttributes helloAttr = UA_MethodAttributes_default;
	helloAttr.description = UA_LOCALIZEDTEXT("en-US", "Say `Hello World` async");
	helloAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Hello World async");
	helloAttr.executable = true;
	helloAttr.userExecutable = true;
	UA_NodeId id = UA_NODEID_NUMERIC(1, 62541);
	UA_Server_addMethodNode(server, id,
		UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
		UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
		UA_QUALIFIEDNAME(1, "hello world"),
		helloAttr, &helloWorldMethodCallback,
		1, &inputArgument, 1, &outputArgument, NULL, NULL);
	
	
	// 把方法设置成async
	UA_Server_setMethodNodeAsync(server, id, UA_TRUE);
}

It also needs a thread to take out the method call request from the queue and then execute it. There are mainly four APIs,

  • Get request: UA_Server_getAsyncOperationNonBlocking()
  • Execution request: UA_Server_call()
  • Return execution result: UA_Server_setAsyncOperationResult()
  • Ending: UA_CallMethodResult_clear()

Note that globalServer is a global variable,

THREAD_CALLBACK(ThreadWorker) 
{
    
    
	while (running) 
	{
    
    
		const UA_AsyncOperationRequest* request = NULL;
		void *context = NULL;
		UA_AsyncOperationType type;
		if (UA_Server_getAsyncOperationNonBlocking(globalServer, &type, &request, &context, NULL) == true) 
		{
    
    
			UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Dequeue an async operation OK");

			UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "AsyncMethod_Testing: Got entry: OKAY");
			UA_CallMethodResult response = UA_Server_call(globalServer, &request->callMethodRequest);
			UA_Server_setAsyncOperationResult(globalServer, (UA_AsyncOperationResponse*)&response, context);
			UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "AsyncMethod_Testing: Call done: OKAY");
			UA_CallMethodResult_clear(&response);
		}
	}

	return 0;
}

The main function is as follows,

/* This callback will be called when a new entry is added to the Callrequest queue */
static void TestCallback(UA_Server *server) 
{
    
    
	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Dispatched an async method");
}

int main(void) 
{
    
    
	signal(SIGINT, stopHandler);
	signal(SIGTERM, stopHandler);

	globalServer = UA_Server_new();
	UA_ServerConfig *config = UA_Server_getConfig(globalServer);
	UA_ServerConfig_setDefault(config);

	/* 设置异步操作的通知回调 */
	config->asyncOperationNotifyCallback = TestCallback;

	/* 启动 Worker-Thread */
	THREAD_HANDLE hThread;
	THREAD_CREATE(hThread, ThreadWorker);

	/* 添加方法 */
	addHellWorldMethod(globalServer);

	UA_StatusCode retval = UA_Server_run(globalServer, &running);

	/* Shutdown the thread */
	THREAD_JOIN(hThread);

	UA_Server_delete(globalServer);
	return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}

Three overall code and operation

/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
* See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */

/**
* Adding Async Methods to Objects
* -------------------------
*
* An object in an OPC UA information model may contain methods similar to
* objects in a programming language. Methods are represented by a MethodNode.
* Note that several objects may reference the same MethodNode. When an object
* type is instantiated, a reference to the method is added instead of copying
* the MethodNode. Therefore, the identifier of the context object is always
* explicitly stated when a method is called.
*
* The method callback takes as input a custom data pointer attached to the
* method node, the identifier of the object from which the method is called,
* and two arrays for the input and output arguments. The input and output
* arguments are all of type :ref:`variant`. Each variant may in turn contain a
* (multi-dimensional) array or scalar of any data type.
*
* Constraints for the method arguments are defined in terms of data type, value
* rank and array dimension (similar to variable definitions). The argument
* definitions are stored in child VariableNodes of the MethodNode with the
* respective BrowseNames ``(0, "InputArguments")`` and ``(0,
* "OutputArguments")``.
*
* Example: Hello World Method
* ^^^^^^^^^^^^^^^^^^^^^^^^^^^
* The method takes a string scalar and returns a string scalar with "Hello "
* prepended. The type and length of the input arguments is checked internally
* by the SDK, so that we don't have to verify the arguments in the callback. */

#include "open62541.h"

#include <signal.h>
#include <stdlib.h>

#ifndef WIN32
#include <pthread.h>
#define THREAD_HANDLE pthread_t
#define THREAD_CREATE(handle, callback) pthread_create(&handle, NULL, callback, NULL)
#define THREAD_JOIN(handle) pthread_join(handle, NULL)
#define THREAD_CALLBACK(name) static void * name(void *_)
#else
#include <windows.h>
#define THREAD_HANDLE HANDLE
#define THREAD_CREATE(handle, callback) { handle = CreateThread( NULL, 0, callback, NULL, 0, NULL); }
#define THREAD_JOIN(handle) WaitForSingleObject(handle, INFINITE)
#define THREAD_CALLBACK(name) static DWORD WINAPI name( LPVOID lpParam )
#endif

static UA_Server* globalServer;
static volatile UA_Boolean running = true;

static void stopHandler(int sign) 
{
    
    
	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
	running = false;
}

static UA_StatusCode helloWorldMethodCallback(UA_Server *server,
	                const UA_NodeId *sessionId, void *sessionHandle,
					const UA_NodeId *methodId, void *methodContext,
					const UA_NodeId *objectId, void *objectContext,
					size_t inputSize, const UA_Variant *input,
					size_t outputSize, UA_Variant *output) 
{
    
    
	// 连接字符串,变成"Hello " + 输入参数
	UA_String *inputStr = (UA_String*)input->data;
	UA_String tmp = UA_STRING_ALLOC("Hello ");
	if (inputStr->length > 0) 
	{
    
    
		tmp.data = (UA_Byte *)UA_realloc(tmp.data, tmp.length + inputStr->length);
		memcpy(&tmp.data[tmp.length], inputStr->data, inputStr->length);
		tmp.length += inputStr->length;
	}

    // 把连接好的字符串拷贝到输出参数里
	UA_Variant_setScalarCopy(output, &tmp, &UA_TYPES[UA_TYPES_STRING]);

	// 添加字符串结束符'\0',其值为0,所以calloc自动就会添加
	char* test = (char*)calloc(1, tmp.length + 1);
	memcpy(test, tmp.data, tmp.length);
	UA_String_clear(&tmp);

	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "'Hello World (async)' was called");
	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "    Data: %s", test);
	free(test);
	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "'Hello World (async)' has ended");
	
	return UA_STATUSCODE_GOOD;
}

static void addHellWorldMethod(UA_Server *server) 
{
    
    
	// 定义输入参数
	UA_Argument inputArgument;
	UA_Argument_init(&inputArgument);
	inputArgument.description = UA_LOCALIZEDTEXT("en-US", "A String");
	inputArgument.name = UA_STRING("MyInput");
	inputArgument.dataType = UA_TYPES[UA_TYPES_STRING].typeId;
	inputArgument.valueRank = UA_VALUERANK_SCALAR;

    // 定义输出参数
	UA_Argument outputArgument;
	UA_Argument_init(&outputArgument);
	outputArgument.description = UA_LOCALIZEDTEXT("en-US", "A String");
	outputArgument.name = UA_STRING("MyOutput");
	outputArgument.dataType = UA_TYPES[UA_TYPES_STRING].typeId;
	outputArgument.valueRank = UA_VALUERANK_SCALAR;

    // 添加方法
	UA_MethodAttributes helloAttr = UA_MethodAttributes_default;
	helloAttr.description = UA_LOCALIZEDTEXT("en-US", "Say `Hello World` async");
	helloAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Hello World async");
	helloAttr.executable = true;
	helloAttr.userExecutable = true;
	UA_NodeId id = UA_NODEID_NUMERIC(1, 62541);
	UA_Server_addMethodNode(server, id,
		UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
		UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
		UA_QUALIFIEDNAME(1, "hello world"),
		helloAttr, &helloWorldMethodCallback,
		1, &inputArgument, 1, &outputArgument, NULL, NULL);
	
	
	// 把方法设置成async
	UA_Server_setMethodNodeAsync(server, id, UA_TRUE);
}

THREAD_CALLBACK(ThreadWorker) 
{
    
    
	while (running) 
	{
    
    
		const UA_AsyncOperationRequest* request = NULL;
		void *context = NULL;
		UA_AsyncOperationType type;
		if (UA_Server_getAsyncOperationNonBlocking(globalServer, &type, &request, &context, NULL) == true) 
		{
    
    
			UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Dequeue an async operation OK");

			UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "AsyncMethod_Testing: Got entry: OKAY");
			UA_CallMethodResult response = UA_Server_call(globalServer, &request->callMethodRequest);
			UA_Server_setAsyncOperationResult(globalServer, (UA_AsyncOperationResponse*)&response, context);
			UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "AsyncMethod_Testing: Call done: OKAY");
			UA_CallMethodResult_clear(&response);
		}
	}

	return 0;
}

/* This callback will be called when a new entry is added to the Callrequest queue */
static void TestCallback(UA_Server *server) 
{
    
    
	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Dispatched an async method");
}

int main(void) 
{
    
    
	signal(SIGINT, stopHandler);
	signal(SIGTERM, stopHandler);

	globalServer = UA_Server_new();
	UA_ServerConfig *config = UA_Server_getConfig(globalServer);
	UA_ServerConfig_setDefault(config);

	/* 设置异步操作的通知回调 */
	config->asyncOperationNotifyCallback = TestCallback;

	/* 启动 Worker-Thread */
	THREAD_HANDLE hThread;
	THREAD_CREATE(hThread, ThreadWorker);

	/* 添加方法 */
	addHellWorldMethod(globalServer);

	UA_StatusCode retval = UA_Server_run(globalServer, &running);

	/* Shutdown the thread */
	THREAD_JOIN(hThread);

	UA_Server_delete(globalServer);
	return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}

I use Visual Studio 2015 to compile and connect (the code is cross-platform), run the server after ok, and then use UaExpert to connect,
Insert picture description here
right-click the method and click Call
Insert picture description here
, and enter a string in the input parameters in the pop-up run box. Then click Call in the lower right corner, and
Insert picture description here
finally you can see the result of the operation.
Insert picture description here
Similarly, you can see the corresponding print on the server side.
Insert picture description here


Four summary

This article mainly describes how to implement asynchronous execution of server-side method calls. In this way, the Server can accept other method call requests before the execution of the previous method ends.

If there is something wrong with the writing, I hope to leave a message to correct it, thank you for reading.

Guess you like

Origin blog.csdn.net/whahu1989/article/details/108021807