



























using Opc.Ua; using Opc.Ua.Server; namespace OpcUaServerDemo { internal class NodeManager : CustomNodeManager2 { private FolderState _rootFolder; private string _rootNodeName; public NodeManager(IServerInternal server, ApplicationConfiguration configuration, string rootNodeName, params string[] namespaceUris) : base(server, configuration, namespaceUris) { _rootNodeName = rootNodeName; } public NodeManager(IServerInternal server, params string[] namespaceUris) : base(server, namespaceUris) { _rootNodeName = "opcdata"; // 默认值 } protected override NodeStateCollection LoadPredefinedNodes(ISystemContext context) { _rootFolder = CreateFolder(null, null, _rootNodeName); _rootFolder.AddReference(ReferenceTypes.Organizes, true, ObjectIds.ObjectsFolder); // 将节点添加到服务器根节点 _rootFolder.EventNotifier = EventNotifiers.SubscribeToEvents; AddRootNotifier(_rootFolder); return new NodeStateCollection(new List<NodeState> { _rootFolder }); } public NodeId? GetRootNodeId() { if (_rootFolder != null) { return _rootFolder.NodeId; } return null; } public FolderState? GetRootFolder() { return _rootFolder; // 直接返回保存的根节点对象 } protected virtual FolderState CreateFolder(NodeState? parent, string? path, string name, string displayName = "") { if (string.IsNullOrWhiteSpace(path)) path = parent?.NodeId.Identifier is string id ? id + "/" + name : name; string finalDisplayName = string.IsNullOrEmpty(displayName) ? name : displayName; FolderState folder = new FolderState(parent); folder.SymbolicName = name; folder.ReferenceTypeId = ReferenceTypes.Organizes; folder.TypeDefinitionId = ObjectTypeIds.FolderType; folder.NodeId = new NodeId(path, NamespaceIndex); folder.BrowseName = new QualifiedName(name, NamespaceIndex); folder.DisplayName = new LocalizedText("en", finalDisplayName); folder.WriteMask = AttributeWriteMask.None; folder.UserWriteMask = AttributeWriteMask.None; folder.EventNotifier = EventNotifiers.None; if (parent != null) { parent.AddChild(folder); } return folder; } protected virtual BaseDataVariableState CreateVariable(NodeState? parent, string? path, string name, BuiltInType dataType, int valueRank) { return CreateVariable(parent, path, name, (uint)dataType, valueRank); } protected virtual BaseDataVariableState CreateVariable(NodeState? parent, string? path, string name, NodeId dataType, int valueRank, string displayName = "", string symbolicName = "") { if (string.IsNullOrWhiteSpace(path)) path = parent?.NodeId.Identifier is string id ? id + "/" + name : name; string finalDisplayName = string.IsNullOrEmpty(displayName) ? name : displayName; BaseDataVariableState variable = new BaseDataVariableState(parent); variable.SymbolicName = string.IsNullOrEmpty(symbolicName) ? name : symbolicName; variable.ReferenceTypeId = ReferenceTypes.Organizes; variable.TypeDefinitionId = VariableTypeIds.BaseDataVariableType; variable.NodeId = new NodeId(path, NamespaceIndex); variable.BrowseName = new QualifiedName(name, NamespaceIndex); variable.DisplayName = new LocalizedText("cn", finalDisplayName); variable.WriteMask = AttributeWriteMask.None; variable.UserWriteMask = AttributeWriteMask.None; variable.DataType = dataType; variable.ValueRank = valueRank; variable.AccessLevel = AccessLevels.CurrentReadOrWrite; variable.UserAccessLevel = AccessLevels.CurrentReadOrWrite; variable.Historizing = false; variable.Value = Opc.Ua.TypeInfo.GetDefaultValue(dataType, valueRank, Server.TypeTree); variable.StatusCode = StatusCodes.Good; variable.Timestamp = DateTime.UtcNow; if (valueRank == ValueRanks.OneDimension) { variable.ArrayDimensions = new ReadOnlyList<uint>(new List<uint> { 0 }); } else if (valueRank == ValueRanks.TwoDimensions) { variable.ArrayDimensions = new ReadOnlyList<uint>(new List<uint> { 0, 0 }); } if (parent != null) { parent.AddChild(variable); } return variable; } public void UpdateValue(NodeId nodeId, object value) { var variable = (BaseDataVariableState)FindPredefinedNode(nodeId, typeof(BaseDataVariableState)); if (variable != null) { variable.Value = value; variable.Timestamp = DateTime.UtcNow; variable.ClearChangeMasks(SystemContext, false); } } public void ClearNodeChangeMasks(NodeState node, bool includeChildren = false) { node.ClearChangeMasks(SystemContext, includeChildren); } public NodeId AddFolder(NodeId parentId, string? path, string name, string displayName = "") { var node = Find(parentId); if (node is null) { Console.WriteLine("父级节点不存在"); return null; } var newNode = CreateFolder(node, path, name, displayName); AddPredefinedNode(SystemContext, newNode); return newNode.NodeId; } public NodeId AddVariable(NodeId parentId, string? path, string name, BuiltInType dataType, int valueRank) { return AddVariable(parentId, path, name, (uint)dataType, valueRank); } public NodeId AddVariable(NodeId parentId, string? path, string name, NodeId dataType, int valueRank, string displayName = "", string symbolicName = "") { var node = Find(parentId); if (node is null) { Console.WriteLine("父级节点不存在"); return null; } var newNode = CreateVariable(node, path, name, dataType, valueRank, displayName, symbolicName); AddPredefinedNode(SystemContext, newNode); return newNode.NodeId; } } }
using Opc.Ua; using Opc.Ua.Server; using System; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading.Tasks; namespace OpcUaServerDemo { public partial class OpcUaServer : StandardServer { private static readonly object s_statusLock = new object(); /// <summary> /// Initializes the server before it starts up. /// </summary> /// <remarks> /// This method is called before any startup processing occurs. The sub-class may update the /// configuration object or do any other application specific startup tasks. /// </remarks> protected async override void OnServerStarting(ApplicationConfiguration configuration) { Utils.Trace("The server is starting."); base.OnServerStarting(configuration); // it is up to the application to decide how to validate user identity tokens. // this function creates validator for X509 identity tokens. CreateUserIdentityValidators(configuration); } /// <summary> /// Called after the server has been started. /// </summary> protected override void OnServerStarted(IServerInternal server) { base.OnServerStarted(server); // request notifications when the user identity is changed. all valid users are accepted by default. server.SessionManager.ImpersonateUser += new ImpersonateEventHandler(SessionManager_ImpersonateUser); try { // allow a faster sampling interval for CurrentTime node. //server.UpdateServerStatus(val => val.Variable.CurrentTime.MinimumSamplingInterval = 250); lock (s_statusLock) { server.Status.Variable.CurrentTime.MinimumSamplingInterval = 250; } } catch { } } #region User Validation Functions private ICertificateValidator m_userCertificateValidator; /// <summary> /// Creates the objects used to validate the user identity tokens supported by the server. /// </summary> private void CreateUserIdentityValidators(ApplicationConfiguration configuration) { for (int ii = 0; ii < configuration.ServerConfiguration.UserTokenPolicies.Count; ii++) { UserTokenPolicy policy = configuration.ServerConfiguration.UserTokenPolicies[ii]; // create a validator for a certificate token policy. if (policy.TokenType == UserTokenType.Certificate) { // check if user certificate trust lists are specified in configuration. if (configuration.SecurityConfiguration.TrustedUserCertificates != null && configuration.SecurityConfiguration.UserIssuerCertificates != null) { CertificateValidator certificateValidator = new CertificateValidator(); certificateValidator.Update(configuration.SecurityConfiguration); certificateValidator.Update(configuration.SecurityConfiguration.UserIssuerCertificates, configuration.SecurityConfiguration.TrustedUserCertificates, configuration.SecurityConfiguration.RejectedCertificateStore); // set custom validator for user certificates. m_userCertificateValidator = certificateValidator.GetChannelValidator(); } } } } /// <summary> /// Called when a client tries to change its user identity. /// </summary> private void SessionManager_ImpersonateUser(Session session, ImpersonateEventArgs args) { // check for a user name token. UserNameIdentityToken userNameToken = args.NewIdentity as UserNameIdentityToken; if (userNameToken != null) { args.Identity = VerifyPassword(userNameToken); // set AuthenticatedUser role for accepted user/password authentication args.Identity.GrantedRoleIds.Add(ObjectIds.WellKnownRole_AuthenticatedUser); if (args.Identity is SystemConfigurationIdentity) { // set ConfigureAdmin role for user with permission to configure server args.Identity.GrantedRoleIds.Add(ObjectIds.WellKnownRole_ConfigureAdmin); args.Identity.GrantedRoleIds.Add(ObjectIds.WellKnownRole_SecurityAdmin); } return; } // check for x509 user token. X509IdentityToken x509Token = args.NewIdentity as X509IdentityToken; if (x509Token != null) { VerifyUserTokenCertificate(x509Token.Certificate); args.Identity = new UserIdentity(x509Token); Utils.Trace("X509 Token Accepted: {0}", args.Identity.DisplayName); // set AuthenticatedUser role for accepted certificate authentication args.Identity.GrantedRoleIds.Add(ObjectIds.WellKnownRole_AuthenticatedUser); return; } // allow anonymous authentication and set Anonymous role for this authentication args.Identity = new UserIdentity(); args.Identity.GrantedRoleIds.Add(ObjectIds.WellKnownRole_Anonymous); } /// <summary> /// Validates the password for a username token. /// </summary> private IUserIdentity VerifyPassword(UserNameIdentityToken userNameToken) { var userName = userNameToken.UserName; var password = userNameToken.DecryptedPassword; if (String.IsNullOrEmpty(userName)) { // an empty username is not accepted. throw ServiceResultException.Create(StatusCodes.BadIdentityTokenInvalid, "Security token is not a valid username token. An empty username is not accepted."); } if (String.IsNullOrEmpty(password)) { // an empty password is not accepted. throw ServiceResultException.Create(StatusCodes.BadIdentityTokenRejected, "Security token is not a valid username token. An empty password is not accepted."); }// standard users for CTT verification if (!((userName == "user1" && password == "user1") || (userName == "user2" && password == "user2"))) { // construct translation object with default text. TranslationInfo info = new TranslationInfo( "InvalidPassword", "en-US", "Invalid username or password.", userName); // create an exception with a vendor defined sub-code. throw new ServiceResultException(new ServiceResult( StatusCodes.BadUserAccessDenied, "InvalidPassword", LoadServerProperties().ProductUri, new LocalizedText(info))); } return new UserIdentity(userNameToken); } /// <summary> /// Verifies that a certificate user token is trusted. /// </summary> private void VerifyUserTokenCertificate(X509Certificate2 certificate) { try { if (m_userCertificateValidator != null) { m_userCertificateValidator.Validate(certificate); } else { CertificateValidator.Validate(certificate); } } catch (Exception e) { TranslationInfo info; StatusCode result = StatusCodes.BadIdentityTokenRejected; ServiceResultException se = e as ServiceResultException; if (se != null && se.StatusCode == StatusCodes.BadCertificateUseNotAllowed) { info = new TranslationInfo( "InvalidCertificate", "en-US", "'{0}' is an invalid user certificate.", certificate.Subject); result = StatusCodes.BadIdentityTokenInvalid; } else { // construct translation object with default text. info = new TranslationInfo( "UntrustedCertificate", "en-US", "'{0}' is not a trusted user certificate.", certificate.Subject); } // create an exception with a vendor defined sub-code. throw new ServiceResultException(new ServiceResult( result, info.Key, LoadServerProperties().ProductUri, new LocalizedText(info))); } } #endregion } }
using Opc.Ua; using Opc.Ua.Configuration; using System.Collections.Concurrent; namespace OpcUaServerDemo { public class RunOpcServer : IDisposable { private Task _serverTask; private CancellationTokenSource _cancellationTokenSource; private System.Timers.Timer _simulationTimer; private ConcurrentDictionary<string, BaseDataVariableState> _nodeDic = new ConcurrentDictionary<string, BaseDataVariableState>(); public RunOpcServer() { _cancellationTokenSource = new CancellationTokenSource(); // 启动服务器任务并保存引用 _serverTask = StartServerAsync(_cancellationTokenSource.Token); } private async Task StartServerAsync(CancellationToken cancellationToken) { try { var rootNodeName = "opcdata"; // 启动OPC UA服务器 ApplicationInstance application = new ApplicationInstance(); application.ConfigSectionName = "OpcUaServer"; application.LoadApplicationConfiguration(false); bool certOk = application.CheckApplicationInstanceCertificate(false, 0).Result; if (!certOk) { Console.WriteLine("警告:应用程序证书检查未通过,但服务器将继续启动。"); } var server = new OpcUaServer(); var nodeManagerFactory = new NodeManagerFactory(rootNodeName); server.AddNodeManager(nodeManagerFactory); // 启动服务器(这会阻塞直到服务器停止) application.Start(server).Wait(cancellationToken); Console.WriteLine("OPC UA 服务器已启动!"); // 模拟数据 var nodeManager = nodeManagerFactory.NodeManager; var root = nodeManager.GetRootFolder(); if (root is null) { Console.WriteLine($"OPCUA_Server异常:获取根节点失败"); return; } var deviceList = new List<string> { "Machine1", "Machine2", "Machine3" }; var machineFolderId = nodeManager.AddFolder(root.NodeId, null, "Machine", "Machine"); FolderState folder = nodeManager.Find(machineFolderId) as FolderState; foreach (var deviceName in deviceList) { var deviceId = nodeManager.AddFolder(machineFolderId, null, deviceName, deviceName); var device = nodeManager.Find(deviceId) as FolderState; var sensorList = new List<string> { "Sensor1", "Sensor2", "Sensor3" }; foreach (var sensorName in sensorList) { var variableNodeId = nodeManager.AddVariable(deviceId, null, sensorName, (int)BuiltInType.Int16, ValueRanks.Scalar); BaseDataVariableState variable = nodeManager.FindPredefinedNode(variableNodeId, typeof(BaseDataVariableState)) as BaseDataVariableState; var ok = _nodeDic.TryAdd(deviceName + "_" + sensorName, variable); if (!ok) { Console.WriteLine($"添加节点失败:{deviceName}_{sensorName}"); } } } _simulationTimer = new System.Timers.Timer(500); var random = new Random(); _simulationTimer.Elapsed += (sender, e) => { if (cancellationToken.IsCancellationRequested) return; try { foreach (var item in _nodeDic) { var node = item.Value; var value = random.Next(1, 60000); node.Value = value; node.Timestamp = DateTime.Now; nodeManager.ClearNodeChangeMasks(node, false); } } catch (Exception ex) { Console.WriteLine($"更新节点数据出错: {ex.Message}"); } }; _simulationTimer.Start(); await Task.Delay(Timeout.Infinite, cancellationToken); } catch (OperationCanceledException) { Console.WriteLine("服务器正在停止..."); } catch (Exception ex) { Console.WriteLine($"服务器发生错误: {ex.Message}"); } finally { _simulationTimer?.Stop(); _simulationTimer?.Dispose(); Console.WriteLine("服务器已停止。"); } } public void Stop() { _cancellationTokenSource?.Cancel(); _serverTask?.Wait(5000); } public void Dispose() { Stop(); _cancellationTokenSource?.Dispose(); _simulationTimer?.Dispose(); } } }
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。