使用C#開發OPC UA伺服器

来源:https://www.cnblogs.com/yada/p/18257593
-Advertisement-
Play Games

OPC基金會提供了OPC UA .NET標準庫以及示常式序,但官方文檔過於簡單,光看官方文檔和示常式序很難弄懂OPC UA .NET標準庫怎麼用,花了不少時間摸索才略微弄懂如何使用,以下記錄如何從一個控制台程式開發一個OPC UA伺服器。 安裝Nuget包 安裝OPCFoundation.NetSt ...


OPC基金會提供了OPC UA .NET標準庫以及示常式序,但官方文檔過於簡單,光看官方文檔和示常式序很難弄懂OPC UA .NET標準庫怎麼用,花了不少時間摸索才略微弄懂如何使用,以下記錄如何從一個控制台程式開發一個OPC UA伺服器。

安裝Nuget包

安裝OPCFoundation.NetStandard.Opc.Ua
image

主程式

修改Program.cs代碼如下:

using Opc.Ua;
using Opc.Ua.Configuration;
using Opc.Ua.Server;

namespace SampleOpcUaServer
{
    internal class Program
    {
        static void Main(string[] args)
        {
            // 啟動OPC UA伺服器
            ApplicationInstance application = new ApplicationInstance();
            application.ConfigSectionName = "OpcUaServer";
            application.LoadApplicationConfiguration(false).Wait();
            application.CheckApplicationInstanceCertificate(false, 0).Wait();

            var server = new StandardServer();
            var nodeManagerFactory = new NodeManagerFactory();
            server.AddNodeManager(nodeManagerFactory);
            application.Start(server).Wait();

            // 模擬數據
            var nodeManager = nodeManagerFactory.NodeManager;
            var simulationTimer = new System.Timers.Timer(1000);
            var random = new Random();
            simulationTimer.Elapsed += (sender, EventArgs) =>
            {
                nodeManager?.UpdateValue("ns=2;s=Root_Test", random.NextInt64());
            };
            simulationTimer.Start();

            // 輸出OPC UA Endpoint
            Console.WriteLine("Endpoints:");
            foreach (var endpoint in server.GetEndpoints().DistinctBy(x => x.EndpointUrl))
            {
                Console.WriteLine(endpoint.EndpointUrl);
            }

            Console.WriteLine("按Enter添加新變數");
            Console.ReadLine();

            // 添加新變數
            nodeManager?.AddVariable("ns=2;s=Root", null, "Test2", (int)BuiltInType.Int16, ValueRanks.Scalar);
            Console.WriteLine("已添加變數");
            Console.ReadLine();
        }
    }
}

上述代碼中:

  • ApplicationInstance是OPC UA標準庫中用於配置並承載OPC UA Server和檢查證書的類。
  • application.ConfigSectionName指定了配置文件的名稱,配置文件是xml文件,將會在程式文件夾查找名為OpcUaServer.Config.xml的配置文件。配置文件內容見後文。
  • application.LoadApplicationConfiguration載入前面指定的配置文件。如果不想使用配置文件,也可通過代碼給application.ApplicationConfiguration賦值。
  • StandardServerReverseConnectServer兩種作為OPC UA伺服器的類,ReverseConnectServer派生於StandardServer,這兩種類的區別未深入研究,用StandardServer可滿足基本的需求。
  • OPC UA的地址空間由節點組成,簡單理解節點就是提供給OPC UA客戶端訪問的變數和文件夾。通過server.AddNodeManager方法添加節點管理工廠類,NodeManagerFactory類定義見後文。
  • 調用application.Start(server)方法後,OPC UA Server就會開始運行,並不會阻塞代碼,為了保持在控制台程式中運行,所以使用Console.ReadLine()阻塞程式。
  • nodeManager?.UpdateValue是自定義的更新OPC UA地址空間中變數值的方法。
  • nodeManager?.AddVariable在此演示動態添加一個新的變數。

OPC UA配置文件

新建OpcUaServer.Config.xml文件。

image

在屬性中設為“始終賦值”。

image

內容如下:

