在前一篇【一】基於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就行了。