目录
一、OPC UA协议简介
二、open62541库简介
三、 opcua协议的多点查询、多点读写案例服务端opcua_server
3.1 opcua_server工程目录
3.2 程序源码
3.3 工程组织文件
3.4 编译及启动
四、opcua协议的多点查询、多点读写案例客户端opcua_client
4.1 opcua_client工程目录
4.2 程序源码
4.3 工程组织文件
4.4 编译及测试
一、OPC UA协议简介
OPC UA(Open Platform Communications Unified Architecture)是一个通用的、安全的、跨平台的通信协议,用于在工业互联网、物联网(IoT)环境中传递数据和信息。它基于工业领域通用的OPC(Open Platform Communications)协议,并针对现代设备互联的需要进行了扩展和优化。
OPC UA协议支持多种网络协议,包括TCP/IP、UDP、WebSockets等。该协议提供了一种标准化的、可扩展的通讯机制,用于设备之间的通信和数据交换,广泛应用于工业自动化、智能制造等领域。
OPC UA协议是一种功能强大、安全可靠、可扩展性好的通用通信协议,被广泛应用于工业自动化、物联网设备接入等领域,OPC UA协议具有以下特点:
1. 安全性高:支持多种加密算法,可以通过数字证书进行身份验证、权限管理等。
2. 跨平台:支持不同操作系统上的应用程序和设备之间的通信。
3. 扩展性强:支持自定义协议数据单元类型和协议对象。
4. 数据模型灵活:可以适应多种应用场景,例如数据传输、事件通知、访问控制等。
5. 可以承载多种应用协议:比如物联网组网协议、智能制造协议等。
二、open62541库简介
open62541是一款基于C语言实现的OPC UA通信库。该库实现了OPC UA标准的客户端和服务器端,并支持各种操作系统和编译器。该OPC UA通信库功能丰富、易于移植、可扩展性好、性能优越,被广泛应用于工业自动化、智能制造、物联网等领域,是开发OPC UA应用的一个重要选择,其特点如下:
1. 开源免费:使用MIT许可协议,可用于商业和非商业项目。
2. 高度可扩展:支持插件式开发,可以方便地增加协议扩展、认证机制、加密协议等功能。
3. 易于移植:实现了跨平台的特性,可以在不同操作系统和硬件平台上使用。
4. 高性能:采用了基于事件的架构和异步I/O机制,具有非常好的性能和伸缩性。
5. 支持多线程:使用线程安全的方法进行函数调用,可以在多线程环境下稳定工作。
6. 可嵌入性强:支持将库文件嵌入到应用程序中,方便整合使用。
关于open62541库的源码编译、安装、环境部署等请参考本专栏的博文:
OPC UA/DA协议库open62541的源码编译及案例测试_open62541源码_py_free-物联智能的博客-CSDN博客
三、 opcua协议的多点查询、多点读写案例服务端opcua_server
3.1 opcua_server工程目录
创建opcua_server工程,该opc服务添加一些节点,并为这些节点设置初始值,然后等待opc客户端链接及操作。
工程目录如下:
opcua_server
bin
build_linux
build_mingw
src
main.c
CMakeLists.txt
3.2 程序源码
main.c文件内容如下:
#include <signal.h>
#ifdef __linux__
#include <open62541/server.h>
#include <open62541/server_config_default.h>
#else
#include "open62541.h"
#endif
// 用于记录SENSOR_1节点值的变量
UA_Int32 sensor1Value = 0;
UA_StatusCode add_sensor_variable(UA_Server *server,char* nodeid_desc,int val) {
/* Add a variable node */
/* 1) Define the node attributes */
UA_VariableAttributes attr = UA_VariableAttributes_default;
attr.displayName = UA_LOCALIZEDTEXT("en-US", nodeid_desc); //属性展示信息
attr.description = UA_LOCALIZEDTEXT("en-US", nodeid_desc); //属性描述信息
// attr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId;
attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
UA_Int32 myInteger = val;
UA_Variant_setScalar(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_INT32]); //设置属性值
/* 2) Define where the node shall be added with which browsename */
UA_NodeId newNodeId = UA_NODEID_STRING(1, nodeid_desc); //节点=命名空间+节点名称,客户端端标识类型是UA_NODEIDTYPE_STRING
UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER); //父节点,客户端端标识类型是UA_NODEIDTYPE_NUMERIC
UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES); //关联节点
UA_NodeId variableType = UA_NODEID_NULL; /* take the default variable type *///变量类型
UA_QualifiedName browseName = UA_QUALIFIEDNAME(1, nodeid_desc); //节点浏览名
/* 3) Add the node */
return UA_Server_addVariableNode(server, newNodeId, parentNodeId, parentReferenceNodeId,
browseName, variableType, attr, NULL, NULL);
}
UA_Boolean running = true;
void signalHandler(int sig) {
running = false;
}
int main() {
signal(SIGINT, signalHandler); /* catch ctrl-c */
UA_Server *server = UA_Server_new();
UA_ServerConfig_setDefault(UA_Server_getConfig(server));
// 添加节点
add_sensor_variable(server,"SENSOR_1",41);
add_sensor_variable(server,"SENSOR_2",42);
// 启动服务端
UA_StatusCode retval = UA_Server_run(server, &running);
UA_Server_delete(server);
return (int)retval;
}
上述代码说明:
1)头文件的引用win和linux情况不同:在win下无论是采用mingw-gcc还是采用Visual Studio编译工具,都会先生成open62541.h/open62541.c文件,然后在编译成库,因此open62541.h相当于一个总集API接口头文件,调用该文件及相当于调用了一系列头文件;在linux下gcc编译源码时,是不会生成中间文件的,而是直接一步到位生成库,头文件依然采用源码的头文件,因此需要单独引用涉及的头文件。
2)注意到添加节点时,节点编号是有命名空间+节点名称组成的,需要指定节点的id和父节点ID,这些信息是要告知客户端的,这样客户端才能访问
UA_NodeId newNodeId = UA_NODEID_STRING(1, nodeid_desc); //节点=命名空间+节点名称,客户端端标识类型是UA_NODEIDTYPE_STRING
UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER); //父节点,客户端端标识类型是UA_NODEIDTYPE_NUMERIC
3)节点的数值变量可以指定名称、描述、访问权限、数值类型等信息,如果客户端需要写入,需要指定UA_ACCESSLEVELMASK_WRITE权限的,如果客户端需要针对数值类型做判断也是需要知道其类型的,如UA_TYPES_INT32。
attr.displayName = UA_LOCALIZEDTEXT("en-US", nodeid_desc); //属性展示信息
attr.description = UA_LOCALIZEDTEXT("en-US", nodeid_desc); //属性描述信息
// attr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId;
attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
UA_Int32 myInteger = val;
UA_Variant_setScalar(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_INT32]); //设置属性值
在本程序中,节点的命名、描述、浏览 字段都指定了一样的字符串,实际项目中,这些是可以不一样的。
4)程序设置了服务端的默认配置,其默认侦听端口是4840:
UA_Server *server = UA_Server_new();
UA_ServerConfig_setDefault(UA_Server_getConfig(server));
3.3 工程组织文件
CMakeLists.txt工程文件如下,
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (opcua_server)
#
if(WIN32)
message(STATUS "windows compiling...")
add_definitions(-D_PLATFORM_IS_WINDOWS_)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd")
set(WIN_OS true)
else(WIN32)
message(STATUS "linux compiling...")
add_definitions( -D_PLATFORM_IS_LINUX_)
add_definitions("-Wno-invalid-source-encoding")
# add_definitions("-O2")
set(UNIX_OS true)
set(_DEBUG true)
endif(WIN32)
set(UA_IPV6 0)
set(org_dir ${PROJECT_SOURCE_DIR}/../..)
#set(build_dir ${PROJECT_SOURCE_DIR}/../../build_mingw)
#set(build_dir ${PROJECT_SOURCE_DIR}/../../build_vc)
set(build_dir ${PROJECT_SOURCE_DIR}/../../build_linux)
#
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
# 指定源文件的目录,并将名称保存到变量
SET(source_h
#
#${build_dir}/open62541.h
)
#头文件目录
include_directories(
${build_dir}
${org_dir}/include
${build_dir}/src_generated
${org_dir}/arch
${org_dir}/deps
${org_dir}/plugins/include
)
if (${UNIX_OS})
SET(source_cpp
#
${PROJECT_SOURCE_DIR}/src/main.c
)
add_definitions(
"-DUA_ARCHITECTURE_POSIX"
"-std=c99"
)
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -O0")
link_directories()
# 指定生成目标
add_executable(opcua_server ${source_h} ${source_cpp} )
#link
target_link_libraries(opcua_server
-pthread
${build_dir}/bin/libopen62541.a
)
endif(${UNIX_OS})
if (${WIN_OS})
SET(source_cpp
#
${PROJECT_SOURCE_DIR}/src/main.c
${build_dir}/open62541.c
)
add_definitions(
"-DUA_ARCHITECTURE_WIN32"
)
link_directories(
"${build_dir}/bin"
)
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${PROJECT_SOURCE_DIR}/bin)
# 指定生成目标
add_executable(opcua_serverd ${source_h} ${source_cpp} )
#link
target_link_libraries(opcua_serverd
ws2_32.lib wsock32.lib Iphlpapi.lib
${build_dir}/bin/libopen62541.a #mingw
#${build_dir}/bin/Debug/open62541d.lib #vc
)
else(CMAKE_BUILD_TYPE)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${PROJECT_SOURCE_DIR}/bin)
# 指定生成目标
add_executable(opcua_server ${source_h} ${source_cpp})
#link
target_link_libraries(opcua_server
ws2_32.lib wsock32.lib Iphlpapi.lib
${build_dir}/bin/libopen62541.a #mingw
#${build_dir}/bin/Release/open62541.lib #vc
)
endif (CMAKE_BUILD_TYPE)
endif(${WIN_OS})
工程文件配置了vs mingw-gcc linux-gcc的支持,本文采用的是linux-gcc编译opcua_server工程,因此
set(build_dir ${PROJECT_SOURCE_DIR}/../../build_linux)
另外需要注意的是linux需要pthread库支持,win下需要 ws2_32.lib wsock32.lib Iphlpapi.lib库的支持。
3.4 编译及启动
本文的编译环境是ubuntu20.4-server-64bit的虚拟机环境,安装了cmake gcc 并编译了open62541库及安装:
进入工程目录下的build_linux:
cmake ..
make -j4
然后启动编译好的程序:
../bin/opcua_server
四、opcua协议的多点查询、多点读写案例客户端opcua_client
4.1 opcua_client工程目录
创建opcua_client工程,该opc程序链接服务端,然后读取节点数据、写入节点数据,查询节点信息,并打印输出这些信息。
opcua_client
bin
build_linux
build_mingw
build_vc
src
main.c
CMakeLists.txt
4.2 程序源码
main.c文件内容,需要注意几点:
1)服务端节点是UA_NODEID_STRING的,客户端创建节点也是该类型,并给出一致的命名空间及节点名称,例如1,SENSOR_1;
2) 在解析数值时,根据数值类型判断,如果知道服务端指定该节点的数值类型,可以快速过滤掉其他节点,例如指定UA_TYPES_INT32;
3)在数据写入时,设定的写入变量的数值类型必须和服务端节点的数值类型一致,服务无法成功写入;
4)查询节点信息时,要查询在服务端的添加节点,就是需要将指定的父节点来查找,会更快的获得节点信息,例如服务端添加节点时,是在父节点UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER)下添加的。
#ifdef __linux__
#include <open62541/server.h>
#include <open62541/server_config_default.h>
#else
#include "open62541.h"
#endif
void read_node(UA_Client *client)
{
// 读取和解析单个节点的值
UA_Variant value;
UA_Client_readValueAttribute(client, UA_NODEID_STRING(1, "SENSOR_1"), &value);
if(value.type == &UA_TYPES[UA_TYPES_INT32]) {
UA_Int32 intValue = *(UA_Int32 *)value.data;
printf("Sensor 1 Value: %i\n", intValue);
} else {
printf("Unsupported Data Type\n");
}
// UA_Variant_deleteMembers(&value);
}
void read_nodes(UA_Client *client)
{
// 读取和解析多个节点的值
UA_ReadValueId valueIds[2];
UA_ReadValueId_init(&valueIds[0]);
UA_ReadValueId_init(&valueIds[1]);
//
valueIds[0].nodeId = UA_NODEID_STRING(1, "SENSOR_1");
valueIds[0].attributeId = UA_ATTRIBUTEID_VALUE;
//
valueIds[1].nodeId = UA_NODEID_STRING(1, "SENSOR_2");
valueIds[1].attributeId = UA_ATTRIBUTEID_VALUE;
UA_ReadRequest request;
UA_ReadRequest_init(&request);
request.nodesToReadSize = 2;
request.nodesToRead = valueIds;
UA_String out = UA_STRING_NULL;
// UA_print(&request, &UA_TYPES[UA_TYPES_READREQUEST], &out);
// printf("%.*s\n", (int)out.length, out.data);
// UA_String_clear(&out);
//
UA_ReadResponse response;
UA_ReadResponse_init(&response);
// UA_print(&response, &UA_TYPES[UA_TYPES_READRESPONSE], &out);
// printf("%.*s\n", (int)out.length, out.data);
// UA_String_clear(&out);
response = UA_Client_Service_read(client, request);
//
// UA_print(&response, &UA_TYPES[UA_TYPES_READRESPONSE], &out);
// printf("%.*s\n", (int)out.length, out.data);
// UA_String_clear(&out);
if(UA_STATUSCODE_GOOD == response.responseHeader.serviceResult) {
for(size_t i = 0; i < response.resultsSize; ++i) {
UA_Variant value = response.results[i].value;
if(value.type == &UA_TYPES[UA_TYPES_INT32]) {
UA_Int32 intValue = *(UA_Int32 *)value.data;
printf("Sensor %i Value: %i\n", i+1, intValue);
} else {
printf("Unsupported Data Type\n");
}
// UA_Variant_deleteMembers(&value);
}
}
//
printf("UA_ReadResponse_clear call\n");
UA_ReadResponse_clear(&response);
};
void write_nodes(UA_Client *client)
{
// 配置及写入多个节点的值
UA_WriteValue valueIds[2];
UA_WriteValue_init(&valueIds[0]);
UA_WriteValue_init(&valueIds[1]);
//
valueIds[0].nodeId = UA_NODEID_STRING(1, "SENSOR_1");
valueIds[0].attributeId = UA_ATTRIBUTEID_VALUE;
//
valueIds[1].nodeId = UA_NODEID_STRING(1, "SENSOR_2");
valueIds[1].attributeId = UA_ATTRIBUTEID_VALUE;
UA_Variant infoVar;
UA_UInt32 uint1Value = 101;
UA_Variant_init(&infoVar);
UA_Variant_setScalar(&infoVar, &uint1Value, &UA_TYPES[UA_TYPES_INT32]);
valueIds[0].value.value = infoVar;
valueIds[0].value.hasValue = true;
UA_UInt32 uint2Value = 102;
UA_Variant_init(&infoVar);
UA_Variant_setScalar(&infoVar, &uint2Value, &UA_TYPES[UA_TYPES_INT32]);
valueIds[1].value.value = infoVar;
valueIds[1].value.hasValue = true;
printf("WriteReques init\n");
UA_WriteRequest wReq;
UA_WriteRequest_init(&wReq);
wReq.nodesToWrite = valueIds;
wReq.nodesToWriteSize = 2;
printf("WriteResponse return\n");
UA_WriteResponse wResp = UA_Client_Service_write(client, wReq);
printf("serviceResult analysis\n");
UA_StatusCode retval = wResp.responseHeader.serviceResult;
if (retval == UA_STATUSCODE_GOOD) {
if (wResp.resultsSize == 1)
retval = wResp.results[0];
else
retval = UA_STATUSCODE_BADUNEXPECTEDERROR;
printf("serviceResult analysis:%0x8d\n",retval);
}
printf("UA_WriteResponse_clear call\n");
UA_WriteResponse_clear(&wResp);
}
//节点查看
void Browse_nodes(UA_Client *client, UA_NodeId nodeId)
{
/* Browse some objects */
printf("Browsing nodes in objects folder:\n");
UA_BrowseRequest bReq;
UA_BrowseRequest_init(&bReq);
bReq.requestedMaxReferencesPerNode = 0;//限制查到的最大节点数,0 不限制
bReq.nodesToBrowse = UA_BrowseDescription_new();
bReq.nodesToBrowseSize = 1;//需要浏览的节点个数,这里只寻找nodeId节点下的节点所以为1
/*UA_BROWSEDIRECTION_FORWARD表示向下查找(即查找添加在节点下的节点),
UA_BROWSEDIRECTION_INVERSE表示向上查找(即查找节点的父节点),
UA_BROWSEDIRECTION_BOTH表示上下都进行查找*/
bReq.nodesToBrowse[0].browseDirection = UA_BROWSEDIRECTION_FORWARD;
bReq.nodesToBrowse[0].includeSubtypes = UA_TRUE;//是否包含subtypes
bReq.nodesToBrowse[0].nodeId = nodeId;
bReq.nodesToBrowse[0].resultMask = UA_BROWSERESULTMASK_ALL; //返回浏览到的节点包含的信息,名称、显示名称......,UA_BROWSERESULTMASK_ALL表示返回所有信息
UA_BrowseResponse bResp = UA_Client_Service_browse(client, bReq);
//输出浏览到的每个节点信息
printf("%-9s %-16s %-16s %-16s\n", "NAMESPACE", "NODEID", "BROWSE NAME", "DISPLAY NAME");
for(size_t i = 0; i < bResp.resultsSize; ++i) {
for(size_t j = 0; j < bResp.results[i].referencesSize; ++j) {
UA_ReferenceDescription *ref = &(bResp.results[i].references[j]);
if(ref->nodeId.nodeId.identifierType == UA_NODEIDTYPE_NUMERIC) {
printf("%-9u %-16u %-16.*s %-16.*s\n", ref->nodeId.nodeId.namespaceIndex,
ref->nodeId.nodeId.identifier.numeric, (int)ref->browseName.name.length,
ref->browseName.name.data, (int)ref->displayName.text.length,
ref->displayName.text.data);
} else if(ref->nodeId.nodeId.identifierType == UA_NODEIDTYPE_STRING) {
printf("%-9u %-16.*s %-16.*s %-16.*s\n", ref->nodeId.nodeId.namespaceIndex,
(int)ref->nodeId.nodeId.identifier.string.length,
ref->nodeId.nodeId.identifier.string.data,
(int)ref->browseName.name.length, ref->browseName.name.data,
(int)ref->displayName.text.length, ref->displayName.text.data);
}
/* TODO: distinguish further types */
}
}
// UA_BrowseRequest_clear(&bReq);
UA_BrowseResponse_clear(&bResp);
}
int main() {
UA_Client *client = UA_Client_new();
UA_ClientConfig_setDefault(UA_Client_getConfig(client));
// 设置OPC UA服务器的URL
UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://192.168.157.143:4840");
if(retval != UA_STATUSCODE_GOOD) {
UA_Client_delete(client);
return retval;
}
//读取单个节点数值
printf("read one node for one times!\n");
read_node(client);
//读取多个节点数值
printf("read more node for one times!\n");
read_nodes(client);
//写入多个节点数值
printf("write more node for one times!\n");
write_nodes(client);
//读取多个节点数值
printf("read more node for one times again!\n");
read_nodes(client);
// 浏览节点
printf("Browse_nodes call!\n");
Browse_nodes(client,UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER));
//
printf("Browse_nodes call!\n");
Browse_nodes(client,UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER));
UA_Client_disconnect(client);
UA_Client_delete(client);
return 0;
}
4.3 工程组织文件
CMakeLists.txt
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (opcua_client)
#
if(WIN32)
message(STATUS "windows compiling...")
add_definitions(-D_PLATFORM_IS_WINDOWS_)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd")
set(WIN_OS true)
else(WIN32)
message(STATUS "linux compiling...")
add_definitions( -D_PLATFORM_IS_LINUX_)
add_definitions("-Wno-invalid-source-encoding")
# add_definitions("-O2")
set(UNIX_OS true)
set(_DEBUG true)
endif(WIN32)
set(UA_IPV6 0)
set(org_dir ${PROJECT_SOURCE_DIR}/../..)
set(build_dir ${PROJECT_SOURCE_DIR}/../../build_mingw)
#set(build_dir ${PROJECT_SOURCE_DIR}/../../build_vc)
#set(build_dir ${PROJECT_SOURCE_DIR}/../../build_linux)
#
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
# 指定源文件的目录,并将名称保存到变量
SET(source_h
#
#${build_dir}/open62541.h
)
SET(source_cpp
#
${PROJECT_SOURCE_DIR}/src/main.c
${build_dir}/open62541.c
)
#头文件目录
include_directories(
${build_dir}
#${org_dir}/include
#${build_dir}/src_generated
#${org_dir}/arch
#${org_dir}/deps
#${org_dir}/plugins/include
)
if (${UNIX_OS})
add_definitions(
"-DUA_ARCHITECTURE_POSIX"
)
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -O0")
link_directories()
# 指定生成目标
add_executable(opcua_client ${source_h} ${source_cpp} )
#link
target_link_libraries(opcua_client
-lpthread
${build_dir}/bin/libopen62541.a
)
endif(${UNIX_OS})
if (${WIN_OS})
add_definitions(
"-DUA_ARCHITECTURE_WIN32"
)
link_directories(
"${build_dir}/bin"
)
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${PROJECT_SOURCE_DIR}/bin)
# 指定生成目标
add_executable(opcua_clientd ${source_h} ${source_cpp} )
#link
target_link_libraries(opcua_clientd
ws2_32.lib wsock32.lib Iphlpapi.lib
${build_dir}/bin/libopen62541.a #mingw
#${build_dir}/bin/Debug/open62541d.lib #vc
)
else(CMAKE_BUILD_TYPE)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${PROJECT_SOURCE_DIR}/bin)
# 指定生成目标
add_executable(opcua_client ${source_h} ${source_cpp})
#link
target_link_libraries(opcua_client
ws2_32.lib wsock32.lib Iphlpapi.lib
${build_dir}/bin/libopen62541.a #mingw
#${build_dir}/bin/Release/open62541.lib #vc
#D:\\workForOrgCode\\open62541\\build_vc\\bin\\Release\\open62541.lib #vc
)
endif (CMAKE_BUILD_TYPE)
endif(${WIN_OS})
4.4 编译及测试
本文客户端编译采用的是cmake+mingw-gcc编译,
cmake .. -G "MinGW Makefiles"
cmake --build . --config debug
编译完成后启动测试程序,可以顺利单节点读取数据、多节点读取数据,并多节点同时写入数据及多节点读取数据验证写入正确性,另外可指定父节点查询其下各个子节点信息: