《物聯網框架ServerSuperIO教程》-4.如開發一套設備驅動,同時支持串口和網路通訊。附:將來支持Windows 10 IOT

来源:http://www.cnblogs.com/lsjwq/archive/2016/10/22/5986952.html
-Advertisement-
Play Games

作為物聯網通訊框架,肯定要支持多種通訊鏈路,在多種通訊鏈路的基礎上完成多種通訊協議的交互,例如:Modbus、自定義協議等等。但是,有一個問題:針對同一臺硬體設備或感測器,完成串口和網路兩種通訊方式的數據採集和控制,是否要分別寫代碼?如果從現實角度分析,同一硬體,它要完成的業務邏輯肯定是相同的,所以... ...


1.C#跨平臺物聯網通訊框架ServerSuperIO(SSIO)介紹

《連載 | 物聯網框架ServerSuperIO教程》1.4種通訊模式機制。

《連載 | 物聯網框架ServerSuperIO教程》2.服務實例的配置參數說明

《連載 | 物聯網框架ServerSuperIO教程》- 3.設備驅動介紹

 

註:ServerSuperIO有可能被移植到Windows 10 IOT上,那麼將來有可能開發一套設備驅動,可以支行在服務端、嵌入式設備中,將形成完整的解決方案。

      現在已經調試通過部分代碼,還得需要一段時間,一般都是晚上乾,時間也有限。如下圖:

 

目       錄

4.如開發一套設備驅動,同時支持串口和網路通訊... 2

4.1           概述... 2

4.2           通訊協議規定... 2

4.2.1    發送讀實時數據命令協議... 2

4.2.2    解析實時數據協議... 3

4.2.3    發送和接收數據事例... 3

4.3           開發設備驅動... 3

4.3.1    構建實時數據持久對象(不是必須)... 3

4.3.2    構建參數數據持久對象... 5

4.3.3    構建發送和解析協議命令對象... 5

4.3.4    構建協議驅動對象... 6

4.3.5    構建設備驅動對象... 8

4.4           構建宿主程式... 12

4.5           運行效果... 15

 

 

4.如開發一套設備驅動,同時支持串口和網路通訊

4.1    概述

     作為物聯網通訊框架,肯定要支持多種通訊鏈路,在多種通訊鏈路的基礎上完成多種通訊協議的交互,例如:Modbus、自定義協議等等。但是,有一個問題:針對同一臺硬體設備或感測器,完成串口和網路兩種通訊方式的數據採集和控制,是否要分別寫代碼?如果從現實角度分析,同一硬體,它要完成的業務邏輯肯定是相同的,所以ServerSuperIO物聯網框架,允許開發一套設備驅動,同時支持串口和網路兩種通訊方式的交互。

     通訊很簡單、交互很簡單、業務很簡單……如果把很多簡單的問題合在一起,那麼就變得不簡單了,所以要有一個框架性的東西,重新把眾多問題變得簡單。

4.2    通訊協議規定

    在完成一個設備驅動的開發之前,首先要知道它的通訊協議,好比兩個人交流的語言一樣。針對通訊協議,我們自定義一個簡單交互方式,只是發送命令,提取數據信息。

4.2.1    發送讀實時數據命令協議

     電腦發送0x61指令為讀實時數據命令,共發送6個位元組,校驗和為從“從機地址”開始的累加和,不包括“數據報頭”、“校驗和”和“協議結束”。

     發送指令數據幀如下:

幀結構

數據報頭

從機地址

指令代碼

校驗和

協議結束

0x55

0xAA

 

0x61

 

0x0D

位元組數

1

1

1

1

1

1

  

4.2.2    解析實時數據協議

    下位機接收到讀實時數據命令後,並校驗成功,返回實時數據,校驗和為從“從機地址”開始的累加和,不包括“數據報頭”、“校驗和”和“協議結束”。

    接收數據幀如下:

幀結構