<?xml version="1.0" encoding="utf-8"?>
<ApplicationConfiguration
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:ua="http://opcfoundation.org/UA/2008/02/Types.xsd"
  xmlns="http://opcfoundation.org/UA/SDK/Configuration.xsd"
>
	<ApplicationName>Sample OPC UA Server</ApplicationName>
	<ApplicationUri>urn:localhost:UA:OpcUaServer</ApplicationUri>
	<ProductUri>uri:opcfoundation.org:OpcUaServer</ProductUri>
	<ApplicationType>Server_0</ApplicationType>

	<SecurityConfiguration>

		<!-- Where the application instance certificate is stored (MachineDefault) -->
		<ApplicationCertificate>
			<StoreType>Directory</StoreType>
			<StorePath>%CommonApplicationData%\OPC Foundation\pki\own</StorePath>
			<SubjectName>CN=Sample Opc Ua Server, C=US, S=Arizona, O=SomeCompany, DC=localhost</SubjectName>
		</ApplicationCertificate>

		<!-- Where the issuer certificate are stored (certificate authorities) -->
		<TrustedIssuerCertificates>
			<StoreType>Directory</StoreType>
			<StorePath>%CommonApplicationData%\OPC Foundation\pki\issuer</StorePath>
		</TrustedIssuerCertificates>

		<!-- Where the trust list is stored -->
		<TrustedPeerCertificates>
			<StoreType>Directory</StoreType>
			<StorePath>%CommonApplicationData%\OPC Foundation\pki\trusted</StorePath>
		</TrustedPeerCertificates>

		<!-- The directory used to store invalid certficates for later review by the administrator. -->
		<RejectedCertificateStore>
			<StoreType>Directory</StoreType>
			<StorePath>%CommonApplicationData%\OPC Foundation\pki\rejected</StorePath>
		</RejectedCertificateStore>
	</SecurityConfiguration>

	<TransportConfigurations></TransportConfigurations>

	<TransportQuotas>
		<OperationTimeout>600000</OperationTimeout>
		<MaxStringLength>1048576</MaxStringLength>
		<MaxByteStringLength>1048576</MaxByteStringLength>
		<MaxArrayLength>65535</MaxArrayLength>
		<MaxMessageSize>4194304</MaxMessageSize>
		<MaxBufferSize>65535</MaxBufferSize>
		<ChannelLifetime>300000</ChannelLifetime>
		<SecurityTokenLifetime>3600000</SecurityTokenLifetime>
	</TransportQuotas>
	<ServerConfiguration>
		<BaseAddresses>
			<ua:String>https://localhost:62545/OpcUaServer/</ua:String>
			<ua:String>opc.tcp://localhost:62546/OpcUaServer</ua:String>
		</BaseAddresses>
		<SecurityPolicies>
			<ServerSecurityPolicy>
				<SecurityMode>SignAndEncrypt_3</SecurityMode>
				<SecurityPolicyUri>http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256</SecurityPolicyUri>
			</ServerSecurityPolicy>
			<ServerSecurityPolicy>
				<SecurityMode>None_1</SecurityMode>
				<SecurityPolicyUri>http://opcfoundation.org/UA/SecurityPolicy#None</SecurityPolicyUri>
			</ServerSecurityPolicy>
			<ServerSecurityPolicy>
				<SecurityMode>Sign_2</SecurityMode>
				<SecurityPolicyUri></SecurityPolicyUri>
			</ServerSecurityPolicy>
			<ServerSecurityPolicy>
				<SecurityMode>SignAndEncrypt_3</SecurityMode>
				<SecurityPolicyUri></SecurityPolicyUri>
			</ServerSecurityPolicy>
		</SecurityPolicies>
		<UserTokenPolicies>
			<ua:UserTokenPolicy>
				<ua:TokenType>Anonymous_0</ua:TokenType>
			</ua:UserTokenPolicy>
			<ua:UserTokenPolicy>
				<ua:TokenType>UserName_1</ua:TokenType>
			</ua:UserTokenPolicy>
			<ua:UserTokenPolicy>
				<ua:TokenType>Certificate_2</ua:TokenType>
			</ua:UserTokenPolicy>
			<!--
      <ua:UserTokenPolicy>
        <ua:TokenType>IssuedToken_3</ua:TokenType>
        <ua:IssuedTokenType>urn:oasis:names:tc:SAML:1.0:assertion:Assertion</ua:IssuedTokenType>
      </ua:UserTokenPolicy>
      -->
		</UserTokenPolicies>
		<DiagnosticsEnabled>false</DiagnosticsEnabled>
		<MaxSessionCount>100</MaxSessionCount>
		<MinSessionTimeout>10000</MinSessionTimeout>
		<MaxSessionTimeout>3600000</MaxSessionTimeout>
		<MaxBrowseContinuationPoints>10</MaxBrowseContinuationPoints>
		<MaxQueryContinuationPoints>10</MaxQueryContinuationPoints>
		<MaxHistoryContinuationPoints>100</MaxHistoryContinuationPoints>
		<MaxRequestAge>600000</MaxRequestAge>
		<MinPublishingInterval>100</MinPublishingInterval>
		<MaxPublishingInterval>3600000</MaxPublishingInterval>
		<PublishingResolution>50</PublishingResolution>
		<MaxSubscriptionLifetime>3600000</MaxSubscriptionLifetime>
		<MaxMessageQueueSize>10</MaxMessageQueueSize>
		<MaxNotificationQueueSize>100</MaxNotificationQueueSize>
		<MaxNotificationsPerPublish>1000</MaxNotificationsPerPublish>
		<MinMetadataSamplingInterval>1000</MinMetadataSamplingInterval>
		<AvailableSamplingRates>
			<SamplingRateGroup>
				<Start>5</Start>
				<Increment>5</Increment>
				<Count>20</Count>
			</SamplingRateGroup>
			<SamplingRateGroup>
				<Start>100</Start>
				<Increment>100</Increment>
				<Count>4</Count>
			</SamplingRateGroup>
			<SamplingRateGroup>
				<Start>500</Start>
				<Increment>250</Increment>
				<Count>2</Count>
			</SamplingRateGroup>
			<SamplingRateGroup>
				<Start>1000</Start>
				<Increment>500</Increment>
				<Count>20</Count>
			</SamplingRateGroup>
		</AvailableSamplingRates>
		<MaxRegistrationInterval>30000</MaxRegistrationInterval>
		<NodeManagerSaveFile>OpcUaServer.nodes.xml</NodeManagerSaveFile>
	</ServerConfiguration>

	<TraceConfiguration>
		<OutputFilePath>Logs\SampleOpcUaServer.log</OutputFilePath>
		<DeleteOnLoad>true</DeleteOnLoad>
		<!-- Show Only Errors -->
		<!-- <TraceMasks>1</TraceMasks> -->
		<!-- Show Only Security and Errors -->
		<!-- <TraceMasks>513</TraceMasks> -->
		<!-- Show Only Security, Errors and Trace -->
		<TraceMasks>515</TraceMasks>
		<!-- Show Only Security, COM Calls, Errors and Trace -->
		<!-- <TraceMasks>771</TraceMasks> -->
		<!-- Show Only Security, Service Calls, Errors and Trace -->
		<!-- <TraceMasks>523</TraceMasks> -->
		<!-- Show Only Security, ServiceResultExceptions, Errors and Trace -->
		<!-- <TraceMasks>519</TraceMasks> -->
	</TraceConfiguration>

