├── .vs
└── Axiu.Opcua.Demo
│ ├── v16
│ └── .suo
│ └── DesignTimeBuild
│ └── .dtbcache.v2
├── Axiu.Opcua.Demo.Model
├── Axiu.Opcua.Demo.Model.csproj
└── OpcuaNode.cs
├── README.md
├── Axiu.Opcua.Demo.Common
├── Axiu.Opcua.Demo.Common.csproj
├── RandomLibrary.cs
└── JsonExtension.cs
├── Axiu.Opcua.Demo.Entry
├── Program.cs
└── Axiu.Opcua.Demo.Entry.csproj
├── Axiu.Opcua.Demo.Service
├── Axiu.Opcua.Demo.Service.csproj
├── OpcuaManagement.cs
├── DiscoveryManagement.cs
├── AxiuOpcuaServer.cs
├── AxiuNodeManager.cs
└── DiscoveryServer.cs
└── Axiu.Opcua.Demo.sln
/.vs/Axiu.Opcua.Demo/v16/.suo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/axiu233/AxiuOpcua.ServerDemo/HEAD/.vs/Axiu.Opcua.Demo/v16/.suo
--------------------------------------------------------------------------------
/.vs/Axiu.Opcua.Demo/DesignTimeBuild/.dtbcache.v2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/axiu233/AxiuOpcua.ServerDemo/HEAD/.vs/Axiu.Opcua.Demo/DesignTimeBuild/.dtbcache.v2
--------------------------------------------------------------------------------
/Axiu.Opcua.Demo.Model/Axiu.Opcua.Demo.Model.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.1
5 | AnyCPU;x86
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # AxiuOpcua.ServerDemo--简单的OPC-UA Server示例
3 | 本示例是在 OPCFoundation/UA-.NETStandard 代码的基础上实现的一个简单OPC-UA服务端
4 | 包含 动态创建节点、删除节点、修改节点,实时数据刷新,历史数据读取等等
5 | ## OPCFoundation官方源码连接: [OPCFoundation/UA-.NETStandard](https://github.com/OPCFoundation/UA-.NETStandard).
--------------------------------------------------------------------------------
/Axiu.Opcua.Demo.Common/Axiu.Opcua.Demo.Common.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.1
5 | AnyCPU;x86
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/Axiu.Opcua.Demo.Entry/Program.cs:
--------------------------------------------------------------------------------
1 | using Axiu.Opcua.Demo.Service;
2 | using System;
3 |
4 | namespace Axiu.Opcua.Demo.Entry
5 | {
6 | class Program
7 | {
8 | static void Main(string[] args)
9 | {
10 | OpcuaManagement server = new OpcuaManagement();
11 | server.CreateServerInstance();
12 | Console.WriteLine("OPC-UA服务已启动...");
13 | Console.ReadLine();
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Axiu.Opcua.Demo.Entry/Axiu.Opcua.Demo.Entry.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp3.1
6 | AnyCPU;x86
7 |
8 |
9 |
10 | D:\CET\Common\
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/Axiu.Opcua.Demo.Service/Axiu.Opcua.Demo.Service.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.1
5 | AnyCPU;x86
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/Axiu.Opcua.Demo.Model/OpcuaNode.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Axiu.Opcua.Demo.Model
4 | {
5 | public class OpcuaNode
6 | {
7 | ///
8 | /// 节点路径(逐级拼接)
9 | ///
10 | public string NodePath { get; set; }
11 | ///
12 | /// 父节点路径(逐级拼接)
13 | ///
14 | public string ParentPath { get; set; }
15 | ///
16 | /// 节点编号 (在我的业务系统中的节点编号并不完全唯一,但是所有测点Id都是不同的)
17 | ///
18 | public int NodeId { get; set; }
19 | ///
20 | /// 节点名称(展示名称)
21 | ///
22 | public string NodeName { get; set; }
23 | ///
24 | /// 是否端点(最底端子节点)
25 | ///
26 | public bool IsTerminal { get; set; }
27 | ///
28 | /// 节点类型
29 | ///
30 | public NodeType NodeType { get; set; }
31 | }
32 | public enum NodeType
33 | {
34 | ///
35 | /// 根节点
36 | ///
37 | Scada = 1,
38 | ///
39 | /// 目录
40 | ///
41 | Channel = 2,
42 | ///
43 | /// 目录
44 | ///
45 | Device = 3,
46 | ///
47 | /// 测点
48 | ///
49 | Measure = 4
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Axiu.Opcua.Demo.Common/RandomLibrary.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Axiu.Opcua.Demo.Common
6 | {
7 | public class RandomLibrary
8 | {
9 | //private static string RandomString = "123456789abcdefghijkmnpqrstuvwxyz123456789ABCDEFGHIJKLMNPQRSTUVWXYZ123456789";
10 | private static string RandomString = "123456789abcdefghijkmnpqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ";
11 | private static Random Random = new Random(DateTime.Now.Second);
12 | private static Random _random = new Random();
13 |
14 | #region 产生随机字符串 +GetRandomStr(int length)
15 | ///
16 | /// 产生随机字符串
17 | ///
18 | /// 字符串长度
19 | ///
20 | public static string GetRandomStr(int length)
21 | {
22 | string retValue = string.Empty;
23 | for (int i = 0; i < length; i++)
24 | {
25 | int r = Random.Next(0, RandomString.Length - 1);
26 | retValue += RandomString[r];
27 | }
28 | return retValue;
29 | }
30 | #endregion
31 |
32 | #region 产生随机数 +GetRandomInt(int min, int max)
33 | ///
34 | /// 产生随机数
35 | ///
36 | /// 最小值
37 | /// 最大值
38 | ///
39 | public static int GetRandomInt(int min, int max)
40 | {
41 | return Random.Next(min, max);
42 | }
43 | #endregion
44 |
45 | #region 产生一个随机小数 +GetRandomDouble()
46 | ///
47 | /// 产生一个随机小数
48 | ///
49 | ///
50 | public static double GetRandomDouble()
51 | {
52 | return _random.NextDouble();
53 | }
54 | #endregion
55 |
56 | #region 随机排序 +GetRandomSort(T[] arr)
57 | ///
58 | /// 随机排序
59 | /// 因为数组是引用类型 所以不需要返回值
60 | /// 调用之后即改变原数组排序,直接使用即可
61 | ///
62 | /// 参数类型,必须为数组类型
63 | /// 数组
64 | public static void GetRandomSort(T[] arr)
65 | {
66 | int count = arr.Length;
67 | for (int i = 0; i < count; i++)
68 | {
69 | int rn1 = GetRandomInt(0, arr.Length);
70 | int rn2 = GetRandomInt(0, arr.Length);
71 | T temp;
72 | temp = arr[rn1];
73 | arr[rn1] = arr[rn2];
74 | arr[rn2] = temp;
75 | }
76 | //return arr;
77 | }
78 | #endregion
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/Axiu.Opcua.Demo.Common/JsonExtension.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using Newtonsoft.Json.Converters;
3 | using Newtonsoft.Json.Linq;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Data;
7 |
8 | namespace Axiu.Opcua.Demo.Common
9 | {
10 | public static class JsonExtension
11 | {
12 | ///
13 | /// 字符串转为json
14 | ///
15 | ///
16 | ///
17 | public static object ToJson(this string Json)
18 | {
19 | return Json == null ? null : JsonConvert.DeserializeObject(Json);
20 | }
21 | ///
22 | /// object转为json
23 | ///
24 | ///
25 | ///
26 | public static string ToJson(this object obj)
27 | {
28 | var timeConverter = new IsoDateTimeConverter { DateTimeFormat = "yyyy-MM-dd HH:mm:ss" };
29 | return JsonConvert.SerializeObject(obj, timeConverter);
30 | }
31 | ///
32 | /// object转为json
33 | ///
34 | ///
35 | ///
36 | ///
37 | public static string ToJson(this object obj, string datetimeformats)
38 | {
39 | var timeConverter = new IsoDateTimeConverter { DateTimeFormat = datetimeformats };
40 | return JsonConvert.SerializeObject(obj, timeConverter);
41 | }
42 | ///
43 | /// json串转为对象
44 | ///
45 | ///
46 | ///
47 | ///
48 | public static T JsonToObject(this string Json)
49 | {
50 | return Json == null ? default(T) : JsonConvert.DeserializeObject(Json);
51 | }
52 | ///
53 | /// json串转为List
54 | ///
55 | ///
56 | ///
57 | ///
58 | public static List JsonToList(this string Json)
59 | {
60 | return Json == null ? null : JsonConvert.DeserializeObject>(Json);
61 | }
62 | ///
63 | /// json串转为DataTable
64 | ///
65 | ///
66 | ///
67 | public static DataTable JsonToTable(this string Json)
68 | {
69 | return Json == null ? null : JsonConvert.DeserializeObject(Json);
70 | }
71 | public static JObject JsonToJObject(this string Json)
72 | {
73 | return Json == null ? JObject.Parse("{}") : JObject.Parse(Json.Replace(" ", ""));
74 | }
75 | public static string DataTableToJson(this DataTable dt)
76 | {
77 | IsoDateTimeConverter timeConverter = new IsoDateTimeConverter { DateTimeFormat = "yyyy'-'MM'-'dd HH':'mm':'ss" };
78 | return JsonConvert.SerializeObject(dt, Formatting.Indented, timeConverter);
79 |
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/Axiu.Opcua.Demo.Service/OpcuaManagement.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Opc.Ua;
3 | using Opc.Ua.Configuration;
4 | using Opc.Ua.Server;
5 | using Opc.Ua.Gds.Server;
6 |
7 | namespace Axiu.Opcua.Demo.Service
8 | {
9 | public class OpcuaManagement
10 | {
11 | public void CreateServerInstance()
12 | {
13 | try
14 | {
15 | var config = new ApplicationConfiguration()
16 | {
17 | ApplicationName = "AxiuOpcua",
18 | ApplicationUri = Utils.Format(@"urn:{0}:AxiuOpcua", System.Net.Dns.GetHostName()),
19 | ApplicationType = ApplicationType.Server,
20 | ServerConfiguration = new ServerConfiguration()
21 | {
22 | BaseAddresses = { "opc.tcp://localhost:8020/", "https://localhost:8021/" },
23 | MinRequestThreadCount = 5,
24 | MaxRequestThreadCount = 100,
25 | MaxQueuedRequestCount = 200,
26 | },
27 | SecurityConfiguration = new SecurityConfiguration
28 | {
29 | ApplicationCertificate = new CertificateIdentifier { StoreType = @"Directory", StorePath = @"%CommonApplicationData%\OPC Foundation\CertificateStores\MachineDefault", SubjectName = Utils.Format(@"CN={0}, DC={1}", "AxiuOpcua", System.Net.Dns.GetHostName()) },
30 | TrustedIssuerCertificates = new CertificateTrustList { StoreType = @"Directory", StorePath = @"%CommonApplicationData%\OPC Foundation\CertificateStores\UA Certificate Authorities" },
31 | TrustedPeerCertificates = new CertificateTrustList { StoreType = @"Directory", StorePath = @"%CommonApplicationData%\OPC Foundation\CertificateStores\UA Applications" },
32 | RejectedCertificateStore = new CertificateTrustList { StoreType = @"Directory", StorePath = @"%CommonApplicationData%\OPC Foundation\CertificateStores\RejectedCertificates" },
33 | AutoAcceptUntrustedCertificates = true,
34 | AddAppCertToTrustedStore = true
35 | },
36 | TransportConfigurations = new TransportConfigurationCollection(),
37 | TransportQuotas = new TransportQuotas { OperationTimeout = 15000 },
38 | ClientConfiguration = new ClientConfiguration { DefaultSessionTimeout = 60000 },
39 | TraceConfiguration = new TraceConfiguration()
40 | };
41 | config.Validate(ApplicationType.Server).GetAwaiter().GetResult();
42 | if (config.SecurityConfiguration.AutoAcceptUntrustedCertificates)
43 | {
44 | config.CertificateValidator.CertificateValidation += (s, e) => { e.Accept = (e.Error.StatusCode == StatusCodes.BadCertificateUntrusted); };
45 | }
46 |
47 | var application = new ApplicationInstance
48 | {
49 | ApplicationName = "AxiuOpcua",
50 | ApplicationType = ApplicationType.Server,
51 | ApplicationConfiguration = config
52 | };
53 | //application.CheckApplicationInstanceCertificate(false, 2048).GetAwaiter().GetResult();
54 | bool certOk = application.CheckApplicationInstanceCertificate(false, 0).Result;
55 | if (!certOk)
56 | {
57 | Console.WriteLine("证书验证失败!");
58 | }
59 |
60 | var dis =new DiscoveryServerBase();
61 | // start the server.
62 | application.Start(new AxiuOpcuaServer()).Wait();
63 | }
64 | catch (Exception ex)
65 | {
66 | Console.ForegroundColor = ConsoleColor.Red;
67 | Console.WriteLine("启动OPC-UA服务端触发异常:" + ex.Message);
68 | Console.ResetColor();
69 | }
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/Axiu.Opcua.Demo.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.30225.117
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Axiu.Opcua.Demo.Entry", "Axiu.Opcua.Demo.Entry\Axiu.Opcua.Demo.Entry.csproj", "{544ADC48-A90A-4DAB-B238-E49D75482439}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Axiu.Opcua.Demo.Service", "Axiu.Opcua.Demo.Service\Axiu.Opcua.Demo.Service.csproj", "{D7941C9A-3955-4C75-8809-DC6C1B3ACFAD}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Axiu.Opcua.Demo.Common", "Axiu.Opcua.Demo.Common\Axiu.Opcua.Demo.Common.csproj", "{8445E2F3-AE84-4894-A272-6FBD98F09278}"
11 | EndProject
12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Axiu.Opcua.Demo.Model", "Axiu.Opcua.Demo.Model\Axiu.Opcua.Demo.Model.csproj", "{019124BD-6BBF-4451-9E33-E23881171D4D}"
13 | EndProject
14 | Global
15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
16 | Debug|Any CPU = Debug|Any CPU
17 | Debug|x86 = Debug|x86
18 | Release|Any CPU = Release|Any CPU
19 | Release|x86 = Release|x86
20 | EndGlobalSection
21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
22 | {544ADC48-A90A-4DAB-B238-E49D75482439}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {544ADC48-A90A-4DAB-B238-E49D75482439}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {544ADC48-A90A-4DAB-B238-E49D75482439}.Debug|x86.ActiveCfg = Debug|x86
25 | {544ADC48-A90A-4DAB-B238-E49D75482439}.Debug|x86.Build.0 = Debug|x86
26 | {544ADC48-A90A-4DAB-B238-E49D75482439}.Release|Any CPU.ActiveCfg = Release|Any CPU
27 | {544ADC48-A90A-4DAB-B238-E49D75482439}.Release|Any CPU.Build.0 = Release|Any CPU
28 | {544ADC48-A90A-4DAB-B238-E49D75482439}.Release|x86.ActiveCfg = Release|x86
29 | {544ADC48-A90A-4DAB-B238-E49D75482439}.Release|x86.Build.0 = Release|x86
30 | {D7941C9A-3955-4C75-8809-DC6C1B3ACFAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
31 | {D7941C9A-3955-4C75-8809-DC6C1B3ACFAD}.Debug|Any CPU.Build.0 = Debug|Any CPU
32 | {D7941C9A-3955-4C75-8809-DC6C1B3ACFAD}.Debug|x86.ActiveCfg = Debug|x86
33 | {D7941C9A-3955-4C75-8809-DC6C1B3ACFAD}.Debug|x86.Build.0 = Debug|x86
34 | {D7941C9A-3955-4C75-8809-DC6C1B3ACFAD}.Release|Any CPU.ActiveCfg = Release|Any CPU
35 | {D7941C9A-3955-4C75-8809-DC6C1B3ACFAD}.Release|Any CPU.Build.0 = Release|Any CPU
36 | {D7941C9A-3955-4C75-8809-DC6C1B3ACFAD}.Release|x86.ActiveCfg = Release|x86
37 | {D7941C9A-3955-4C75-8809-DC6C1B3ACFAD}.Release|x86.Build.0 = Release|x86
38 | {8445E2F3-AE84-4894-A272-6FBD98F09278}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
39 | {8445E2F3-AE84-4894-A272-6FBD98F09278}.Debug|Any CPU.Build.0 = Debug|Any CPU
40 | {8445E2F3-AE84-4894-A272-6FBD98F09278}.Debug|x86.ActiveCfg = Debug|x86
41 | {8445E2F3-AE84-4894-A272-6FBD98F09278}.Debug|x86.Build.0 = Debug|x86
42 | {8445E2F3-AE84-4894-A272-6FBD98F09278}.Release|Any CPU.ActiveCfg = Release|Any CPU
43 | {8445E2F3-AE84-4894-A272-6FBD98F09278}.Release|Any CPU.Build.0 = Release|Any CPU
44 | {8445E2F3-AE84-4894-A272-6FBD98F09278}.Release|x86.ActiveCfg = Release|x86
45 | {8445E2F3-AE84-4894-A272-6FBD98F09278}.Release|x86.Build.0 = Release|x86
46 | {019124BD-6BBF-4451-9E33-E23881171D4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
47 | {019124BD-6BBF-4451-9E33-E23881171D4D}.Debug|Any CPU.Build.0 = Debug|Any CPU
48 | {019124BD-6BBF-4451-9E33-E23881171D4D}.Debug|x86.ActiveCfg = Debug|x86
49 | {019124BD-6BBF-4451-9E33-E23881171D4D}.Debug|x86.Build.0 = Debug|x86
50 | {019124BD-6BBF-4451-9E33-E23881171D4D}.Release|Any CPU.ActiveCfg = Release|Any CPU
51 | {019124BD-6BBF-4451-9E33-E23881171D4D}.Release|Any CPU.Build.0 = Release|Any CPU
52 | {019124BD-6BBF-4451-9E33-E23881171D4D}.Release|x86.ActiveCfg = Release|x86
53 | {019124BD-6BBF-4451-9E33-E23881171D4D}.Release|x86.Build.0 = Release|x86
54 | EndGlobalSection
55 | GlobalSection(SolutionProperties) = preSolution
56 | HideSolutionNode = FALSE
57 | EndGlobalSection
58 | GlobalSection(ExtensibilityGlobals) = postSolution
59 | SolutionGuid = {88B972FA-57C5-4C0C-83DA-45944C03A37C}
60 | EndGlobalSection
61 | EndGlobal
62 |
--------------------------------------------------------------------------------
/Axiu.Opcua.Demo.Service/DiscoveryManagement.cs:
--------------------------------------------------------------------------------
1 | using Opc.Ua;
2 | using Opc.Ua.Configuration;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.IO;
6 | using System.Text;
7 |
8 | namespace Axiu.Opcua.Demo.Service
9 | {
10 | public class DiscoveryManagement
11 | {
12 | ///
13 | /// 启动一个Discovery服务端
14 | ///
15 | public void StartDiscovery()
16 | {
17 | try
18 | {
19 | var config = new ApplicationConfiguration()
20 | {
21 | ApplicationName = "Axiu UA Discovery",
22 | ApplicationUri = Utils.Format(@"urn:{0}:AxiuUADiscovery", System.Net.Dns.GetHostName()),
23 | ApplicationType = ApplicationType.DiscoveryServer,
24 | ServerConfiguration = new ServerConfiguration()
25 | {
26 | BaseAddresses = { "opc.tcp://localhost:4840/" },
27 | MinRequestThreadCount = 5,
28 | MaxRequestThreadCount = 100,
29 | MaxQueuedRequestCount = 200
30 | },
31 | DiscoveryServerConfiguration = new DiscoveryServerConfiguration()
32 | {
33 | BaseAddresses = { "opc.tcp://localhost:4840/" },
34 | ServerNames = { "OpcuaDiscovery" }
35 | },
36 | SecurityConfiguration = new SecurityConfiguration
37 | {
38 | ApplicationCertificate = new CertificateIdentifier { StoreType = @"Directory", StorePath = @"%CommonApplicationData%\OPC Foundation\CertificateStores\MachineDefault", SubjectName = Utils.Format(@"CN={0}, DC={1}", "AxiuOpcua", System.Net.Dns.GetHostName()) },
39 | TrustedIssuerCertificates = new CertificateTrustList { StoreType = @"Directory", StorePath = @"%CommonApplicationData%\OPC Foundation\CertificateStores\UA Certificate Authorities" },
40 | TrustedPeerCertificates = new CertificateTrustList { StoreType = @"Directory", StorePath = @"%CommonApplicationData%\OPC Foundation\CertificateStores\UA Applications" },
41 | RejectedCertificateStore = new CertificateTrustList { StoreType = @"Directory", StorePath = @"%CommonApplicationData%\OPC Foundation\CertificateStores\RejectedCertificates" },
42 | AutoAcceptUntrustedCertificates = true,
43 | AddAppCertToTrustedStore = true
44 | },
45 | TransportConfigurations = new TransportConfigurationCollection(),
46 | TransportQuotas = new TransportQuotas { OperationTimeout = 15000 },
47 | ClientConfiguration = new ClientConfiguration { DefaultSessionTimeout = 60000 },
48 | TraceConfiguration = new TraceConfiguration()
49 | };
50 | config.Validate(ApplicationType.DiscoveryServer).GetAwaiter().GetResult();
51 | if (config.SecurityConfiguration.AutoAcceptUntrustedCertificates)
52 | {
53 | config.CertificateValidator.CertificateValidation += (s, e) => { e.Accept = (e.Error.StatusCode == StatusCodes.BadCertificateUntrusted); };
54 | }
55 |
56 | var application = new ApplicationInstance
57 | {
58 | ApplicationName = "Axiu UA Discovery",
59 | ApplicationType = ApplicationType.DiscoveryServer,
60 | ApplicationConfiguration = config
61 | };
62 | //application.CheckApplicationInstanceCertificate(false, 2048).GetAwaiter().GetResult();
63 | bool certOk = application.CheckApplicationInstanceCertificate(false, 0).Result;
64 | if (!certOk)
65 | {
66 | Console.WriteLine("证书验证失败!");
67 | }
68 |
69 | var server = new DiscoveryServer();
70 | // start the server.
71 | application.Start(server).Wait();
72 | }
73 | catch (Exception ex)
74 | {
75 | Console.ForegroundColor = ConsoleColor.Red;
76 | Console.WriteLine("启动OPC-UA Discovery服务端触发异常:" + ex.Message);
77 | Console.ResetColor();
78 | }
79 | }
80 | }
81 |
82 | ///
83 | /// 已注册服务列表
84 | ///
85 | ///
86 | /// 主要用于保存服务注册信息,客户端获取列表时将对应的信息返回给客户端
87 | ///
88 | public class RegisteredServerTable
89 | {
90 | public string ServerUri { get; set; }
91 |
92 | public string ProductUri { get; set; }
93 |
94 | public LocalizedTextCollection ServerNames { get; set; }
95 |
96 | public ApplicationType ServerType { get; set; }
97 |
98 | public string GatewayServerUri { get; set; }
99 |
100 | public StringCollection DiscoveryUrls { get; set; }
101 |
102 | public string SemaphoreFilePath { get; set; }
103 |
104 | public bool IsOnline { get; set; }
105 |
106 | ///
107 | /// 最后一次注册时间
108 | ///
109 | public DateTime LastRegistered { get; set; }
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/Axiu.Opcua.Demo.Service/AxiuOpcuaServer.cs:
--------------------------------------------------------------------------------
1 | using Opc.Ua;
2 | using Opc.Ua.Server;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Security.Cryptography.X509Certificates;
6 | using System.Text;
7 |
8 | namespace Axiu.Opcua.Demo.Service
9 | {
10 | public class AxiuOpcuaServer : StandardServer
11 | {
12 | #region Overridden Methods
13 | ///
14 | /// Creates the node managers for the server.
15 | ///
16 | ///
17 | /// This method allows the sub-class create any additional node managers which it uses. The SDK
18 | /// always creates a CoreNodeManager which handles the built-in nodes defined by the specification.
19 | /// Any additional NodeManagers are expected to handle application specific nodes.
20 | ///
21 | protected override MasterNodeManager CreateMasterNodeManager(IServerInternal server, ApplicationConfiguration configuration)
22 | {
23 | Utils.Trace("Creating the Node Managers.");
24 |
25 | List nodeManagers = new List();
26 |
27 | // create the custom node managers.
28 | nodeManagers.Add(new AxiuNodeManager(server, configuration));
29 |
30 | // create master node manager.
31 | return new MasterNodeManager(server, configuration, null, nodeManagers.ToArray());
32 | }
33 |
34 | ///
35 | /// Loads the non-configurable properties for the application.
36 | ///
37 | ///
38 | /// These properties are exposed by the server but cannot be changed by administrators.
39 | ///
40 | protected override ServerProperties LoadServerProperties()
41 | {
42 | ServerProperties properties = new ServerProperties();
43 |
44 | properties.ManufacturerName = "OPC Foundation";
45 | properties.ProductName = "Quickstart Reference Server";
46 | properties.ProductUri = "http://opcfoundation.org/Quickstart/ReferenceServer/v1.04";
47 | properties.SoftwareVersion = Utils.GetAssemblySoftwareVersion();
48 | properties.BuildNumber = Utils.GetAssemblyBuildNumber();
49 | properties.BuildDate = Utils.GetAssemblyTimestamp();
50 |
51 | return properties;
52 | }
53 |
54 | ///
55 | /// Creates the resource manager for the server.
56 | ///
57 | protected override ResourceManager CreateResourceManager(IServerInternal server, ApplicationConfiguration configuration)
58 | {
59 | ResourceManager resourceManager = new ResourceManager(server, configuration);
60 |
61 | System.Reflection.FieldInfo[] fields = typeof(StatusCodes).GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
62 |
63 | foreach (System.Reflection.FieldInfo field in fields)
64 | {
65 | uint? id = field.GetValue(typeof(StatusCodes)) as uint?;
66 |
67 | if (id != null)
68 | {
69 | resourceManager.Add(id.Value, "en-US", field.Name);
70 | }
71 | }
72 |
73 | return resourceManager;
74 | }
75 |
76 | ///
77 | /// Initializes the server before it starts up.
78 | ///
79 | ///
80 | /// This method is called before any startup processing occurs. The sub-class may update the
81 | /// configuration object or do any other application specific startup tasks.
82 | ///
83 | protected override void OnServerStarting(ApplicationConfiguration configuration)
84 | {
85 | Utils.Trace("The server is starting.");
86 |
87 | base.OnServerStarting(configuration);
88 |
89 | // it is up to the application to decide how to validate user identity tokens.
90 | // this function creates validator for X509 identity tokens.
91 | CreateUserIdentityValidators(configuration);
92 | }
93 |
94 | ///
95 | /// Called after the server has been started.
96 | ///
97 | protected override void OnServerStarted(IServerInternal server)
98 | {
99 | base.OnServerStarted(server);
100 |
101 | // request notifications when the user identity is changed. all valid users are accepted by default.
102 | server.SessionManager.ImpersonateUser += new ImpersonateEventHandler(SessionManager_ImpersonateUser);
103 |
104 | try
105 | {
106 | // allow a faster sampling interval for CurrentTime node.
107 | server.Status.Variable.CurrentTime.MinimumSamplingInterval = 250;
108 | }
109 | catch
110 | { }
111 |
112 | }
113 |
114 | #endregion
115 | #region User Validation Functions
116 | ///
117 | /// Creates the objects used to validate the user identity tokens supported by the server.
118 | ///
119 | private void CreateUserIdentityValidators(ApplicationConfiguration configuration)
120 | {
121 | for (int ii = 0; ii < configuration.ServerConfiguration.UserTokenPolicies.Count; ii++)
122 | {
123 | UserTokenPolicy policy = configuration.ServerConfiguration.UserTokenPolicies[ii];
124 |
125 | // create a validator for a certificate token policy.
126 | if (policy.TokenType == UserTokenType.Certificate)
127 | {
128 | // check if user certificate trust lists are specified in configuration.
129 | if (configuration.SecurityConfiguration.TrustedUserCertificates != null &&
130 | configuration.SecurityConfiguration.UserIssuerCertificates != null)
131 | {
132 | CertificateValidator certificateValidator = new CertificateValidator();
133 | certificateValidator.Update(configuration.SecurityConfiguration).Wait();
134 | certificateValidator.Update(configuration.SecurityConfiguration.UserIssuerCertificates,
135 | configuration.SecurityConfiguration.TrustedUserCertificates,
136 | configuration.SecurityConfiguration.RejectedCertificateStore);
137 |
138 | // set custom validator for user certificates.
139 | m_userCertificateValidator = certificateValidator.GetChannelValidator();
140 | }
141 | }
142 | }
143 | }
144 |
145 | ///
146 | /// Called when a client tries to change its user identity.
147 | ///
148 | private void SessionManager_ImpersonateUser(Session session, ImpersonateEventArgs args)
149 | {
150 | // check for a user name token.
151 | UserNameIdentityToken userNameToken = args.NewIdentity as UserNameIdentityToken;
152 |
153 | if (userNameToken != null)
154 | {
155 | args.Identity = VerifyPassword(userNameToken);
156 |
157 | // set AuthenticatedUser role for accepted user/password authentication
158 | args.Identity.GrantedRoleIds.Add(ObjectIds.WellKnownRole_AuthenticatedUser);
159 |
160 | if (args.Identity is SystemConfigurationIdentity)
161 | {
162 | // set ConfigureAdmin role for user with permission to configure server
163 | args.Identity.GrantedRoleIds.Add(ObjectIds.WellKnownRole_ConfigureAdmin);
164 | args.Identity.GrantedRoleIds.Add(ObjectIds.WellKnownRole_SecurityAdmin);
165 | }
166 |
167 | return;
168 | }
169 |
170 | // check for x509 user token.
171 | X509IdentityToken x509Token = args.NewIdentity as X509IdentityToken;
172 |
173 | if (x509Token != null)
174 | {
175 | VerifyUserTokenCertificate(x509Token.Certificate);
176 | args.Identity = new UserIdentity(x509Token);
177 | Utils.Trace("X509 Token Accepted: {0}", args.Identity.DisplayName);
178 |
179 | // set AuthenticatedUser role for accepted certificate authentication
180 | args.Identity.GrantedRoleIds.Add(ObjectIds.WellKnownRole_AuthenticatedUser);
181 |
182 | return;
183 | }
184 |
185 | // allow anonymous authentication and set Anonymous role for this authentication
186 | args.Identity = new UserIdentity();
187 | args.Identity.GrantedRoleIds.Add(ObjectIds.WellKnownRole_Anonymous);
188 | }
189 |
190 | ///
191 | /// Validates the password for a username token.
192 | ///
193 | private IUserIdentity VerifyPassword(UserNameIdentityToken userNameToken)
194 | {
195 | var userName = userNameToken.UserName;
196 | var password = userNameToken.DecryptedPassword;
197 | if (String.IsNullOrEmpty(userName))
198 | {
199 | // an empty username is not accepted.
200 | throw ServiceResultException.Create(StatusCodes.BadIdentityTokenInvalid,
201 | "Security token is not a valid username token. An empty username is not accepted.");
202 | }
203 |
204 | if (String.IsNullOrEmpty(password))
205 | {
206 | // an empty password is not accepted.
207 | throw ServiceResultException.Create(StatusCodes.BadIdentityTokenRejected,
208 | "Security token is not a valid username token. An empty password is not accepted.");
209 | }
210 |
211 | // User with permission to configure server
212 | if (userName == "sysadmin" && password == "demo")
213 | {
214 | return new SystemConfigurationIdentity(new UserIdentity(userNameToken));
215 | }
216 |
217 | // standard users for CTT verification
218 | if (!((userName == "user1" && password == "password") ||
219 | (userName == "user2" && password == "password1")))
220 | {
221 | // construct translation object with default text.
222 | TranslationInfo info = new TranslationInfo(
223 | "InvalidPassword",
224 | "en-US",
225 | "Invalid username or password.",
226 | userName);
227 |
228 | // create an exception with a vendor defined sub-code.
229 | throw new ServiceResultException(new ServiceResult(
230 | StatusCodes.BadUserAccessDenied,
231 | "InvalidPassword",
232 | LoadServerProperties().ProductUri,
233 | new LocalizedText(info)));
234 | }
235 |
236 | return new UserIdentity(userNameToken);
237 | }
238 |
239 | ///
240 | /// Verifies that a certificate user token is trusted.
241 | ///
242 | private void VerifyUserTokenCertificate(X509Certificate2 certificate)
243 | {
244 | try
245 | {
246 | if (m_userCertificateValidator != null)
247 | {
248 | m_userCertificateValidator.Validate(certificate);
249 | }
250 | else
251 | {
252 | CertificateValidator.Validate(certificate);
253 | }
254 | }
255 | catch (Exception e)
256 | {
257 | TranslationInfo info;
258 | StatusCode result = StatusCodes.BadIdentityTokenRejected;
259 | ServiceResultException se = e as ServiceResultException;
260 | if (se != null && se.StatusCode == StatusCodes.BadCertificateUseNotAllowed)
261 | {
262 | info = new TranslationInfo(
263 | "InvalidCertificate",
264 | "en-US",
265 | "'{0}' is an invalid user certificate.",
266 | certificate.Subject);
267 |
268 | result = StatusCodes.BadIdentityTokenInvalid;
269 | }
270 | else
271 | {
272 | // construct translation object with default text.
273 | info = new TranslationInfo(
274 | "UntrustedCertificate",
275 | "en-US",
276 | "'{0}' is not a trusted user certificate.",
277 | certificate.Subject);
278 | }
279 |
280 | // create an exception with a vendor defined sub-code.
281 | throw new ServiceResultException(new ServiceResult(
282 | result,
283 | info.Key,
284 | LoadServerProperties().ProductUri,
285 | new LocalizedText(info)));
286 | }
287 | }
288 | #endregion
289 |
290 | #region Private Fields
291 | private ICertificateValidator m_userCertificateValidator;
292 | #endregion
293 | }
294 | }
295 |
--------------------------------------------------------------------------------
/Axiu.Opcua.Demo.Service/AxiuNodeManager.cs:
--------------------------------------------------------------------------------
1 | using Axiu.Opcua.Demo.Common;
2 | using Axiu.Opcua.Demo.Model;
3 | using Opc.Ua;
4 | using Opc.Ua.Server;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Linq;
8 | using System.Text;
9 | using System.Threading;
10 | using System.Threading.Tasks;
11 |
12 | namespace Axiu.Opcua.Demo.Service
13 | {
14 | ///
15 | /// 以下备注中 测点即代表最叶子级节点
16 | /// 目前设计是 只有测点有数据 其余节点都是目录
17 | ///
18 | public class AxiuNodeManager : CustomNodeManager2
19 | {
20 | ///
21 | /// 配置修改次数 主要用来识别菜单树是否有变动 如果发生变动则修改菜单树对应节点 测点的实时数据变化不算在内
22 | ///
23 | private int cfgCount = -1;
24 | private IList _references;
25 | ///
26 | /// 测点集合,实时数据刷新时,直接从字典中取出对应的测点,修改值即可
27 | ///
28 | private Dictionary _nodeDic = new Dictionary();
29 | ///
30 | /// 目录集合,修改菜单树时需要(我们需要知道哪些菜单需要修改,哪些需要新增,哪些需要删除)
31 | ///
32 | private Dictionary _folderDic = new Dictionary();
33 |
34 | public AxiuNodeManager(IServerInternal server, ApplicationConfiguration configuration) : base(server, configuration, "http://opcfoundation.org/Quickstarts/ReferenceApplications")
35 | {
36 | }
37 |
38 | ///
39 | /// 重写NodeId生成方式(目前采用'_'分隔,如需更改,请修改此方法)
40 | ///
41 | ///
42 | ///
43 | ///
44 | public override NodeId New(ISystemContext context, NodeState node)
45 | {
46 | BaseInstanceState instance = node as BaseInstanceState;
47 |
48 | if (instance != null && instance.Parent != null)
49 | {
50 | string id = instance.Parent.NodeId.Identifier as string;
51 |
52 | if (id != null)
53 | {
54 | return new NodeId(id + "_" + instance.SymbolicName, instance.Parent.NodeId.NamespaceIndex);
55 | }
56 | }
57 |
58 | return node.NodeId;
59 | }
60 |
61 | ///
62 | /// 重写获取节点句柄的方法
63 | ///
64 | ///
65 | ///
66 | ///
67 | ///
68 | protected override NodeHandle GetManagerHandle(ServerSystemContext context, NodeId nodeId, IDictionary cache)
69 | {
70 | lock (Lock)
71 | {
72 | // quickly exclude nodes that are not in the namespace.
73 | if (!IsNodeIdInNamespace(nodeId))
74 | {
75 | return null;
76 | }
77 |
78 | NodeState node = null;
79 |
80 | if (!PredefinedNodes.TryGetValue(nodeId, out node))
81 | {
82 | return null;
83 | }
84 |
85 | NodeHandle handle = new NodeHandle();
86 |
87 | handle.NodeId = nodeId;
88 | handle.Node = node;
89 | handle.Validated = true;
90 |
91 | return handle;
92 | }
93 | }
94 |
95 | ///
96 | /// 重写节点的验证方式
97 | ///
98 | ///
99 | ///
100 | ///
101 | ///
102 | protected override NodeState ValidateNode(ServerSystemContext context, NodeHandle handle, IDictionary cache)
103 | {
104 | // not valid if no root.
105 | if (handle == null)
106 | {
107 | return null;
108 | }
109 |
110 | // check if previously validated.
111 | if (handle.Validated)
112 | {
113 | return handle.Node;
114 | }
115 | // TBD
116 | return null;
117 | }
118 |
119 | ///
120 | /// 重写创建基础目录
121 | ///
122 | ///
123 | public override void CreateAddressSpace(IDictionary> externalReferences)
124 | {
125 | lock (Lock)
126 | {
127 | if (!externalReferences.TryGetValue(ObjectIds.ObjectsFolder, out _references))
128 | {
129 | externalReferences[ObjectIds.ObjectsFolder] = _references = new List();
130 | }
131 |
132 | try
133 | {
134 | //TODO: 获取节点树
135 | List nodes = new List()
136 | {
137 | /*
138 | * OpcuaNode类由于个人业务相关 定义成如下格式,
139 | * 可根据自身数据便利 修改相应的数据结构
140 | * 只需保证能明确知道各个节点的从属关系即可
141 | */
142 | new OpcuaNode(){NodeId=1,NodeName="模拟根节点",NodePath="1",NodeType=NodeType.Scada,ParentPath="",IsTerminal=false },
143 | new OpcuaNode(){NodeId=11,NodeName="子目录1",NodePath="11",NodeType=NodeType.Channel,ParentPath="1",IsTerminal=false },
144 | new OpcuaNode(){NodeId=12,NodeName="子目录2",NodePath="12",NodeType=NodeType.Device,ParentPath="1",IsTerminal=false },
145 | new OpcuaNode(){NodeId=111,NodeName="叶子节点1",NodePath="111",NodeType=NodeType.Measure,ParentPath="11", IsTerminal=true },
146 | new OpcuaNode(){NodeId=112,NodeName="叶子节点2",NodePath="112",NodeType=NodeType.Measure,ParentPath="11",IsTerminal=true },
147 | new OpcuaNode(){NodeId=113,NodeName="叶子节点3",NodePath="113",NodeType=NodeType.Measure,ParentPath="11",IsTerminal=true },
148 | new OpcuaNode(){NodeId=114,NodeName="叶子节点4",NodePath="114",NodeType=NodeType.Measure,ParentPath="11",IsTerminal=true },
149 | new OpcuaNode(){NodeId=121,NodeName="叶子节点1",NodePath="121",NodeType=NodeType.Measure,ParentPath="12",IsTerminal=true },
150 | new OpcuaNode(){NodeId=122,NodeName="叶子节点2",NodePath="122",NodeType=NodeType.Measure,ParentPath="12",IsTerminal=true }
151 | };
152 | //开始创建节点的菜单树
153 | GeneraterNodes(nodes, _references);
154 | //实时更新测点的数据
155 | UpdateVariableValue();
156 | }
157 | catch (Exception ex)
158 | {
159 | Console.ForegroundColor = ConsoleColor.Red;
160 | Console.WriteLine("调用接口初始化触发异常:" + ex.Message);
161 | Console.ResetColor();
162 | }
163 | }
164 | }
165 |
166 | ///
167 | /// 生成根节点(由于根节点需要特殊处理,此处单独出来一个方法)
168 | ///
169 | ///
170 | ///
171 | private void GeneraterNodes(List nodes, IList references)
172 | {
173 | var list = nodes.Where(d => d.NodeType == NodeType.Scada);
174 | foreach (var item in list)
175 | {
176 | try
177 | {
178 | FolderState root = CreateFolder(null, item.NodePath, item.NodeName);
179 | root.AddReference(ReferenceTypes.Organizes, true, ObjectIds.ObjectsFolder);
180 | references.Add(new NodeStateReference(ReferenceTypes.Organizes, false, root.NodeId));
181 | root.EventNotifier = EventNotifiers.SubscribeToEvents;
182 | AddRootNotifier(root);
183 | CreateNodes(nodes, root, item.NodePath);
184 | _folderDic.Add(item.NodePath, root);
185 | //添加引用关系
186 | AddPredefinedNode(SystemContext, root);
187 | }
188 | catch (Exception ex)
189 | {
190 | Console.ForegroundColor = ConsoleColor.Red;
191 | Console.WriteLine("创建OPC-UA根节点触发异常:" + ex.Message);
192 | Console.ResetColor();
193 | }
194 | }
195 | }
196 |
197 | ///
198 | /// 递归创建子节点(包括创建目录和测点)
199 | ///
200 | ///
201 | ///
202 | private void CreateNodes(List nodes, FolderState parent, string parentPath)
203 | {
204 | var list = nodes.Where(d => d.ParentPath == parentPath);
205 | foreach (var node in list)
206 | {
207 | try
208 | {
209 | if (!node.IsTerminal)
210 | {
211 | FolderState folder = CreateFolder(parent, node.NodePath, node.NodeName);
212 | _folderDic.Add(node.NodePath, folder);
213 | CreateNodes(nodes, folder, node.NodePath);
214 | }
215 | else
216 | {
217 | BaseDataVariableState variable = CreateVariable(parent, node.NodePath, node.NodeName, DataTypeIds.Double, ValueRanks.Scalar);
218 | //此处需要注意 目录字典是以目录路径作为KEY 而 测点字典是以测点ID作为KEY 为了方便更新实时数据
219 | _nodeDic.Add(node.NodeId.ToString(), variable);
220 | }
221 | }
222 | catch (Exception ex)
223 | {
224 | Console.ForegroundColor = ConsoleColor.Red;
225 | Console.WriteLine("创建OPC-UA子节点触发异常:" + ex.Message);
226 | Console.ResetColor();
227 | }
228 | }
229 | }
230 |
231 | ///
232 | /// 创建目录
233 | ///
234 | ///
235 | ///
236 | ///
237 | ///
238 | private FolderState CreateFolder(NodeState parent, string path, string name)
239 | {
240 | FolderState folder = new FolderState(parent);
241 |
242 | folder.SymbolicName = name;
243 | folder.ReferenceTypeId = ReferenceTypes.Organizes;
244 | folder.TypeDefinitionId = ObjectTypeIds.FolderType;
245 | folder.NodeId = new NodeId(path, NamespaceIndex);
246 | folder.BrowseName = new QualifiedName(path, NamespaceIndex);
247 | folder.DisplayName = new LocalizedText("en", name);
248 | folder.WriteMask = AttributeWriteMask.None;
249 | folder.UserWriteMask = AttributeWriteMask.None;
250 | folder.EventNotifier = EventNotifiers.None;
251 |
252 | if (parent != null)
253 | {
254 | parent.AddChild(folder);
255 | }
256 |
257 | return folder;
258 | }
259 |
260 | ///
261 | /// 创建节点
262 | ///
263 | ///
264 | ///
265 | ///
266 | ///
267 | ///
268 | ///
269 | private BaseDataVariableState CreateVariable(NodeState parent, string path, string name, NodeId dataType, int valueRank)
270 | {
271 | BaseDataVariableState variable = new BaseDataVariableState(parent);
272 |
273 | variable.SymbolicName = name;
274 | variable.ReferenceTypeId = ReferenceTypes.Organizes;
275 | variable.TypeDefinitionId = VariableTypeIds.BaseDataVariableType;
276 | variable.NodeId = new NodeId(path, NamespaceIndex);
277 | variable.BrowseName = new QualifiedName(path, NamespaceIndex);
278 | variable.DisplayName = new LocalizedText("en", name);
279 | variable.WriteMask = AttributeWriteMask.DisplayName | AttributeWriteMask.Description;
280 | variable.UserWriteMask = AttributeWriteMask.DisplayName | AttributeWriteMask.Description;
281 | variable.DataType = dataType;
282 | variable.ValueRank = valueRank;
283 | variable.AccessLevel = AccessLevels.CurrentReadOrWrite;
284 | variable.UserAccessLevel = AccessLevels.CurrentReadOrWrite;
285 | variable.Historizing = false;
286 | //variable.Value = GetNewValue(variable);
287 | variable.StatusCode = StatusCodes.Good;
288 | variable.Timestamp = DateTime.Now;
289 | variable.OnWriteValue = OnWriteDataValue;
290 |
291 | if (valueRank == ValueRanks.OneDimension)
292 | {
293 | variable.ArrayDimensions = new ReadOnlyList(new List { 0 });
294 | }
295 | else if (valueRank == ValueRanks.TwoDimensions)
296 | {
297 | variable.ArrayDimensions = new ReadOnlyList(new List { 0, 0 });
298 | }
299 |
300 | if (parent != null)
301 | {
302 | parent.AddChild(variable);
303 | }
304 |
305 | return variable;
306 | }
307 |
308 | ///
309 | /// 实时更新节点数据
310 | ///
311 | public void UpdateVariableValue()
312 | {
313 | Task.Run(() =>
314 | {
315 | while (true)
316 | {
317 | try
318 | {
319 | /*
320 | * 此处仅作示例代码 所以不修改节点树 故将UpdateNodesAttribute()方法跳过
321 | * 在实际业务中 请根据自身的业务需求决定何时修改节点菜单树
322 | */
323 | int count = 0;
324 | //配置发生更改时,重新生成节点树
325 | if (count > 0 && count != cfgCount)
326 | {
327 | cfgCount = count;
328 | List nodes = new List();
329 | /*
330 | * 此处有想过删除整个菜单树,然后重建 保证各个NodeId仍与原来的一直
331 | * 但是 后来发现这样会导致原来的客户端订阅信息丢失 无法获取订阅数据
332 | * 所以 只能一级级的检查节点 然后修改属性
333 | */
334 | UpdateNodesAttribute(nodes);
335 | }
336 | //模拟获取实时数据
337 | BaseDataVariableState node = null;
338 | /*
339 | * 在实际业务中应该是根据对应的标识来更新固定节点的数据
340 | * 这里 我偷个懒 全部测点都更新为一个新的随机数
341 | */
342 | foreach (var item in _nodeDic)
343 | {
344 | node = item.Value;
345 | node.Value = RandomLibrary.GetRandomInt(0, 99);
346 | node.Timestamp = DateTime.Now;
347 | //变更标识 只有执行了这一步,订阅的客户端才会收到新的数据
348 | node.ClearChangeMasks(SystemContext, false);
349 | }
350 | //1秒更新一次
351 | Thread.Sleep(1000 * 1);
352 | }
353 | catch (Exception ex)
354 | {
355 | Console.ForegroundColor = ConsoleColor.Red;
356 | Console.WriteLine("更新OPC-UA节点数据触发异常:" + ex.Message);
357 | Console.ResetColor();
358 | }
359 | }
360 | });
361 | }
362 |
363 | ///
364 | /// 修改节点树(添加节点,删除节点,修改节点名称)
365 | ///
366 | ///
367 | public void UpdateNodesAttribute(List nodes)
368 | {
369 | //修改或创建根节点
370 | var scadas = nodes.Where(d => d.NodeType == NodeType.Scada);
371 | foreach (var item in scadas)
372 | {
373 | FolderState scadaNode = null;
374 | if (!_folderDic.TryGetValue(item.NodePath, out scadaNode))
375 | {
376 | //如果根节点都不存在 那么整个树都需要创建
377 | FolderState root = CreateFolder(null, item.NodePath, item.NodeName);
378 | root.AddReference(ReferenceTypes.Organizes, true, ObjectIds.ObjectsFolder);
379 | _references.Add(new NodeStateReference(ReferenceTypes.Organizes, false, root.NodeId));
380 | root.EventNotifier = EventNotifiers.SubscribeToEvents;
381 | AddRootNotifier(root);
382 | CreateNodes(nodes, root, item.NodePath);
383 | _folderDic.Add(item.NodePath, root);
384 | AddPredefinedNode(SystemContext, root);
385 | continue;
386 | }
387 | else
388 | {
389 | scadaNode.DisplayName = item.NodeName;
390 | scadaNode.ClearChangeMasks(SystemContext, false);
391 | }
392 | }
393 | //修改或创建目录(此处设计为可以有多级目录,上面是演示数据,所以我只写了三级,事实上更多级也是可以的)
394 | var folders = nodes.Where(d => d.NodeType != NodeType.Scada && !d.IsTerminal);
395 | foreach (var item in folders)
396 | {
397 | FolderState folder = null;
398 | if (!_folderDic.TryGetValue(item.NodePath, out folder))
399 | {
400 | var par = GetParentFolderState(nodes, item);
401 | folder = CreateFolder(par, item.NodePath, item.NodeName);
402 | AddPredefinedNode(SystemContext, folder);
403 | par.ClearChangeMasks(SystemContext, false);
404 | _folderDic.Add(item.NodePath, folder);
405 | }
406 | else
407 | {
408 | folder.DisplayName = item.NodeName;
409 | folder.ClearChangeMasks(SystemContext, false);
410 | }
411 | }
412 | //修改或创建测点
413 | //这里我的数据结构采用IsTerminal来代表是否是测点 实际业务中可能需要根据自身需要调整
414 | var paras = nodes.Where(d => d.IsTerminal);
415 | foreach (var item in paras)
416 | {
417 | BaseDataVariableState node = null;
418 | if (_nodeDic.TryGetValue(item.NodeId.ToString(), out node))
419 | {
420 | node.DisplayName = item.NodeName;
421 | node.Timestamp = DateTime.Now;
422 | node.ClearChangeMasks(SystemContext, false);
423 | }
424 | else
425 | {
426 | FolderState folder = null;
427 | if (_folderDic.TryGetValue(item.ParentPath, out folder))
428 | {
429 | node = CreateVariable(folder, item.NodePath, item.NodeName, DataTypeIds.Double, ValueRanks.Scalar);
430 | AddPredefinedNode(SystemContext, node);
431 | folder.ClearChangeMasks(SystemContext, false);
432 | _nodeDic.Add(item.NodeId.ToString(), node);
433 | }
434 | }
435 | }
436 |
437 | /*
438 | * 将新获取到的菜单列表与原列表对比
439 | * 如果新菜单列表中不包含原有的菜单
440 | * 则说明这个菜单被删除了 这里也需要删除
441 | */
442 | List folderPath = _folderDic.Keys.ToList();
443 | List nodePath = _nodeDic.Keys.ToList();
444 | var remNode = nodePath.Except(nodes.Where(d => d.IsTerminal).Select(d => d.NodeId.ToString()));
445 | foreach (var str in remNode)
446 | {
447 | BaseDataVariableState node = null;
448 | if (_nodeDic.TryGetValue(str, out node))
449 | {
450 | var parent = node.Parent;
451 | parent.RemoveChild(node);
452 | _nodeDic.Remove(str);
453 | }
454 | }
455 | var remFolder = folderPath.Except(nodes.Where(d => !d.IsTerminal).Select(d => d.NodePath));
456 | foreach (string str in remFolder)
457 | {
458 | FolderState folder = null;
459 | if (_folderDic.TryGetValue(str, out folder))
460 | {
461 | var parent = folder.Parent;
462 | if (parent != null)
463 | {
464 | parent.RemoveChild(folder);
465 | _folderDic.Remove(str);
466 | }
467 | else
468 | {
469 | RemoveRootNotifier(folder);
470 | RemovePredefinedNode(SystemContext, folder, new List());
471 | }
472 | }
473 | }
474 | }
475 |
476 | ///
477 | /// 创建父级目录(请确保对应的根目录已创建)
478 | ///
479 | ///
480 | ///
481 | ///
482 | public FolderState GetParentFolderState(IEnumerable nodes, OpcuaNode currentNode)
483 | {
484 | FolderState folder = null;
485 | if (!_folderDic.TryGetValue(currentNode.ParentPath, out folder))
486 | {
487 | var parent = nodes.Where(d => d.NodePath == currentNode.ParentPath).FirstOrDefault();
488 | if (!string.IsNullOrEmpty(parent.ParentPath))
489 | {
490 | var pFol = GetParentFolderState(nodes, parent);
491 | folder = CreateFolder(pFol, parent.NodePath, parent.NodeName);
492 | pFol.ClearChangeMasks(SystemContext, false);
493 | AddPredefinedNode(SystemContext, folder);
494 | _folderDic.Add(currentNode.ParentPath, folder);
495 | }
496 | }
497 | return folder;
498 | }
499 |
500 | ///
501 | /// 客户端写入值时触发(绑定到节点的写入事件上)
502 | ///
503 | ///
504 | ///
505 | ///
506 | ///
507 | ///
508 | ///
509 | ///
510 | ///
511 | private ServiceResult OnWriteDataValue(ISystemContext context, NodeState node, NumericRange indexRange, QualifiedName dataEncoding,
512 | ref object value, ref StatusCode statusCode, ref DateTime timestamp)
513 | {
514 | BaseDataVariableState variable = node as BaseDataVariableState;
515 | try
516 | {
517 | //验证数据类型
518 | TypeInfo typeInfo = TypeInfo.IsInstanceOfDataType(
519 | value,
520 | variable.DataType,
521 | variable.ValueRank,
522 | context.NamespaceUris,
523 | context.TypeTable);
524 |
525 | if (typeInfo == null || typeInfo == TypeInfo.Unknown)
526 | {
527 | return StatusCodes.BadTypeMismatch;
528 | }
529 | if (typeInfo.BuiltInType == BuiltInType.Double)
530 | {
531 | double number = Convert.ToDouble(value);
532 | value = TypeInfo.Cast(number, typeInfo.BuiltInType);
533 | }
534 | return ServiceResult.Good;
535 | }
536 | catch (Exception)
537 | {
538 | return StatusCodes.BadTypeMismatch;
539 | }
540 | }
541 |
542 | ///
543 | /// 读取历史数据
544 | ///
545 | ///
546 | ///
547 | ///
548 | ///
549 | ///
550 | ///
551 | ///
552 | public override void HistoryRead(OperationContext context, HistoryReadDetails details, TimestampsToReturn timestampsToReturn, bool releaseContinuationPoints,
553 | IList nodesToRead, IList results, IList errors)
554 | {
555 | ReadProcessedDetails readDetail = details as ReadProcessedDetails;
556 | //假设查询历史数据 都是带上时间范围的
557 | if (readDetail == null || readDetail.StartTime == DateTime.MinValue || readDetail.EndTime == DateTime.MinValue)
558 | {
559 | errors[0] = StatusCodes.BadHistoryOperationUnsupported;
560 | return;
561 | }
562 | for (int ii = 0; ii < nodesToRead.Count; ii++)
563 | {
564 | int sss = readDetail.StartTime.Millisecond;
565 | double res = sss + DateTime.Now.Millisecond;
566 | //这里 返回的历史数据可以是多种数据类型 请根据实际的业务来选择
567 | Opc.Ua.KeyValuePair keyValue = new Opc.Ua.KeyValuePair()
568 | {
569 | Key = new QualifiedName(nodesToRead[ii].NodeId.Identifier.ToString()),
570 | Value = res
571 | };
572 | results[ii] = new HistoryReadResult()
573 | {
574 | StatusCode = StatusCodes.Good,
575 | HistoryData = new ExtensionObject(keyValue)
576 | };
577 | errors[ii] = StatusCodes.Good;
578 | //切记,如果你已处理完了读取历史数据的操作,请将Processed设为true,这样OPC-UA类库就知道你已经处理过了 不需要再进行检查了
579 | nodesToRead[ii].Processed = true;
580 | }
581 | }
582 | }
583 | }
584 |
--------------------------------------------------------------------------------
/Axiu.Opcua.Demo.Service/DiscoveryServer.cs:
--------------------------------------------------------------------------------
1 | using Opc.Ua;
2 | using Opc.Ua.Server;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.IO;
6 | using System.Linq;
7 | using System.Net;
8 | using System.Text;
9 | using System.Threading;
10 | using System.Threading.Tasks;
11 |
12 | namespace Axiu.Opcua.Demo.Service
13 | {
14 | public class DiscoveryServer : StandardServer, IDiscoveryServer
15 | {
16 | private ServerInternalData m_serverInternal;
17 | ///
18 | /// 清理掉线服务端定时器
19 | ///
20 | private static Timer m_timer = null;
21 | ///
22 | /// 已注册服务端列表
23 | ///
24 | List _serverTable = new List();
25 |
26 | ///
27 | /// 加载服务属性(这里采用直接写死,不用配置文件的方式)
28 | ///
29 | ///
30 | protected override ServerProperties LoadServerProperties()
31 | {
32 | ServerProperties properties = new ServerProperties();
33 |
34 | properties.ManufacturerName = "OPC Foundation";
35 | properties.ProductName = "CET Discovery Server";
36 | properties.ProductUri = "http://opcfoundation.org/Quickstart/ReferenceServer/v1.04";
37 | properties.SoftwareVersion = Utils.GetAssemblySoftwareVersion();
38 | properties.BuildNumber = Utils.GetAssemblyBuildNumber();
39 | properties.BuildDate = Utils.GetAssemblyTimestamp();
40 |
41 | return properties;
42 | }
43 |
44 | ///
45 | /// 获取终结点实例(此方法必须重写,Discovery服务端必须创建DiscoveryEndpoint终结点,而实时数据服务端必须创建SessionEndpoint终结点)
46 | ///
47 | ///
48 | ///
49 | protected override EndpointBase GetEndpointInstance(ServerBase server)
50 | {
51 | return new DiscoveryEndpoint(server);//SessionEndpoint
52 | }
53 |
54 | ///
55 | /// 启动服务
56 | ///
57 | ///
58 | protected override void StartApplication(ApplicationConfiguration configuration)
59 | {
60 | lock (m_lock)
61 | {
62 | try
63 | {
64 | // create the datastore for the instance.
65 | m_serverInternal = new ServerInternalData(
66 | ServerProperties,
67 | configuration,
68 | MessageContext,
69 | new CertificateValidator(),
70 | InstanceCertificate);
71 |
72 | // create the manager responsible for providing localized string resources.
73 | ResourceManager resourceManager = CreateResourceManager(m_serverInternal, configuration);
74 |
75 | // create the manager responsible for incoming requests.
76 | RequestManager requestManager = new RequestManager(m_serverInternal);
77 |
78 | // create the master node manager.
79 | MasterNodeManager masterNodeManager = new MasterNodeManager(m_serverInternal, configuration, null);
80 |
81 | // add the node manager to the datastore.
82 | m_serverInternal.SetNodeManager(masterNodeManager);
83 |
84 | // put the node manager into a state that allows it to be used by other objects.
85 | masterNodeManager.Startup();
86 |
87 | // create the manager responsible for handling events.
88 | EventManager eventManager = new EventManager(m_serverInternal, (uint)configuration.ServerConfiguration.MaxEventQueueSize);
89 |
90 | // creates the server object.
91 | m_serverInternal.CreateServerObject(
92 | eventManager,
93 | resourceManager,
94 | requestManager);
95 |
96 |
97 | // create the manager responsible for aggregates.
98 | m_serverInternal.AggregateManager = CreateAggregateManager(m_serverInternal, configuration);
99 |
100 | // start the session manager.
101 | SessionManager sessionManager = new SessionManager(m_serverInternal, configuration);
102 | sessionManager.Startup();
103 |
104 | // start the subscription manager.
105 | SubscriptionManager subscriptionManager = new SubscriptionManager(m_serverInternal, configuration);
106 | subscriptionManager.Startup();
107 |
108 | // add the session manager to the datastore.
109 | m_serverInternal.SetSessionManager(sessionManager, subscriptionManager);
110 |
111 | ServerError = null;
112 |
113 | // set the server status as running.
114 | SetServerState(ServerState.Running);
115 |
116 | // monitor the configuration file.
117 | if (!String.IsNullOrEmpty(configuration.SourceFilePath))
118 | {
119 | var m_configurationWatcher = new ConfigurationWatcher(configuration);
120 | m_configurationWatcher.Changed += new EventHandler(this.OnConfigurationChanged);
121 | }
122 |
123 | CertificateValidator.CertificateUpdate += OnCertificateUpdate;
124 | //60s后开始清理过期服务列表,此后每60s检查一次
125 | m_timer = new Timer(ClearNoliveServer, null, 60000, 60000);
126 | Console.WriteLine("Discovery服务已启动完成,请勿退出程序!!!");
127 | }
128 | catch (Exception e)
129 | {
130 | Utils.Trace(e, "Unexpected error starting application");
131 | m_serverInternal = null;
132 | ServiceResult error = ServiceResult.Create(e, StatusCodes.BadInternalError, "Unexpected error starting application");
133 | ServerError = error;
134 | throw new ServiceResultException(error);
135 | }
136 | }
137 | }
138 |
139 | ///
140 | /// 初始化服务监听(此方法可以不重写)
141 | ///
142 | ///
143 | ///
144 | ///
145 | ///
146 | protected override IList InitializeServiceHosts(
147 | ApplicationConfiguration configuration,
148 | out ApplicationDescription serverDescription,
149 | out EndpointDescriptionCollection endpoints)
150 | {
151 | serverDescription = null;
152 | endpoints = null;
153 |
154 | Dictionary hosts = new Dictionary();
155 |
156 | // ensure at least one security policy exists.
157 | if (configuration.ServerConfiguration.SecurityPolicies.Count == 0)
158 | {
159 | configuration.ServerConfiguration.SecurityPolicies.Add(new ServerSecurityPolicy());
160 | }
161 |
162 | // ensure at least one user token policy exists.
163 | if (configuration.ServerConfiguration.UserTokenPolicies.Count == 0)
164 | {
165 | UserTokenPolicy userTokenPolicy = new UserTokenPolicy();
166 |
167 | userTokenPolicy.TokenType = UserTokenType.Anonymous;
168 | userTokenPolicy.PolicyId = userTokenPolicy.TokenType.ToString();
169 |
170 | configuration.ServerConfiguration.UserTokenPolicies.Add(userTokenPolicy);
171 | }
172 |
173 | // set server description.
174 | serverDescription = new ApplicationDescription();
175 |
176 | serverDescription.ApplicationUri = configuration.ApplicationUri;
177 | serverDescription.ApplicationName = new LocalizedText("en-US", configuration.ApplicationName);
178 | serverDescription.ApplicationType = configuration.ApplicationType;
179 | serverDescription.ProductUri = configuration.ProductUri;
180 | serverDescription.DiscoveryUrls = GetDiscoveryUrls();
181 |
182 | endpoints = new EndpointDescriptionCollection();
183 | IList endpointsForHost = null;
184 |
185 | // create UA TCP host.
186 | endpointsForHost = CreateUaTcpServiceHost(
187 | hosts,
188 | configuration,
189 | configuration.ServerConfiguration.BaseAddresses,
190 | serverDescription,
191 | configuration.ServerConfiguration.SecurityPolicies);
192 |
193 | endpoints.InsertRange(0, endpointsForHost);
194 |
195 | // create HTTPS host.
196 | #if !NO_HTTPS
197 | endpointsForHost = CreateHttpsServiceHost(
198 | hosts,
199 | configuration,
200 | configuration.ServerConfiguration.BaseAddresses,
201 | serverDescription,
202 | configuration.ServerConfiguration.SecurityPolicies);
203 |
204 | endpoints.AddRange(endpointsForHost);
205 | #endif
206 | return new List(hosts.Values);
207 | }
208 |
209 | ///
210 | /// 发现服务(由客户端请求)
211 | ///
212 | ///
213 | ///
214 | ///
215 | ///
216 | ///
217 | ///
218 | public override ResponseHeader FindServers(
219 | RequestHeader requestHeader,
220 | string endpointUrl,
221 | StringCollection localeIds,
222 | StringCollection serverUris,
223 | out ApplicationDescriptionCollection servers)
224 | {
225 | servers = new ApplicationDescriptionCollection();
226 |
227 | ValidateRequest(requestHeader);
228 |
229 | Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + ":请求查找服务...");
230 | string hostName = Dns.GetHostName();
231 |
232 | lock (_serverTable)
233 | {
234 | foreach (var item in _serverTable)
235 | {
236 | StringCollection urls = new StringCollection();
237 | foreach (var url in item.DiscoveryUrls)
238 | {
239 | if (url.Contains("localhost"))
240 | {
241 | string str = url.Replace("localhost", hostName);
242 | urls.Add(str);
243 | }
244 | else
245 | {
246 | urls.Add(url);
247 | }
248 | }
249 |
250 | servers.Add(new ApplicationDescription()
251 | {
252 | ApplicationName = item.ServerNames.FirstOrDefault(),
253 | ApplicationType = item.ServerType,
254 | ApplicationUri = item.ServerUri,
255 | DiscoveryProfileUri = item.SemaphoreFilePath,
256 | DiscoveryUrls = urls,
257 | ProductUri = item.ProductUri,
258 | GatewayServerUri = item.GatewayServerUri
259 | });
260 | }
261 | }
262 |
263 | return CreateResponse(requestHeader, StatusCodes.Good);
264 | }
265 |
266 | ///
267 | /// 获得终结点列表
268 | ///
269 | ///
270 | ///
271 | ///
272 | ///
273 | ///
274 | ///
275 | public override ResponseHeader GetEndpoints(
276 | RequestHeader requestHeader,
277 | string endpointUrl,
278 | StringCollection localeIds,
279 | StringCollection profileUris,
280 | out EndpointDescriptionCollection endpoints)
281 | {
282 | endpoints = null;
283 |
284 | ValidateRequest(requestHeader);
285 |
286 | try
287 | {
288 | lock (m_lock)
289 | {
290 | // filter by profile.
291 | IList baseAddresses = FilterByProfile(profileUris, BaseAddresses);
292 |
293 | // get the descriptions.
294 | Uri parsedEndpointUrl = Utils.ParseUri(endpointUrl);
295 |
296 | if (parsedEndpointUrl != null)
297 | {
298 | baseAddresses = FilterByEndpointUrl(parsedEndpointUrl, baseAddresses);
299 | }
300 |
301 | // check if nothing to do.
302 | if (baseAddresses.Count != 0)
303 | {
304 | // localize the application name if requested.
305 | LocalizedText applicationName = this.ServerDescription.ApplicationName;
306 |
307 | if (localeIds != null && localeIds.Count > 0)
308 | {
309 | applicationName = m_serverInternal.ResourceManager.Translate(localeIds, applicationName);
310 | }
311 |
312 | // translate the application description.
313 | ApplicationDescription application = TranslateApplicationDescription(
314 | parsedEndpointUrl,
315 | base.ServerDescription,
316 | baseAddresses,
317 | applicationName);
318 |
319 | // translate the endpoint descriptions.
320 | endpoints = TranslateEndpointDescriptions(
321 | parsedEndpointUrl,
322 | baseAddresses,
323 | this.Endpoints,
324 | application);
325 | }
326 | }
327 | return CreateResponse(requestHeader, StatusCodes.Good);
328 | }
329 | catch (Exception ex)
330 | {
331 | Console.ForegroundColor = ConsoleColor.Red;
332 | Console.WriteLine("获取终结点列表GetEndpoints()触发异常:" + ex.Message);
333 | Console.ResetColor();
334 | }
335 | return CreateResponse(requestHeader, StatusCodes.BadUnexpectedError);
336 | }
337 |
338 | ///
339 | /// 验证请求是否有效
340 | ///
341 | ///
342 | protected override void ValidateRequest(RequestHeader requestHeader)
343 | {
344 | // check for server error.
345 | ServiceResult error = ServerError;
346 |
347 | if (ServiceResult.IsBad(error))
348 | {
349 | throw new ServiceResultException(error);
350 | }
351 |
352 | // check server state.
353 | ServerInternalData serverInternal = m_serverInternal;
354 |
355 | if (serverInternal == null || !serverInternal.IsRunning)
356 | {
357 | throw new ServiceResultException(StatusCodes.BadServerHalted);
358 | }
359 |
360 | if (requestHeader == null)
361 | {
362 | throw new ServiceResultException(StatusCodes.BadRequestHeaderInvalid);
363 | }
364 | }
365 |
366 | ///
367 | /// 发现网络中的服务端(主要为其他PC端获取本机的服务列表使用)
368 | ///
369 | ///
370 | ///
371 | ///
372 | ///
373 | ///
374 | ///
375 | ///
376 | public override ResponseHeader FindServersOnNetwork(
377 | RequestHeader requestHeader,
378 | uint startingRecordId,
379 | uint maxRecordsToReturn,
380 | StringCollection serverCapabilityFilter,
381 | out DateTime lastCounterResetTime,
382 | out ServerOnNetworkCollection servers)
383 | {
384 | lastCounterResetTime = DateTime.MinValue;
385 | servers = null;
386 |
387 | ValidateRequest(requestHeader);
388 |
389 | try
390 | {
391 | lock (_serverTable)
392 | {
393 | uint rId = 0;
394 | foreach (var item in _serverTable)
395 | {
396 | rId++;
397 | servers.Add(new ServerOnNetwork()
398 | {
399 | DiscoveryUrl = item.DiscoveryUrls.FirstOrDefault(),
400 | RecordId = rId,
401 | ServerName = item.ServerNames.FirstOrDefault()?.Text,
402 | ServerCapabilities = { "Realtime Data", "Events & Alarms" },
403 | });
404 | }
405 | }
406 |
407 | lastCounterResetTime = DateTime.Now;
408 | return CreateResponse(requestHeader, StatusCodes.Good);
409 | }
410 | catch (Exception ex)
411 | {
412 | Console.ForegroundColor = ConsoleColor.Red;
413 | Console.WriteLine("查找网络中的服务FindServersOnNetwork()时触发异常:" + ex.Message);
414 | Console.ResetColor();
415 | }
416 | return CreateResponse(requestHeader, StatusCodes.BadUnexpectedError);
417 | }
418 |
419 | ///
420 | /// 注册服务(每个在本机启动的服务端都会主动调用此方法进行注册)
421 | ///
422 | ///
423 | ///
424 | ///
425 | public virtual ResponseHeader RegisterServer(
426 | RequestHeader requestHeader,
427 | RegisteredServer server)
428 | {
429 | ValidateRequest(requestHeader);
430 |
431 | // Insert implementation.
432 | try
433 | {
434 | Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + ":服务注册:" + server.DiscoveryUrls.FirstOrDefault());
435 | RegisteredServerTable model = _serverTable.Where(d => d.ServerUri == server.ServerUri).FirstOrDefault();
436 | if (model != null)
437 | {
438 | model.LastRegistered = DateTime.Now;
439 | }
440 | else
441 | {
442 | model = new RegisteredServerTable()
443 | {
444 | DiscoveryUrls = server.DiscoveryUrls,
445 | GatewayServerUri = server.GatewayServerUri,
446 | IsOnline = server.IsOnline,
447 | LastRegistered = DateTime.Now,
448 | ProductUri = server.ProductUri,
449 | SemaphoreFilePath = server.SemaphoreFilePath,
450 | ServerNames = server.ServerNames,
451 | ServerType = server.ServerType,
452 | ServerUri = server.ServerUri
453 | };
454 | _serverTable.Add(model);
455 | }
456 | return CreateResponse(requestHeader, StatusCodes.Good);
457 | }
458 | catch (Exception ex)
459 | {
460 | Console.ForegroundColor = ConsoleColor.Red;
461 | Console.WriteLine("客户端调用RegisterServer()注册服务时触发异常:" + ex.Message);
462 | Console.ResetColor();
463 | }
464 | return CreateResponse(requestHeader, StatusCodes.BadUnexpectedError);
465 | }
466 |
467 | ///
468 | /// 注册服务(每个在本机启动的服务端都会主动调用此方法进行注册)
469 | ///
470 | ///
471 | ///
472 | ///
473 | ///
474 | ///
475 | ///
476 | public virtual ResponseHeader RegisterServer2(
477 | RequestHeader requestHeader,
478 | RegisteredServer server,
479 | ExtensionObjectCollection discoveryConfiguration,
480 | out StatusCodeCollection configurationResults,
481 | out DiagnosticInfoCollection diagnosticInfos)
482 | {
483 | configurationResults = null;
484 | diagnosticInfos = null;
485 |
486 | ValidateRequest(requestHeader);
487 |
488 | // Insert implementation.
489 | try
490 | {
491 | Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + ":服务注册:" + server.DiscoveryUrls.FirstOrDefault());
492 | RegisteredServerTable model = _serverTable.Where(d => d.ServerUri == server.ServerUri).FirstOrDefault();
493 | if (model != null)
494 | {
495 | model.LastRegistered = DateTime.Now;
496 | }
497 | else
498 | {
499 | model = new RegisteredServerTable()
500 | {
501 | DiscoveryUrls = server.DiscoveryUrls,
502 | GatewayServerUri = server.GatewayServerUri,
503 | IsOnline = server.IsOnline,
504 | LastRegistered = DateTime.Now,
505 | ProductUri = server.ProductUri,
506 | SemaphoreFilePath = server.SemaphoreFilePath,
507 | ServerNames = server.ServerNames,
508 | ServerType = server.ServerType,
509 | ServerUri = server.ServerUri
510 | };
511 | _serverTable.Add(model);
512 | }
513 | configurationResults = new StatusCodeCollection() { StatusCodes.Good };
514 | return CreateResponse(requestHeader, StatusCodes.Good);
515 | }
516 | catch (Exception ex)
517 | {
518 | Console.ForegroundColor = ConsoleColor.Red;
519 | Console.WriteLine("客户端调用RegisterServer2()注册服务时触发异常:" + ex.Message);
520 | Console.ResetColor();
521 | }
522 | return CreateResponse(requestHeader, StatusCodes.BadUnexpectedError);
523 | }
524 |
525 | ///
526 | /// 创建服务器使用的聚合管理器
527 | ///
528 | ///
529 | ///
530 | ///
531 | protected override AggregateManager CreateAggregateManager(IServerInternal server, ApplicationConfiguration configuration)
532 | {
533 | AggregateManager manager = new AggregateManager(server);
534 |
535 | manager.RegisterFactory(ObjectIds.AggregateFunction_Interpolative, BrowseNames.AggregateFunction_Interpolative, Aggregators.CreateStandardCalculator);
536 | manager.RegisterFactory(ObjectIds.AggregateFunction_Average, BrowseNames.AggregateFunction_Average, Aggregators.CreateStandardCalculator);
537 | manager.RegisterFactory(ObjectIds.AggregateFunction_TimeAverage, BrowseNames.AggregateFunction_TimeAverage, Aggregators.CreateStandardCalculator);
538 | manager.RegisterFactory(ObjectIds.AggregateFunction_TimeAverage2, BrowseNames.AggregateFunction_TimeAverage2, Aggregators.CreateStandardCalculator);
539 | manager.RegisterFactory(ObjectIds.AggregateFunction_Total, BrowseNames.AggregateFunction_Total, Aggregators.CreateStandardCalculator);
540 | manager.RegisterFactory(ObjectIds.AggregateFunction_Total2, BrowseNames.AggregateFunction_Total2, Aggregators.CreateStandardCalculator);
541 |
542 | manager.RegisterFactory(ObjectIds.AggregateFunction_Minimum, BrowseNames.AggregateFunction_Minimum, Aggregators.CreateStandardCalculator);
543 | manager.RegisterFactory(ObjectIds.AggregateFunction_Maximum, BrowseNames.AggregateFunction_Maximum, Aggregators.CreateStandardCalculator);
544 | manager.RegisterFactory(ObjectIds.AggregateFunction_MinimumActualTime, BrowseNames.AggregateFunction_MinimumActualTime, Aggregators.CreateStandardCalculator);
545 | manager.RegisterFactory(ObjectIds.AggregateFunction_MaximumActualTime, BrowseNames.AggregateFunction_MaximumActualTime, Aggregators.CreateStandardCalculator);
546 | manager.RegisterFactory(ObjectIds.AggregateFunction_Range, BrowseNames.AggregateFunction_Range, Aggregators.CreateStandardCalculator);
547 | manager.RegisterFactory(ObjectIds.AggregateFunction_Minimum2, BrowseNames.AggregateFunction_Minimum2, Aggregators.CreateStandardCalculator);
548 | manager.RegisterFactory(ObjectIds.AggregateFunction_Maximum2, BrowseNames.AggregateFunction_Maximum2, Aggregators.CreateStandardCalculator);
549 | manager.RegisterFactory(ObjectIds.AggregateFunction_MinimumActualTime2, BrowseNames.AggregateFunction_MinimumActualTime2, Aggregators.CreateStandardCalculator);
550 | manager.RegisterFactory(ObjectIds.AggregateFunction_MaximumActualTime2, BrowseNames.AggregateFunction_MaximumActualTime2, Aggregators.CreateStandardCalculator);
551 | manager.RegisterFactory(ObjectIds.AggregateFunction_Range2, BrowseNames.AggregateFunction_Range2, Aggregators.CreateStandardCalculator);
552 |
553 | manager.RegisterFactory(ObjectIds.AggregateFunction_Count, BrowseNames.AggregateFunction_Count, Aggregators.CreateStandardCalculator);
554 | manager.RegisterFactory(ObjectIds.AggregateFunction_AnnotationCount, BrowseNames.AggregateFunction_AnnotationCount, Aggregators.CreateStandardCalculator);
555 | manager.RegisterFactory(ObjectIds.AggregateFunction_DurationInStateZero, BrowseNames.AggregateFunction_DurationInStateZero, Aggregators.CreateStandardCalculator);
556 | manager.RegisterFactory(ObjectIds.AggregateFunction_DurationInStateNonZero, BrowseNames.AggregateFunction_DurationInStateNonZero, Aggregators.CreateStandardCalculator);
557 | manager.RegisterFactory(ObjectIds.AggregateFunction_NumberOfTransitions, BrowseNames.AggregateFunction_NumberOfTransitions, Aggregators.CreateStandardCalculator);
558 |
559 | manager.RegisterFactory(ObjectIds.AggregateFunction_Start, BrowseNames.AggregateFunction_Start, Aggregators.CreateStandardCalculator);
560 | manager.RegisterFactory(ObjectIds.AggregateFunction_End, BrowseNames.AggregateFunction_End, Aggregators.CreateStandardCalculator);
561 | manager.RegisterFactory(ObjectIds.AggregateFunction_Delta, BrowseNames.AggregateFunction_Delta, Aggregators.CreateStandardCalculator);
562 | manager.RegisterFactory(ObjectIds.AggregateFunction_StartBound, BrowseNames.AggregateFunction_StartBound, Aggregators.CreateStandardCalculator);
563 | manager.RegisterFactory(ObjectIds.AggregateFunction_EndBound, BrowseNames.AggregateFunction_EndBound, Aggregators.CreateStandardCalculator);
564 | manager.RegisterFactory(ObjectIds.AggregateFunction_DeltaBounds, BrowseNames.AggregateFunction_DeltaBounds, Aggregators.CreateStandardCalculator);
565 |
566 | manager.RegisterFactory(ObjectIds.AggregateFunction_DurationGood, BrowseNames.AggregateFunction_DurationGood, Aggregators.CreateStandardCalculator);
567 | manager.RegisterFactory(ObjectIds.AggregateFunction_DurationBad, BrowseNames.AggregateFunction_DurationBad, Aggregators.CreateStandardCalculator);
568 | manager.RegisterFactory(ObjectIds.AggregateFunction_PercentGood, BrowseNames.AggregateFunction_PercentGood, Aggregators.CreateStandardCalculator);
569 | manager.RegisterFactory(ObjectIds.AggregateFunction_PercentBad, BrowseNames.AggregateFunction_PercentBad, Aggregators.CreateStandardCalculator);
570 | manager.RegisterFactory(ObjectIds.AggregateFunction_WorstQuality, BrowseNames.AggregateFunction_WorstQuality, Aggregators.CreateStandardCalculator);
571 | manager.RegisterFactory(ObjectIds.AggregateFunction_WorstQuality2, BrowseNames.AggregateFunction_WorstQuality2, Aggregators.CreateStandardCalculator);
572 |
573 | manager.RegisterFactory(ObjectIds.AggregateFunction_StandardDeviationPopulation, BrowseNames.AggregateFunction_StandardDeviationPopulation, Aggregators.CreateStandardCalculator);
574 | manager.RegisterFactory(ObjectIds.AggregateFunction_VariancePopulation, BrowseNames.AggregateFunction_VariancePopulation, Aggregators.CreateStandardCalculator);
575 | manager.RegisterFactory(ObjectIds.AggregateFunction_StandardDeviationSample, BrowseNames.AggregateFunction_StandardDeviationSample, Aggregators.CreateStandardCalculator);
576 | manager.RegisterFactory(ObjectIds.AggregateFunction_VarianceSample, BrowseNames.AggregateFunction_VarianceSample, Aggregators.CreateStandardCalculator);
577 |
578 | return manager;
579 | }
580 |
581 | ///
582 | /// 为服务器创建资源管理器
583 | ///
584 | ///
585 | ///
586 | ///
587 | protected override ResourceManager CreateResourceManager(IServerInternal server, ApplicationConfiguration configuration)
588 | {
589 | ResourceManager resourceManager = new ResourceManager(server, configuration);
590 |
591 | System.Reflection.FieldInfo[] fields = typeof(StatusCodes).GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
592 |
593 | foreach (System.Reflection.FieldInfo field in fields)
594 | {
595 | uint? id = field.GetValue(typeof(StatusCodes)) as uint?;
596 |
597 | if (id != null)
598 | {
599 | resourceManager.Add(id.Value, "en-US", field.Name);
600 | }
601 | }
602 |
603 | return resourceManager;
604 | }
605 |
606 | ///
607 | /// 设置服务状态
608 | ///
609 | ///
610 | protected override void SetServerState(ServerState state)
611 | {
612 | lock (m_lock)
613 | {
614 | if (ServiceResult.IsBad(ServerError))
615 | {
616 | throw new ServiceResultException(ServerError);
617 | }
618 |
619 | if (m_serverInternal == null)
620 | {
621 | throw new ServiceResultException(StatusCodes.BadServerHalted);
622 | }
623 |
624 | m_serverInternal.CurrentState = state;
625 | }
626 | }
627 |
628 | ///
629 | /// 配置变更事件
630 | ///
631 | ///
632 | ///
633 | protected override async void OnConfigurationChanged(object sender, ConfigurationWatcherEventArgs args)
634 | {
635 | try
636 | {
637 | ApplicationConfiguration configuration = await ApplicationConfiguration.Load(
638 | new FileInfo(args.FilePath),
639 | Configuration.ApplicationType,
640 | Configuration.GetType());
641 |
642 | OnUpdateConfiguration(configuration);
643 | }
644 | catch (Exception e)
645 | {
646 | Utils.Trace(e, "Could not load updated configuration file from: {0}", args);
647 | }
648 | }
649 |
650 | ///
651 | /// 清理掉线服务
652 | ///
653 | ///
654 | private void ClearNoliveServer(object obj)
655 | {
656 | try
657 | {
658 | var tmpList = _serverTable.Where(d => d.LastRegistered < DateTime.Now.AddMinutes(-1) || !d.IsOnline).ToList();
659 | if (tmpList.Count > 0)
660 | {
661 | lock (_serverTable)
662 | {
663 | foreach (var item in tmpList)
664 | {
665 | Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + ":清理服务:" + item.DiscoveryUrls.FirstOrDefault());
666 | _serverTable.Remove(item);
667 | }
668 | }
669 | }
670 | }
671 | catch (Exception ex)
672 | {
673 | Console.ForegroundColor = ConsoleColor.Red;
674 | Console.WriteLine("清理掉线服务ClearNoliveServer()时触发异常:" + ex.Message);
675 | Console.ResetColor();
676 | }
677 | }
678 | }
679 | }
680 |
--------------------------------------------------------------------------------