數據報頭

從機地址

指令代碼

流量

信號

校驗和

協議結束

0x55

0xAA

 

0x61

浮點型

浮點型

 

0x0D

位元組數

1

1

1

1

4

 

4

1

1

4.2.3    發送和接收數據事例

發送(十六進位):0x55 0xaa 0x00 0x61 0x61 0x0d

接收(十六進位):0x55 0xaa 0x00 0x61 0x43 0x7a 0x00 0x00 0x43 0xb4 0x15 0x0d

流量數據為:250.00

信號數據為:360.00

4.3    開發設備驅動

4.3.1    構建實時數據持久對象(不是必須)

1.通過返回數據的通訊協議,有流量和信號兩個動態變數,我們需要創建一個動態對象實體類,主要用於協議驅動與設備驅動之間的數據交互。代碼如下:

public class Dyn
{
        private float _Flow = 0.0f;
        /// <summary>
        /// 流量
        /// </summary>
        public float Flow
        {
            get { return _Flow; }
            set { _Flow = value; }
        }
        private float _Signal = 0.0f;
        /// <summary>
        /// 信號
        /// </summary>
        public float Signal
        {
            get { return _Signal; }
            set { _Signal = value; }
        }
}

  2.我們主要的工作是要創建一個實時數據持久對象類,實時緩存數據信息,也可以把該實時數據信息保存到資料庫中或其他存儲媒質。實時數據持久對象類的代碼如下:

public class DeviceDyn:DeviceDynamic
{
        public DeviceDyn() : base()
        {
            Dyn=new Dyn();
        }
        public override string GetAlertState()
        {
            throw new NotImplementedException("無報警信息");
        }
        public override object Repair()
        {
            return new DeviceDyn();
        }
        public Dyn Dyn { get; set; }
}

     DeviceDyn 類繼承自DeviceDynamic,因為每個硬體設備的報警信息有可能不一樣,所以GetAlertState函數可以實該功能,但是SSIO框架並沒有直接引用;這個類本質上是一個可以序列化,在不加互斥的情況下可能造成文件損壞,所以Repair可以完成修複功能,在DeviceDynamic基類里實現了該功能;另外,實現DeviceDynamic基類自帶兩個函數,Save函數用於持久化(序列化)此類的信息,Load用於獲得(反序列化)此類的信息,在設備驅動中可以使用。

4.3.2    構建參數數據持久對象

    一般來說硬體設備會有讀參數的命令,那麼返回來的參數也需要進行持久化存儲,並且每台設備的參數都可能不一樣,在此提供一個可擴展的介面。在這個通訊協議中並沒有涉及到設備參數相關的協議說明,但是我們也需要創建一個參數數據持久對象類,可以不寫任何擴展的參數屬性,在SSIO框架對參數的介面進行了引用,這是必須進行了工作。代碼如下:

public class DevicePara:ServerSuperIO.Device.DeviceParameter
{
        public override object Repair()
        {
            return new DevicePara();
        }
}

     DevicePara繼承自DeviceParameter類,情況與實時數據持久對象類似,可以參數。

4.3.3    構建發送和解析協議命令對象

    與設備進行交互會涉及到很多互動式的命令或指令代碼,而這些命令在SSIO框架內是以協議命令對象的形式存在,大體包括三個部:執行命令介面、打包發送數據介面、解析接收數據介面等。

    針對上面的通訊協議,有一個61指令,那麼我們就可以根據61指令為命名構建一個協議命令對象,包括發送數據和解析數據部分。如果有其他命令代碼,舉一反三。代碼如下:

internal class DeviceCommand:ProtocolCommand
{
        public override string Name
        {
            get { return "61"; }
        }

        public override void ExcuteCommand<T>(T t)
        {
            throw new NotImplementedException();
        }

        public override byte[] Package<T> (string code, T1 t1,T2 t2)
        {
            //發送:0x55 0xaa 0x00 0x61 0x61 0x0d
            byte[] data = new byte[6];
            data[0] = 0x55;
            data[1] = 0xaa;
            data[2] = byte.Parse(code);
            data[3] = 0x61;
            data[4] = this.ProtocolDriver.GetCheckData(data)[0];
            data[5] = 0x0d;
            return data;
        }

        public override dynamic Analysis<T>(byte[] data, T t)
        {
            Dyn dyn = new Dyn()
            //一般下位機是單片的話,接收到數據的高低位需要互換,才能正常解析。
            byte[] flow = BinaryUtil.SubBytes(data, 4, 4, true);
            dyn.Flow = BitConverter.ToSingle(flow, 0);
            byte[] signal = BinaryUtil.SubBytes(data, 8, 4, true);
            dyn.Signal = BitConverter.ToSingle(signal, 0);
            return dyn;
        }

}

     構建協議命令需要全部繼承自ProtocolCommand,根據通訊協議規定,Name屬性返回61,作為關鍵字;Package是打包要送的數據信息;Analysis對應著接收數據之後進行解析操作。就這樣一個簡單的協議命令驅動就構建完成了。

4.3.4    構建協議驅動對象

    有了協議命令之後,我們需要構建協議驅動對象,SSIO框架支持自定義協議也在於此,並且與設備驅動的介面相關聯,在SSIO框架的高級應用中也進行了引用,構建這引對象很關鍵。代碼如下:

internal class DeviceProtocol:ProtocolDriver
{
        public override bool CheckData(byte[] data)
        {
            if (data[0] == 0x55 && data[1] == 0xaa && data[data.Length - 1] == 0x0d)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        public override byte[] GetCommand(byte[] data)
        {
            return new byte[] { data[3] };
        }

        public override int GetAddress(byte[] data)
        {
            return data[2];
        }

        public override byte[] GetHead(byte[] data)
        {
            return new byte[] { data[0], data[1] };
        }

        public override byte[] GetEnd(byte[] data)
        {
            return new byte[] { data[data.Length - 1] };
        }

        public override byte[] GetCheckData(byte[] data)
        {
            byte checkSum = 0;
            for (int i = 2; i < data.Length - 2; i++)
            {
                checkSum += data[i];
            }
            return new byte[] { checkSum };
        }

        public override string GetCode(byte[] data)
        {
            throw new NotImplementedException();
        }

        public override int GetPackageLength(byte[] data, IChannel channel, ref int readTimeout)
        {
           throw new NotImplementedException();
        }
}

     DeviceProtocol 協議驅動繼承自ProtocolDriver ,一個設備驅動只存在一個協議驅動,一個協議驅動可以存在多個協議命令(如61命令)。該類中的CheckData函數很關鍵,SSIO框架中的設備驅動基類引用了,主要是完成校驗接收數據的完事性,是否符合協議,從而決定了通訊狀態:通訊正常、通訊中斷、通訊干擾、以及通訊未知,不同的通訊狀態也決定了調用設備驅動中的哪個函數介面:Communicate、CommunicateInterrupt、CommunicateError和CommunicateNone。

4.3.5    構建設備驅動對象

     上邊的基礎工作都做完之後,現在就構建設備驅動的核心部分,也就是SSIO框架與設備驅動對接、協調、調度的唯一介面,寫完這個介面,設備驅動就可以在SSIO上直接運行了,並且與硬體設備進行交互。直接上代碼:

public class DeviceDriver:RunDevice
{
        private DeviceDyn _deviceDyn;
        private DevicePara _devicePara;
        private DeviceProtocol _protocol;
        public DeviceDriver() : base()
        {
            _devicePara = new DevicePara();
            _deviceDyn = new DeviceDyn();
            _protocol = new DeviceProtocol();
        }

        public override void Initialize(string devid)
        {
            this.Protocol.InitDriver(this.GetType(),null);
            //初始化設備參數信息
            _devicePara.DeviceID = devid;//設備的ID必須先賦值,因為要查找對應的參數文件。
            if (System.IO.File.Exists(_devicePara.SavePath))
            {
                //如果參數文件存在,則獲得參數實例
                _devicePara = _devicePara.Load<DevicePara>();
            }
            else
            {
                //如果參數文件不存在,則序列化一個文件
                _devicePara.Save<DevicePara>(_devicePara);
            }

            //初始化設備實時數據信息
            _deviceDyn.DeviceID = devid;//設備的ID必須先賦值,因為要查找對應的實時數據文件。
            if (System.IO.File.Exists(_deviceDyn.SavePath))
            {
                //參數文件存在,則獲得參數實例
                _deviceDyn = _deviceDyn.Load<DeviceDyn>();
            }
            else
            {
                //如果參數文件不存在,則序列化一個文件
                _deviceDyn.Save<DeviceDyn>(_deviceDyn);
            }
        }
 
        public override byte[] GetConstantCommand()
        {
            return this.Protocol.DriverPackage<String>("0", "61", null);
        }
 
        public override void Communicate(ServerSuperIO.Communicate.IRequestInfo info)
        {
            Dyn dyn = this.Protocol.DriverAnalysis<String>("61", info.Data, null);
            if (dyn != null)
            {
                _deviceDyn.Dyn = dyn;
            }
            OnDeviceRuningLog("通訊正常");
        }

        public override void CommunicateInterrupt(ServerSuperIO.Communicate.IRequestInfo info)
        {
            OnDeviceRuningLog("通訊中斷");
        }
 
        public override void CommunicateError(ServerSuperIO.Communicate.IRequestInfo info)
        {
            OnDeviceRuningLog("通訊干擾");
        }
 
        public override void CommunicateNone()
        {
            OnDeviceRuningLog("通訊未知");
        }

        public override void Alert()
        {
            return;
        }

        public override void Save()
        {
            try
            {
                _deviceDyn.Save<DeviceDyn>(_deviceDyn);
            }
            catch (Exception ex)
            {
                OnDeviceRuningLog(ex.Message);
            }
        }

        public override void Show()
        {
            List<string> list=new List<string>();
            list.Add(_devicePara.DeviceName);
            list.Add(_deviceDyn.Dyn.Flow.ToString());
            list.Add(_deviceDyn.Dyn.Signal.ToString());
            OnDeviceObjectChanged(list.ToArray());
        }
 
        public override void UnknownIO()
        {
            OnDeviceRuningLog("未知通訊介面");
        }

        public override void CommunicateStateChanged(ServerSuperIO.Communicate.CommunicateState comState)
        {
            OnDeviceRuningLog("通訊狀態改變");
        }

        public override void ChannelStateChanged(ServerSuperIO.Communicate.ChannelState channelState)
        {
            OnDeviceRuningLog("通道狀態改變");
        }

        public override void Exit()
        {
            OnDeviceRuningLog("退出設備");
        }

        public override void Delete()
        {
            OnDeviceRuningLog("刪除設備");
        }
 
        public override object GetObject()
        {
            throw new NotImplementedException();
        }

        public override void ShowContextMenu()
        {
            throw new NotImplementedException();
        }

        public override IDeviceDynamic DeviceDynamic
        {
            get { return _deviceDyn; }
        }

        public override IDeviceParameter DeviceParameter
        {
            get { return _devicePara; }
        }

        public override IProtocolDriver Protocol
       {
            get { return _protocol;}
        }

        public override DeviceType DeviceType
        {
            get { return DeviceType.Common; }
        }

        public override string ModelNumber
        {
            get { return "serversuperio"; }
        }

        public override System.Windows.Forms.Control DeviceGraphics
        {
            get { throw new NotImplementedException(); }
        }
}

    實時動態數據對象_deviceDyn、參數數據對象_devicePara、協議驅動對象_protocol分別提供給介面:DeviceDynamic、DeviceParameter和Protocol,為SSIO提供可引用的基礎屬性參數。

     Initialize是設備驅動初始化的函數介面,在這個介面完成兩個主要工作:初始化協議驅動和參數性的信息。通過this.Protocol.InitDriver(this.GetType(),null);代碼可以載入所有協議命令到協議驅動的緩存中,以便實時調用。當然這裡邊也可以進行其他方面的工作,但是註意對異常的處理。

     DeviceType這個是設備的類型,一般指定為Common就好了。其他函數介面功能已經在《物聯網框架ServerSuperIO教程-3.設備驅動介紹》中詳細介紹了,請參考。

4.4    構建宿主程式

     一個簡單的設備驅動就已經開發好了,光有驅動還不行,那麼我們基於SSIO框架再寫幾行代碼,完成一個宿主程式,把設備驅動實例化,放SSIO的服務實例中運行,完成串口和網路兩種方式的通訊交互,代碼也非常簡單。代碼如下:

class Program
{
        static void Main(string[] args)
        {
            DeviceDriver dev1 = new DeviceDriver();
            dev1.DeviceParameter.DeviceName = "串口設備1";
            dev1.DeviceParameter.DeviceAddr = 0;
            dev1.DeviceParameter.DeviceID = "0";
            dev1.DeviceDynamic.DeviceID = "0";
            dev1.DeviceParameter.COM.Port = 1;
            dev1.DeviceParameter.COM.Baud = 9600;
            dev1.CommunicateType = CommunicateType.COM;
            dev1.Initialize("0");

            DeviceDriver dev4 = new DeviceDriver();
            dev4.DeviceParameter.DeviceName = "網路設備2";
            dev4.DeviceParameter.DeviceAddr = 0;
            dev4.DeviceParameter.DeviceID = "3";
            dev4.DeviceDynamic.DeviceID = "3";
            dev4.DeviceParameter.NET.RemoteIP = "127.0.0.1";
            dev4.DeviceParameter.NET.RemotePort = 9600;
            dev4.CommunicateType = CommunicateType.NET;
            dev4.Initialize("3");

            IServer server = new ServerFactory().CreateServer(new ServerConfig()
            {
                ServerName = "服務實例1",
                SocketMode = SocketMode.Tcp,
                ControlMode = ControlMode.Loop,
                CheckSameSocketSession = false,
                StartCheckPackageLength = false,
            });

            server.AddDeviceCompleted += server_AddDeviceCompleted;
            server.DeleteDeviceCompleted += server_DeleteDeviceCompleted;
            server.SocketConnected+=server_SocketConnected;
            server.SocketClosed+=server_SocketClosed;
            server.Start();

            server.AddDevice(dev1);
            server.AddDevice(dev4);

            while ("exit"==Console.ReadLine())
            {
                 server.Stop();
            }
        }

        private static void server_SocketClosed(string ip, int port)
        {
            Console.WriteLine(String.Format("斷開:{0}-{1} 成功", ip, port));
        }

        private static void server_SocketConnected(string ip, int port)
        {
            Console.WriteLine(String.Format("連接:{0}-{1} 成功",ip, port));
        }

        private static void server_AddDeviceCompleted(string devid, string devName, bool isSuccess)

        {
            Console.WriteLine(devName+",增加:"+isSuccess.ToString());
        }

        private static void server_DeleteDeviceCompleted(string devid, string devName, bool isSuccess)
        {
            Console.WriteLine(devName + ",刪除:" + isSuccess.ToString());
        }
    }
}

     這個代碼大家都能看明白,具體的控制模式我們接下來會一一介紹。在構建宿主程式的時候,切忌對服務實例這樣引用:server.ChannelManager、server.ControllerManager、server.DeviceManager。儘管提供了這樣的介面,主要是為了SSIO框架內部使用的,不需要我們單獨去操作這些介面。有的網友是這樣的寫的,那麼就變成了一個純的通信IO框架,那麼就失去了SSIO框架本身的價值。作為二次開發者,只需要設置設備驅動的參數,以及向服務實例中增加或刪除設備就行了,其他所有的運行全部交給SSIO框架來完成。