</ApplicationConfiguration>

需要關註的內容有:

  • ApplicationName:在通過OPC UA工具連接此伺服器時,顯示的伺服器名稱就是該值。

  • ApplicationType:應用類型,可用的值有:

    • Server_0:伺服器
    • Client_1:客戶端
    • ClientAndServer_2:客戶機和伺服器
    • DisconveryServer_3:發現伺服器。發現伺服器用於註冊OPC UA伺服器,然後提供OPC UA客戶端搜索到伺服器。
  • SecurityConfiguration:該節點中指定了OPC UA的證書存儲路徑,一般保持預設,不需修改。

  • ServerConfiguration.BaseAddresses:該節點指定OPC UA伺服器的url地址。

  • ServerConfiguration.SecurityPolicies:該節點配置允許的伺服器安全策略,配置通訊是否要簽名和加密。

  • ServerConfiguration.UserTokenPolicies:該節點配置允許的用戶Token策略,例如是否允許匿名訪問。

  • AvailableSamplingRates:配置支持的變數採樣率。

  • TraceConfiguration:配置OPC UA伺服器的日誌記錄,設定日誌記錄路徑,配置的路徑是在系統臨時文件夾下的路徑,日誌文件的完整路徑是在%TEMP%\Logs\SampleOpcUaServer.log

