在前一篇【一】基于open62541的OPC UA服务器和客户端的基础上,本篇主要讲述怎么配置默认的server配置,使其成为我们需要的服务器。
1. 创建和初始化server配置
这是open62541建立服务器最省事的function,啥都默认的。
UA_ServerConfig *config = UA_ServerConfig_new_default();
进入其中查看,发现port已经指定为了4840,然后另一个是certificate——证书,OPC UA采用无验证、用户名+密码或者证书+签名方式进行数据传输与通讯的加密,默认是不用加密的,如下所示
/* Creates a server config on the default port 4840 with no server * certificate. */ static UA_INLINE UA_ServerConfig * UA_ServerConfig_new_default(void) { return UA_ServerConfig_new_minimal(4840, NULL); }
再次深入可以看到,需要的参数更多,但是都给你设置成默认的了,后面的两个0分别是发送和接收缓冲区大小
/* Creates a new server config with one endpoint. * * The config will set the tcp network layer to the given port and adds a single * endpoint with the security policy ``SecurityPolicy#None`` to the server. A * server certificate may be supplied but is optional. * * @param portNumber The port number for the tcp network layer * @param certificate Optional certificate for the server endpoint. Can be * ``NULL``. */ static UA_INLINE UA_ServerConfig * UA_ServerConfig_new_minimal(UA_UInt16 portNumber, const UA_ByteString *certificate) { return UA_ServerConfig_new_customBuffer(portNumber, certificate, 0 ,0); }
再次进入可以看到一些更加详细的设置了,createDefaultConfig就是创建和初始化config结构体,配置了线程数,服务器的一些描述,接受哪些证书验证(默认全部)等等,详细的自己进去看吧
UA_ServerConfig * UA_ServerConfig_new_customBuffer(UA_UInt16 portNumber, const UA_ByteString *certificate, UA_UInt32 sendBufferSize, UA_UInt32 recvBufferSize) { UA_ServerConfig *conf = createDefaultConfig(); UA_StatusCode retval = UA_Nodestore_default_new(&conf->nodestore); if(retval != UA_STATUSCODE_GOOD) { UA_ServerConfig_delete(conf); return NULL; } if(addDefaultNetworkLayers(conf, portNumber, sendBufferSize, recvBufferSize) != UA_STATUSCODE_GOOD) { UA_ServerConfig_delete(conf); return NULL; } /* Allocate the endpoint */ conf->endpointsSize = 1; conf->endpoints = (UA_Endpoint *)UA_malloc(sizeof(UA_Endpoint)); if(!conf->endpoints) { UA_ServerConfig_delete(conf); return NULL; } /* Populate the endpoint */ UA_ByteString localCertificate = UA_BYTESTRING_NULL; if(certificate) localCertificate = *certificate; retval = createSecurityPolicyNoneEndpoint(conf, &conf->endpoints[0], localCertificate); if(retval != UA_STATUSCODE_GOOD) { UA_ServerConfig_delete(conf); return NULL; } return conf; }
createSecurityPolicyNoneEndpoint就是配置无加密方式的endpoint的函数,主要是设置security policy,这里列出了opcua支持的安全策略,有兴趣的可以看看
2. 设置ip
我不知道设置这个的意义在哪,主要是我还没用到过,毕竟localhost和本机ip都能到(有知道的朋友可以给我说下嘛),但是还是说下吧,万一有朋友需要可以自己设置啊,这个函数就在UA_ServerConfig_new_default下面
/* Set a custom hostname in server configuration * * @param config A valid server configuration * @param customHostname The custom hostname used by the server */ UA_EXPORT void UA_ServerConfig_set_customHostname(UA_ServerConfig *config, const UA_String customHostname);
直接 UA_ServerConfig_set_customHostname(config, UA_String_fromChars("192.168.5.133")); 就行了
3. 创建服务
UA_Server *server = UA_Server_new(config);
用上面创建和配置好的config初始化一个服务器模型,这个函数就是初始化一些服务器的配置。open62541默认建立了2个命名空间0和1,其中命名空间0建立了标准OPC UA所需要的所有type,还有一个标准的OPC UA服务器模型,也就是我们连接之后的server
4. 添加命名空间
opcua是用节点来标识一个个folder、object、variable、type等等的,节点由命名空间——namespace,识别号——identifier组成来表示。所以,我们建立我们自己的服务器的时候可以参考server,命名空间的话推荐用2开始的,同时我们所需要的type可以直接引用官方给建立的,当然,也可自定义自己的type,这是添加命名空间的函数
UA_UInt16 UA_Server_addNamespace(UA_Server *server, const char* name) { /* Override const attribute to get string (dirty hack) */ UA_String nameString; nameString.length = strlen(name); nameString.data = (UA_Byte*)(uintptr_t)name; return addNamespace(server, nameString); }
直接用就行了,UA_UInt16 ns = UA_Server_addNamespace(server, "https://www.cnblogs.com/eatfishcat/"); 返回值是命名空间的序号,如下图所示
5. 建立自己的服务器
一名优秀的程序员不可能上来就直接建立variable,既不美观,也不好直接反应对应变量与设备的关系
1. 建立folder
话不多说,直接上代码
UA_UInt16 ns = UA_Server_addNamespace(server, "https://www.cnblogs.com/eatfishcat/"); // 添加命名空间 UA_NodeId folderId; // 建立folder、object、type等之类的返回节点信息,方便后续使用 UA_ObjectAttributes folderAttr = UA_ObjectAttributes_default; // 创建默认object节点 folderAttr.displayName = UA_LOCALIZEDTEXT("en-US", "myFolder"); // 设置节点名字 UA_NodeId folderNodeid = UA_NODEID_NUMERIC(ns, 1); // 设置节点的id ns就用的我上面建立的namespace, identity随便 UA_NodeId folderParNodeid = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER); // 设置父节点,我放在了Objects下面 UA_NodeId folderParReferNodeid = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES); // 设置参考类型,其实就是与Objects的关系 UA_QualifiedName folderBrowseName = UA_QUALIFIEDNAME(ns, "myFolder"); // 设置由命名空间决定的浏览名称 UA_NodeId folderType = UA_NODEID_NUMERIC(0, UA_NS0ID_FOLDERTYPE); // 设置我们建立的节点的类型 UA_Server_addObjectNode(server, folderNodeid, folderParNodeid, folderParReferNodeid, folderBrowseName, folderType, folderAttr, NULL, &folderId); // 往server中添加节点 printf("ns = %d\tidentifier=%d\r\n", folderId.namespaceIndex, folderId.identifier); // 打印添加成功的节点信息
结果如下,1为代码,2为uaExpert查看的结果,3为server终端打印的我建立的folder的nodeid信息
2. 建立object
其实跟建立folder差不多,代码如下
UA_NodeId outId; // 建立folder、object、type等之类的返回节点信息,方便后续使用 UA_ObjectAttributes objattr = UA_ObjectAttributes_default; // 创建默认object节点 objattr.displayName = UA_LOCALIZEDTEXT("en-US", "myObject"); // 设置节点名字 UA_NodeId objNodeid = UA_NODEID_NUMERIC(ns, 2); // 设置节点的id ns就用的我上面建立的namespace, identity随便 UA_NodeId parReferNodeid = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES); // 设置参考类型,其实就是与Objects的关系 UA_QualifiedName objBrowseName = UA_QUALIFIEDNAME(ns, "myObject"); // 设置由命名空间决定的浏览名称 UA_NodeId objType = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE); // 设置我们建立的节点的类型,类型是引用ns0的 UA_Server_addObjectNode(server, objNodeid, folderId, parReferNodeid, objBrowseName, objType, objattr, NULL, &outId); // 往server中添加节点 printf("ns = %d\tidentifier=%d\r\n", outId.namespaceIndex, outId.identifier); // 打印添加成功的节点信息
结果如下,1为代码,2为uaExpert查看的结果,3为server终端打印的我建立在我上面建立的folder下的object的nodeid信息
3. 建立variable
代码如下,主要说下UA_NodeId newNodeId = UA_NODEID_STRING(ns, "the.answer"); 这是设置identity为字符串形式,为整数就是UA_NodeId newNodeId = UA_NODEID_NUMERIC(ns, 3); ,如果想添加float、double、string等数据类型的变量就修改UA_Variant_setScalar(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_INT32]); 后面数组的传参
UA_VariableAttributes attr = UA_VariableAttributes_default; attr.displayName = UA_LOCALIZEDTEXT("en-US", "the answer"); UA_Int32 myInteger = 42; // 定义并初始化变量 UA_Variant_setScalar(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_INT32]); // 设置变量类型,并将值传入 UA_NodeId newNodeId = UA_NODEID_STRING(ns, "the.answer"); UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES); UA_NodeId variableType = UA_NODEID_NULL; UA_QualifiedName browseName = UA_QUALIFIEDNAME(ns, "the answer"); UA_Server_addVariableNode(server, newNodeId, outId, parentReferenceNodeId, browseName, variableType, attr, NULL, NULL);
其余的自己去查看源码吧,有例程参考应该很简单的。最后运行UA_Server_run就行了。