OPC UA的源码使用

        最近接到了坑爹的任务,要做个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> externalReferences)的方法这个方法,在建立连接的时候会调用,有一个比较奇怪的现象,就是createAddressSpace()之后,再调用AddReference()添加节点是不会刷新的,这个可能是我的使用方法不对。

       这之后就很简单了,改动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文件夹),那么令一台机的证书又怎么来,这个的话很容易搞的,就是在对应的机器上运行服务器端,这样在对应目录下就会有自动生成的证书,把证书复制过去服务器上,就好了,证书是不会覆盖的,虽然大家都叫客户端证书。

        到了这一时刻,你就可以在两台机器上相互连接了。

你可能感兴趣的:(随笔)