NodeManagerFactory

新建NodeManagerFactory類,OPC UA server將調用該類的Create方法創建INodeManager實現類,而INodeManager實現類用於管理OPC UA地址空間。內容如下:

using Opc.Ua;
using Opc.Ua.Server;

namespace SampleOpcUaServer
{
    internal class NodeManagerFactory : INodeManagerFactory
    {
        public NodeManager? NodeManager { get; private set; }
        public StringCollection NamespacesUris => new StringCollection() { "http://opcfoundation.org/OpcUaServer" };

        public INodeManager Create(IServerInternal server, ApplicationConfiguration configuration)
        {
            if (NodeManager != null)
                return NodeManager;

            NodeManager = new NodeManager(server, configuration, NamespacesUris.ToArray());
            return NodeManager;
        }
    }
}
  • 實現INodeManagerFactory介面,需實現NamespacesUris屬性和Create方法。
  • NodeManager類是自定義的類,定義見後文。
  • 為了獲取Create方法返回的NodeManager類,定義了NodeManager屬性。

NodeManager

新建NodeManager類:

using Opc.Ua;
using Opc.Ua.Server;

namespace SampleOpcUaServer
{
    internal class NodeManager : CustomNodeManager2
    {
        public NodeManager(IServerInternal server, params string[] namespaceUris)
            : base(server, namespaceUris)
        {
        }

        public NodeManager(IServerInternal server, ApplicationConfiguration configuration, params string[] namespaceUris)
            : base(server, configuration, namespaceUris)
        {
        }

        protected override NodeStateCollection LoadPredefinedNodes(ISystemContext context)
        {
            FolderState root = CreateFolder(null, null, "Root");
            root.AddReference(ReferenceTypes.Organizes, true, ObjectIds.ObjectsFolder); // 將節點添加到伺服器根節點
            root.EventNotifier = EventNotifiers.SubscribeToEvents;
            AddRootNotifier(root);

            CreateVariable(root, null, "Test", BuiltInType.Int64, ValueRanks.Scalar);

            return new NodeStateCollection(new List<NodeState> { root });
        }

        protected virtual FolderState CreateFolder(NodeState? parent, string? path, string name)
        {
            if (string.IsNullOrWhiteSpace(path))
                path = parent?.NodeId.Identifier is string id ? id + "_" + name : name;

            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(path, NamespaceIndex);
            folder.DisplayName = new LocalizedText("en", name);
            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)
        {
            if (string.IsNullOrWhiteSpace(path))
                path = parent?.NodeId.Identifier is string id ? id + "_" + name : name;

            BaseDataVariableState variable = new BaseDataVariableState(parent);
            variable.SymbolicName = name;
            variable.ReferenceTypeId = ReferenceTypes.Organizes;
            variable.TypeDefinitionId = VariableTypeIds.BaseDataVariableType;
            variable.NodeId = new NodeId(path, NamespaceIndex);
            variable.BrowseName = new QualifiedName(path, NamespaceIndex);
            variable.DisplayName = new LocalizedText("en", name);
            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 NodeId AddFolder(NodeId parentId, string? path, string name)
        {
            var node = Find(parentId);
            var newNode = CreateFolder(node, path, name);
            AddPredefinedNode(SystemContext, node);
            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)
        {
            var node = Find(parentId);
            var newNode = CreateVariable(node, path, name, dataType, valueRank);
            AddPredefinedNode(SystemContext, node);
            return newNode.NodeId;
        }
    }
}

上述代碼中:

  • 需繼承CustomNodeManager2,這是OPC UA標準庫中提供的類。
  • 重寫LoadPredefinedNodes方法,在該方法中配置預定義節點。其中創建了一個Root文件夾,Root文件夾中添加了Test變數。
  • root.AddReference(ReferenceTypes.Organizes, true, ObjectIds.ObjectsFolder)該語句將節點添加到OPC UA伺服器根節點,如果不使用該語句,可在Server節點下看到添加的節點。
  • CreateFolder是定義的方法,用於簡化創建文件夾節點。
  • CreateVariable是自定義的方法,用於簡化創建變數節點。
  • UpdateValue是用於更新變數節點值的方法。其中修改值後,需調用ClearChangeMasks方法,才能通知客戶端更新值。
  • AddFolder用於啟動伺服器後添加新的文件夾。
  • AddVariable用於啟動伺服器後添加新的變數。

測試伺服器

比較好用的測試工具有:

  • UaExpert:Unified Automation公司提供的測試工具,需安裝,能用於連接OPC UA。

  • OpcExpert:opcti公司提供的免費測試工具,綠色版,能連接OPC和OPC UA。

以下用OpcExpert測試。

瀏覽本地電腦可發現OPC UA伺服器,可看到添加的Root節點和Test變數,Test變數的值會每秒更新。

image

源碼地址:https://github.com/Yada-Yang/SampleOpcUaServer

本文來自博客園,作者:星墨,轉載請註明原文鏈接:https://www.cnblogs.com/yada/p/18257593


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言 之前已經分享過幾篇關於中台項目框架的文章,相關介紹就不再贅述 所謂工欲善其事必先利其器,一個項目擁有一個代碼生成器是很有必要的,能夠大大的節省時間,減少手誤,提供開發效率(ps:特別小團隊搞微服務但是沒有代碼生成器,簡直要了老命) 本文將分享如何在中台框架項目 Admin.Core 中添加代碼 ...
  • 官網:Git for Windows 點擊下載安裝。 右擊滑鼠會出現GUI和Bash 選擇git bash here 配置全局用戶名和郵箱(gitee) git config --global user.name "你的名字" git config --global user.email 你的郵箱 ...
  • C# 13 即 .Net 9 按照計劃會在2024年11月發佈,目前一些新特性已經定型,今天讓我們來預覽一個比較大型比較重要的新特性: 擴展類型 extension types ...
  • 目錄前言學習參考過程總結: 前言 做個自由仔。 學習參考 ChatGpt; https://www.cnblogs.com/zhili/p/DesignPatternSummery.html(大佬的,看了好多次) 過程 原由: 一開始只是想查查鏈式調用原理,以為是要繼承什麼介面,實現什麼方法才可以實 ...
  • 一:背景 1. 講故事 在dump分析的過程中經常會看到很多線程卡在Monitor.Wait方法上,曾經也有不少人問我為什麼用 !syncblk 看不到 Monitor.Wait 上的鎖信息,剛好昨天有時間我就來研究一下。 二:Monitor.Wait 底層怎麼玩的 1. 案例演示 為了方便講述,先 ...
  • 在以前我做程式的時候,一般在登錄視窗裡面顯示程式名稱,登錄視窗一般設置一張背景圖片,由於程式的名稱一般都是確定的,所以也不存在太大的問題,不過如果客戶定製不同的系統的時候,需要使用Photoshop修改下圖層的文字,再生成圖片,然後替換一下也可以了。不過本著減少客戶使用繁瑣性,也可以使用空白名稱的通... ...
  • .Net 中提供了一系列的管理對象集合的類型,數組、可變列表、字典等。從類型安全上集合分為兩類,泛型集合 和 非泛型集合,傳統的非泛型集合存儲為Object,需要類型轉。而泛型集合提供了更好的性能、編譯時類型安全,推薦使用。 ...
  • 今天在技術群里,石頭哥向大家提了個問題:"如何在一個以System身份運行的.NET程式(Windows Services)中,以其它活動的用戶身份啟動可互動式進程(桌面應用程式、控制台程式、等帶有UI和互動式體驗的程式)"? 我以前有過類似的需求,是在GitLab流水線中運行帶有UI的自動化測試程 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...