4.5    運行效果

 

 

1.[連載]《C#通訊(串口和網路)框架的設計與實現》

2.[開源]C#跨平臺物聯網通訊框架ServerSuperIO(SSIO)介紹

2.應用SuperIO(SIO)和開源跨平臺物聯網框架ServerSuperIO(SSIO)構建系統的整體方案

3.C#工業物聯網和集成系統解決方案的技術路線(數據源、數據採集、數據上傳與接收、ActiveMQ、Mongodb、WebApi、手機App)

5.ServerSuperIO開源地址:https://github.com/wxzz/ServerSuperIO

物聯網&集成技術(.NET) QQ群54256083


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

-Advertisement-
Play Games
更多相關文章
  • 1、單機MySQL的美好年代 在90年代,一個網站的訪問量一般都不大,用單個資料庫完全可以輕鬆應付。在那個時候,更多的都是靜態網頁,動態交互類型的網站不多。 上述架構下,我們來看看數據存儲的瓶頸是什麼?1.數據量的總大小 一個機器放不下時2.數據的索引(B+ Tree)一個機器的記憶體放不下時3.訪問 ...
  • 官方說5.5開始就廢棄mysql_query()這塊東西很多,當然常用的就幾個還是看手冊吧。 這裡簡單記錄一下。在我失憶之後可以找回一點記憶。最近一直用框架原生的都快忘了。 1.打開mysql連接 $resource1 = mysql_connect('localhost', 'root', 'ro ...
  • 字元串截斷函數是指:Stuff 和 SubString,字元串查找函數是:CharIndex 和 PatIndex 一,SubString 截取子串 最常用的字元串函數,用於截取特定長度的子串。 參數說明: start 參數:整數,表示開始位置;字元的序號(index)從1開始,即第一個字元的序號是 ...
  • 今天遇到了一個問題,要查詢今天添加到一個表中的數據,後來解決了,嗯,記錄一下: SELECT * FROM 表1 WHERE convert(varchar(10),表1.創建時間,120)=CONVERT(VARCHAR(10),GETDATE(),120) 如果是本月的話: SELECT * F ...
  • Centos6.5之ssh免密碼登錄配置 centos ssh 免密碼登錄 0.說明 這裡為了方便說明問題,假設有A和B兩台安裝了centos6.5的主機。目標是實現A、B兩台主機分別能夠通過ssh免密碼登錄到對方主機。不同主機的配置過程一樣,這裡介紹A主機的配置過程。 事先在AB主機分別創建好要免 ...
  • 一、說明 安裝環境 centos6.6 (64位) python2.7.10 (升級系統預設python版本的方法參見 "在CentOS 6.5上安裝python2.7" ) 約定 工作目錄假定為當前用戶的HOME目錄 安裝的各軟體版本以文檔編寫時的最新版為基準,請結合實際情況參考。 尖括弧""包括 ...
  • 簡介 ABP提供了一個緩存介面,它內部使用了這個緩存介面。雖然介面的預設實現是MemoryCache,但可以用任何其它實現的緩存供應器。Abp.RedisCache包用Redis實現了緩存(查看下方的“Redis 緩存集成”)。 ICacheManager 緩存的主要介面是ICacheManager ...
  • MySqlSugar 3.X API 作為支持.NET CORE 為數不多的ORM之一,除了具有優越的性能外,還擁有強大的功能,不只是滿足你的增,刪,查和改。實質上擁有更多你想像不到的功能,當你需要實現某個功能時會發現有這個功能太棒了。 所有版本 ASP.NET 4.0+ MSSQL https:/ ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...