最近接到了坑爹的任务,要做个opc ua服务器,让wincc能连接上,opc ua这个东西从来都没有听过,就是因为懂c#就要写了,确实有点尴尬,入门就去看了opc,ua之前的版本,发现很复杂,后来发现了opc ua的存在,解决了很多问题,而且很好有,推荐大家使用,不要再搞opc这坑爹东西。毕竟防火墙就搞傻人了。而且在win10上也很难搞的。opc ua就解决了这些问题。下载了官方github的源码。直接用vs2017运行,新版的代码在vs2015上是运行不了的。
1、修改上的坑
着手改动显示的节点,这个改动是在UnderlyingSystem.cs这个文件中,打开文件时就能看到s_BlockPathDatabase和s_BlockDatabase这两个string的数组,从数组的内容就知道是节点和节点的内容了,在看看下面的FindSegments(),FindSegment(),findBlocks(),findBlock(),这几个函数的写法,其实就是解析这两个数组,如果是segment就继续返回segment,如果是block就显示内容,这里有两个概念,segment,block,我的理解是,segment是路径的而且,block才是真正的容易,所以如果要做一级级的路径,可以参考这个,我是直接在一级segment下就是block,所以不会这么多级的。
在这里要注意一个事情,在DataAccessNodeManager.cs这个文件中有一个CreateAddressSpace(IDictionary
这之后就很简单了,改动findBlock里,返回直接的block就好了,如果想添加segment,就在s_BlockPathDatabase这个数组里面添加自己的segment就好了,但要记得在最后面加上‘/’斜杆,这样findSegment才能找到的。
2、部署的问题
opc ua为了保障安全(这个肯定是要的),就搞了个证书验证的过程,这个使用起来就有点尴尬了。在局域网的两台机器上连接的话,会报错:The domain x is not listed in the server certificate.这个在源码里面看看是什么原因。
private static void CheckCertificateDomain(ConfiguredEndpoint endpoint)
{
bool domainFound = false;
X509Certificate2 serverCertificate = new X509Certificate2(endpoint.Description.ServerCertificate);
// check the certificate domains.
IList domains = Utils.GetDomainsFromCertficate(serverCertificate);
if (domains != null)
{
string hostname;
string dnsHostName = hostname = endpoint.EndpointUrl.DnsSafeHost;
bool isLocalHost = false;
if (endpoint.EndpointUrl.HostNameType == UriHostNameType.Dns)
{
if (dnsHostName.ToLowerInvariant() == "localhost")
{
isLocalHost = true;
}
else
{ // strip domain names from hostname
hostname = dnsHostName.Split('.')[0];
}
}
else
{ // dnsHostname is a IPv4 or IPv6 address
// normalize ip addresses, cert parser returns normalized addresses
hostname = Utils.NormalizedIPAddress(dnsHostName);
if (hostname == "127.0.0.1" || hostname == "::1")
{
isLocalHost = true;
}
}
if (isLocalHost)
{
dnsHostName = Utils.GetFullQualifiedDomainName();
hostname = Utils.GetHostName();
}
for (int ii = 0; ii < domains.Count; ii++)
{
if (String.Compare(hostname, domains[ii], StringComparison.OrdinalIgnoreCase) == 0 ||
String.Compare(dnsHostName, domains[ii], StringComparison.OrdinalIgnoreCase) == 0)
{
domainFound = true;
break;
}
}
}
if (!domainFound)
{
string message = Utils.Format(
"The domain '{0}' is not listed in the server certificate.",
endpoint.EndpointUrl.DnsSafeHost);
throw new ServiceResultException(StatusCodes.BadCertificateHostNameInvalid, message);
}
}
看到domainFound是去判断连接的地址是不是在证书里面。domains这个列表是怎么得到的。
public static IList GetDomainsFromCertficate(X509Certificate2 certificate)
{
List dnsNames = new List();
// extracts the domain from the subject name.
List fields = Utils.ParseDistinguishedName(certificate.Subject);
StringBuilder builder = new StringBuilder();
for (int ii = 0; ii < fields.Count; ii++)
{
if (fields[ii].StartsWith("DC="))
{
if (builder.Length > 0)
{
builder.Append('.');
}
builder.Append(fields[ii].Substring(3));
}
}
if (builder.Length > 0)
{
dnsNames.Add(builder.ToString().ToUpperInvariant());
}
// extract the alternate domains from the subject alternate name extension.
X509SubjectAltNameExtension alternateName = null;
foreach (X509Extension extension in certificate.Extensions)
{
if (extension.Oid.Value == X509SubjectAltNameExtension.SubjectAltNameOid || extension.Oid.Value == X509SubjectAltNameExtension.SubjectAltName2Oid)
{
alternateName = new X509SubjectAltNameExtension(extension, extension.Critical);
break;
}
}
if (alternateName != null)
{
for (int ii = 0; ii < alternateName.DomainNames.Count; ii++)
{
string hostname = alternateName.DomainNames[ii];
// do not add duplicates to the list.
bool found = false;
for (int jj = 0; jj < dnsNames.Count; jj++)
{
if (String.Compare(dnsNames[jj], hostname, StringComparison.OrdinalIgnoreCase) == 0)
{
found = true;
break;
}
}
if (!found)
{
dnsNames.Add(hostname.ToUpperInvariant());
}
}
for (int ii = 0; ii < alternateName.IPAddresses.Count; ii++)
{
string ipAddress = alternateName.IPAddresses[ii];
if (!dnsNames.Contains(ipAddress))
{
dnsNames.Add(ipAddress);
}
}
}
// return the list.
return dnsNames;
}
在看到有个一判断字符串的开头是否等于“DC=”这个就要看回配置文件了,在项目里面是会有一个DataAccessServer.Config.xml的配置文件,这个这个文件里面就会有一段写DC=localhost,那么取出了DC的内容,那么改掉了DC的内容不就好了,这个想法非常的好,后面的就是从证书的额外信息里面提取出来匹配,比较。使用X509证书是可以携带额外信息的。注意:DC字段只能有一个,因为多了会用‘.’来分隔而已。
事情并没有这么简单,虽然可以连接过去了,但会提示说有安全问题,询问你是否连接的,就算了你点了确定,还会报另外一个错误。服务器不存在这个证书,那么又怎么解决。在配置文件里面有写到几个证书的存放地方,还会有对应的作用解析。看完作用之后C:\ProgramData\OPC Foundation\CertificateStores\UA Applications\certs这个路径的证书应该是服务器验证客户端证书的存放地方(在配置文件里面是写%CommonApplicationData%的,其实就是c盘的ProgramData文件夹),那么令一台机的证书又怎么来,这个的话很容易搞的,就是在对应的机器上运行服务器端,这样在对应目录下就会有自动生成的证书,把证书复制过去服务器上,就好了,证书是不会覆盖的,虽然大家都叫客户端证书。
到了这一时刻,你就可以在两台机器上相互连接了。