解析博圖數據塊(昆侖通態觸摸屏自動命名)

来源:https://www.cnblogs.com/frogkiller/archive/2020/02/27/12374010.html
-Advertisement-
Play Games

1,博圖數據塊的數據排列原則: 數據對齊演算法: 將當前地址對齊到整數:numBytes = (int)Math.Ceiling(numBytes); 將當前地址對齊到偶整數: numBytes = Math.Ceiling(numBytes); if ((numBytes / 2 - Math.Fl... ...


1,博圖數據塊的數據排列原則:

   數據對齊演算法:

  •    將當前地址對齊到整數:
numBytes = (int)Math.Ceiling(numBytes);
  • 將當前地址對齊到偶整數:
  •  numBytes = Math.Ceiling(numBytes);
                    if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
                        numBytes++;  

2,數據對齊的原則,如果當前的數據類型是:

  1. bool,則使用當前地址.
  2. byte,則使用對齊方式1
  3. 其他,則使用對齊方式2,即偶數對齊的方法.

3,如何從地址和bool進行設定和取值,假設我們有一個byte[]數組代表整個DB,和一個浮點數代表bool的地址?

  • 取值:
  • int bytePos = (int)Math.Floor(numBytes);
                        int bitPos = (int)((numBytes - (double)bytePos) / 0.125);
                        if ((bytes[bytePos] & (int)Math.Pow(2, bitPos)) != 0)
                            value = true;
                        else
                            value = false;
  • 賦值
     bytePos = (int)Math.Floor(numBytes);
                            bitPos = (int)((numBytes - (double)bytePos) / 0.125);
                            if ((bool)obj)
                                bytes[bytePos] |= (byte)Math.Pow(2, bitPos);            // is true
                            else
                                bytes[bytePos] &= (byte)(~(byte)Math.Pow(2, bitPos));   // is false

         思路:獲取當前bit所在的位元組地址.然後使用  (註意位是從0開始的.位0..位15..位31.)

                    t=t | 1<<(bitPos)來進行置位單個位. (掩碼置位,使用|,所有為1的位進行置位)

                    t=t &(~1<<(bitPos)來進行複位單個位,(掩碼複位,使用 &(~掩碼),則所有位1的掩碼進行複位)

                    t=t^(1<<(bitPos) 來進行異或運算(,掩碼的1所在的位進行取反操作.)

                    t=t&(1<<(bitPos))來判斷某個位是否為1或為0.

2,迭代解析

   numbytes: 迭代的plc地址

itemInfos:迭代的信息存儲的列表

preName:迭代的名稱.

ElementItem:用於解析的元素.

public static void DeserializeItem(ref double numBytes, List<ItemInfo> itemInfos, string preName, ElementItem item)
        {
            var PreName = (string.IsNullOrEmpty(preName)) ? "" : preName + "_";
            var Info = new ItemInfo() { Name = PreName + item.Name, Type = item.Type };

            switch (item.GetTypeInfo())
            {

                case PlcParamType.BaseType:


                    switch (item.Type)
                    {

                        case "Bool":

                            Info.Addr = ParseAddr(numBytes, item);

                            numBytes += 0.125;
                            break;
                        case "Char":
                        case "Byte":
                            numBytes = Math.Ceiling(numBytes);
                            Info.Addr = ParseAddr(numBytes, item);
                            numBytes++;
                            ;
                            break;
                        case "Int":
                        case "UInt":
                        case "Word":
                            numBytes = Math.Ceiling(numBytes);
                            if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
                                numBytes++;
                            Info.Addr = ParseAddr(numBytes, item);
                            numBytes += 2;
                            ;
                            break;
                        case "DInt":
                        case "UDInt":
                        case "DWord":
                        case "Time":
                        case "Real":
                            numBytes = Math.Ceiling(numBytes);
                            if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
                                numBytes++;
                            Info.Addr = ParseAddr(numBytes, item);
                            numBytes += 4;
                            ;
                            break;
                        default:
                            break;



                    }
                    itemInfos.Add(Info);
                    break;
                case PlcParamType.String:
                    numBytes = Math.Ceiling(numBytes);
                    if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
                        numBytes++;
                    //----------
                    Info.Addr = ParseAddr(numBytes, item);
                    numBytes += item.GetStringLength();
                    itemInfos.Add(Info);
                    break;

                case PlcParamType.Array:

                    //------------原程式的可能是個漏洞.
                    numBytes = Math.Ceiling(numBytes);
                    if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
                        numBytes++;
                    //-------------
                    var elementType = item.GetElementType();

                    for (var i = 0; i < item.GetArrayLength(); i++)
                    {
                        var element = new ElementItem() { Name = item.Name + $"[{i}]", Type = elementType };

                        DeserializeItem(ref numBytes, itemInfos, PreName, element);

                    }
                    break;
                case PlcParamType.UDT:
                    numBytes = Math.Ceiling(numBytes);
                    if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
                        numBytes++;
                    //格式
                    foreach (var element in item.GetElementItems(Dict))
                    {

                        DeserializeItem(ref numBytes, itemInfos, PreName + item.Name, element);
                    }
                    break;
                default:
                    throw new ArgumentException("do not find type");

            }





        }

                   思路: 如果元素的類型是基本類型:比如 bool,int,time...等等,則直接生成一條ItemInfo記錄

                           如果元素的類型是數組,則獲取數組的長度和獲取數組的元素類型,然後進行迭代解析.

                          如果元素是UDT類型,也就是自定義的類型.那麼就迭代獲取元素,並且迭代解析.

----------------------

所有的這一切,源於一個Dictionary<string,List<ElementItem>>對象.這個對象存儲了自定義的類別的名稱和其子元素

比如有以下的一個DB導出文件;

則可以看到其有 "step"類型

                    元素有element1.....type為 int...

              還有 "Recipe"類型

               還有 "test1"類型

               還有一個DataBlock,就是這個資料庫,也將它當作一個類型,其type就是"數據塊_1" ,其元素就是

               名稱             類型

            Recipe1         Recipe(這個可以在上面找到)

         關鍵是其內部的Struct結構,對於這個結構,我們自定義生成其類型 為 Name_Struct.

TYPE "step"
VERSION : 0.1
   STRUCT
      element1 : Int;
      element2 : Int;
      element3 : Int;
      element4 : Int;
      element5 : Int;
      element6 : Int;
      element7 : Real;
      element8 : Real;
      element9 : Real;
      element10 : Real;
      element11 : Real;
      element12 : Real;
      element13 : Real;
      element14 : Bool;
      element15 : Bool;
      element16 : Int;
   END_STRUCT;

END_TYPE

TYPE "Recipe"
VERSION : 0.1
   STRUCT
      "Name" : String[20];
      steps : Array[0..29] of "step";
   END_STRUCT;

END_TYPE

TYPE "test1"
VERSION : 0.1
   STRUCT
      b : Bool;
   END_STRUCT;

END_TYPE

DATA_BLOCK "數據塊_1"
{ S7_Optimized_Access := 'FALSE' }
VERSION : 0.1
NON_RETAIN
   STRUCT
      Recipe1 : "Recipe";
      AAA : "Recipe";
      aa : String;
      adef : Struct
         a : Bool;
         b : Bool;
         c : Int;
         bool1 : Bool;
         bool2 : Bool;
         ffff : Struct
            ttt : Bool;
            aaa : Bool;
            fff : Bool;
            eefg : Bool;
         END_STRUCT;
         afe : Bool;
         aaaaaaa : "test1";
         x1 : "test1";
         x2 : "test1";
         x3 : Array[0..1] of Byte;
         abcef : Array[0..10] of Struct
            aef : Struct
               Static_1 : Bool;
               Static_2 : Bool;
            END_STRUCT;
            eef : Bool;
            affe : Bool;
         END_STRUCT;
      END_STRUCT;
   END_STRUCT;


BEGIN
   Recipe1.steps[29].element14 := FALSE;

END_DATA_BLOCK

3,從db文件中讀取類信息.

//從db文件中讀取類信息,並且放入到Dict之中,同時填充MainBlock信息,如果有的話.
        public static void GetTypeFromFile(Dictionary<string, List<ElementItem>> Dict, string path, out ElementItem MainBlock)
        {
            MainBlock = new ElementItem();//生成Block對象.
            using (Stream st = new FileStream(path, FileMode.Open))//讀取文本文件DB塊中的數據.
            {
                StreamReader reader = new StreamReader(st);
                List<ElementItem> list;
                while (!reader.EndOfStream)
                {
                    string line = reader.ReadLine();//讀取一行數據進行判斷
                    switch (GetReaderLineTYPE(line))//通過解析函數解析這一行的數據是什麼類型.
                    {
                        case "TYPE"://如果是類型 對應 db文件裡面 TYPE xxxx 的格式
                            list = new List<ElementItem>();//則創建一個列表準備容納該TYPE的ELementItem,也就是元素集.
                            string tn = GetReaderLineName(line);//解析type行的名稱.也就是該type的名稱.
                            GetElementsFromReader(reader, list, tn, Dict);//然後調用函數進行將元素放入列表,最後哪個Dictionary參數用於接受內聯Struct類型.

                            Dict[tn] = list;//將該類型在字典中生成名值對,也就是Type名稱,List<elementItem>作為值.
                            break;
                        case "DATA_BLOCK":
                            MainBlock = new ElementItem();
                            string bn = GetReaderLineName(line);
                            MainBlock.Name = bn;
                            MainBlock.Type = bn;
                            list = new List<ElementItem>();
                            GetElementsFromReader(reader, list, bn, Dict);//如果是DB塊,則填充Main Block(備用),剩下的根上面一樣).
                            Dict[bn] = list;
                            break;
                        default:
                            break;

                    }


                }
            }
        }

4, 輔助的讀取迭代函數,實現的關鍵...

  public static void GetElementsFromReader(StreamReader reader, List<ElementItem> list, string type_name, Dictionary<string, List<ElementItem>> Dict)
        {
            ElementItem item;
            Tuple<string, string> tp;
            while (!reader.EndOfStream)
            {
                string line = reader.ReadLine();//首先,其必須是一個解析Type或者DataBlock的過程,因為只有這兩處調用它.
                switch (GetReaderLineTYPE(line))//解析每行的類別.
                {
                    case "ELEMENT"://當解析到該行是元素的時候.就將該行加入到列表中.
                        item = new ElementItem();
                        tp = GetElementInfo(line);
                        item.Name = tp.Item1;
                        item.Type = tp.Item2;
                        list.Add(item);
                        break;
                    case "StructELEMENT"://當解析到該行是Struct時,也就是元素類型時Struct的時候,
                        item = new ElementItem();
                        tp = GetElementInfo(line);
                        item.Name = tp.Item1;
                        item.Type = tp.Item2.Remove(tp.Item2.LastIndexOf("Struct"));//由於Array Struct的存在,將該元素類型從....Struct 
                        //替換為 .... elementName_Struct
                        item.Type = item.Type + type_name + "_" + item.Name + "_" + "Struct";//
                        string structType = type_name + "_" + item.Name + "_" + "Struct";//該名稱為其新的類別.
                        list.Add(item);//首先將該元素加入列表.
                        List<ElementItem> sub = new List<ElementItem>();//將該子類別(我們自定義的類別,添加到字典中.
                        GetElementsFromReader(reader, sub, structType, Dict);
                        Dict[structType] = sub;
                        break;
                    case "END_STRUCT"://當接受到這個時,表明一個Type或者一個DataBlock的解析工作結束了,返回上層對象.
                        return;
                    default:
                        break;
                }
            }
        }

5,工具函數1,用於幫助判斷DB文件每行的信息.

 private static Tuple<string, string> GetElementInfo(string line)
        {
            if (!GetReaderLineTYPE(line).Contains("ELEMENT")) throw new Exception("this line is not element " + line);
            int pos = line.IndexOf(" : ");
            string Name = line.Remove(pos).Trim(' ', ';');
            var t = Name.IndexOf(' ');
            if (t > 0)
                Name = Name.Remove(t);
            string Type = line.Substring(pos + 3).Trim(' ', ';');
            if (Type.Contains(" :=")) Type = Type.Remove(Type.IndexOf(" :="));
            return new Tuple<string, string>(Name, Type);
        }

        private static string GetReaderLineName(string line)
        {
            if ((GetReaderLineTYPE(line) != "TYPE") && (GetReaderLineTYPE(line) != "DATA_BLOCK")) throw new Exception("not read name of " + line);

            return line.Substring(line.IndexOf(' ')).Trim(' ');
        }

        private static string GetReaderLineTYPE(string line)
        {
            //Console.WriteLine(line);
            if (line.Contains("TYPE ")) return "TYPE";
            if (line.Contains("DATA_BLOCK ")) return "DATA_BLOCK";
            if (line.Contains("END_STRUCT;")) return "END_STRUCT";
            if (line.Trim(' ') == "STRUCT") return "STRUCT";
            if (line.EndsWith("Struct")) return "StructELEMENT";
            if ((line.EndsWith(";"))) return "ELEMENT";
            return null;
        }

6,從之前生成的字典和MainDataBlock中生成 List<Item Infos>也就是展開DB塊信息.

public static void DeserializeItem(ref double numBytes, List<ItemInfo> itemInfos, string preName, ElementItem item)
        {
            var PreName = (string.IsNullOrEmpty(preName)) ? "" : preName + "_";//如果前導名稱不為空,則添加到展開的名稱上去.
            //這裡是個bug,最後將這行放到string生成,或者BaseType生成上,因為會出現多次_
            var Info = new ItemInfo() { Name = PreName + item.Name, Type = item.Type };

            switch (item.GetTypeInfo())//解析這個元素的類別
            {

                case PlcParamType.BaseType://基類直接添加.


                    switch (item.Type)
                    {

                        case "Bool":

                            Info.Addr = ParseAddr(numBytes, item);

                            numBytes += 0.125;
                            break;
                        case "Char":
                        case "Byte":
                            numBytes = Math.Ceiling(numBytes);
                            Info.Addr = ParseAddr(numBytes, item);
                            numBytes++;
                            ;
                            break;
                        case "Int":
                        case "UInt":
                        case "Word":
                            numBytes = Math.Ceiling(numBytes);
                            if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
                                numBytes++;
                            Info.Addr = ParseAddr(numBytes, item);
                            numBytes += 2;
                            ;
                            break;
                        case "DInt":
                        case "UDInt":
                        case "DWord":
                        case "Time":
                        case "Real":
                            numBytes = Math.Ceiling(numBytes);
                            if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
                                numBytes++;
                            Info.Addr = ParseAddr(numBytes, item);
                            numBytes += 4;
                            ;
                            break;
                        default:
                            break;



                    }
                    itemInfos.Add(Info);
                    break;
                case PlcParamType.String://string類型.直接添加
                    numBytes = Math.Ceiling(numBytes);
                    if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
                        numBytes++;
                    //----------
                    Info.Addr = ParseAddr(numBytes, item);
                    numBytes += item.GetStringLength();//如果是string,則加256,否則加 xxx+2;
                    itemInfos.Add(Info);
                    break;

                case PlcParamType.Array://數組則進行分解,再迭代.

                    //------------原程式的可能是個漏洞.
                    numBytes = Math.Ceiling(numBytes);
                    if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
                        numBytes++;
                    //-------------
                    var elementType = item.GetElementType();

                    for (var i = 0; i < item.GetArrayLength(); i++)
                    {
                        var element = new ElementItem() { Name = item.Name + $"[{i}]", Type = elementType };

                        DeserializeItem(ref numBytes, itemInfos, PreName, element);

                    }
                    break;
                case PlcParamType.UDT://PLC類型,進行分解後迭代.
                    numBytes = Math.Ceiling(numBytes);
                    if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
                        numBytes++;
                    //格式
                    foreach (var element in item.GetElementItems(Dict))
                    {

                        DeserializeItem(ref numBytes, itemInfos, PreName + item.Name, element);
                    }
                    break;
                default:
                    throw new ArgumentException("do not find type");

            }





        }

註意,每條信息組成:(s

public class ItemInfo
        {
            public ItemInfo()
            {
            }

            public string Name { get; set; }//element的名稱
            public string Type { get; set; }//element的類別(基類別,比如字元串,byte,bool之類.
            public object Addr { get; internal set; }//地址;比如dbx0.0之類.
//綜合就是給出  PLC變數名  PLC變數類型   PLC變數地址 的一個列表.
        }

7,功能擴展,支持多個db文件的解析操作

 // MainFunction to read message from dbs.
        //迭代解析 多個db文件.
        public static void GetDBInfosFromFiles(string path, Dictionary<string, List<ElementItem>> Dict, Dictionary<string, List<ItemInfo>> DataBlocks)
        {
            DirectoryInfo dir = new DirectoryInfo(path);
            FileInfo[] files = dir.GetFiles("*.db");
            List<ElementItem> blocks = new List<ElementItem>();
            foreach (var file in files)//將每個文件的db塊加入到 db數組中,再將解析的TYPE都加入到字典中.
            {
                ElementItem MainBlock;
                GetTypeFromFile(Dict, file.FullName, out MainBlock);
                if (MainBlock != null)
                {
                    MainBlock.Name = file.Name.Remove(file.Name.IndexOf('.'));
                    blocks.Add(MainBlock);
                }
            }
            foreach (var block in blocks)//然後迭代解析每個DB塊的信息,將求加入到第二個字典中.
            {

                double numBytes = 0.0;
                List<ItemInfo> itemInfos = new List<ItemInfo>();
                DeserializeItem(ref numBytes, itemInfos, "", block);
                DataBlocks[block.Name] = itemInfos;
            }

        }

8,使用CSVHELPER類從昆侖通態導出的文件中來讀取信息

public static MacInfos[] GetCSVInfosFromFile(string path,string[] infos)
        {
            //用csvhelper類讀取數據:註意,必須用這個方法讀取,否則是亂碼!
            using (var reader = new StreamReader(path, Encoding.GetEncoding("GB2312")))
            {
                using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
                {

                    //讀取4行無效信息.............
                        csv.Read();
                    infos[0] = csv.GetField(0);


                    csv.Read();
                    infos[1] = csv.GetField(0);
                    csv.Read();
                    infos[2] = csv.GetField(0);
                    csv.Read();
                    infos[3] = csv.GetField(0);
                    //讀取所有的數據放入一個數組中.標準用法.
                    var records = csv.GetRecords<MacInfos>().ToArray();
                    return records;
                }



            }




        }

9,將CSV文件的變數名進行填充,即將 Dict之中的變數名填充到 CSV的變數名之中.

  //用於將填充完的信息數組反寫回csv文件.
        public static void WriteIntoCSVOfMAC(string path)
        {
            string csvpath = FindFiles(path, "*.csv").First();//找到csv文件.
            string[] strinfos = new string[4];//填充無效信息的4行
            MacInfos[] macInfos = GetCSVInfosFromFile(csvpath,strinfos);//獲取MacInfos數組和無效信息字元串數組.

            foreach(var key in DBInfos.Keys)//輪詢每個DB塊.
            {


                var infos = (from info in macInfos

                             where GetDBFromMacInfo(info) == key.Remove(key.IndexOf('_'))

                             select info).ToArray();//將找到的Macinfo中再去查找對應的DB塊的Macinfos[]數組.
                WriteDBToMacInfos(key, infos);//然後將對應的db塊的信息,(找到其中的元素的名稱,一一寫入對應infos的變數名之中.
            }


            //填充完所有的Macinfos數組後,將這個數組反寫回CSV文件.
            using (var writer = new StreamWriter(new FileStream(csvpath, FileMode.Open), Encoding.GetEncoding("GB2312")))
            using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
            {
                //將原來的無效信息反填充回去.
                csv.WriteField(strinfos[0]);
                csv.NextRecord();//轉移到下一行.去讀寫數據.
                csv.WriteField(strinfos[1]);
                csv.NextRecord();
                csv.WriteField(strinfos[2]);
                csv.NextRecord();
                csv.WriteField(strinfos[3]);
                csv.NextRecord();
                //然後填充數組.
                csv.WriteRecords(macInfos);
            }



        }

10,結論:

1,在使用的時候,首先導出DB塊的塊生成源,

2,然後將所有的*.db文件們放入c:\macfile文件夾之中.

3,使用昆侖通態軟體導入標簽功能,導入db塊和導入utc塊,將變數導入到軟體中,這個時候,變數名一欄是空的.

4,使用導出設備信息,將導出一個csv文件.

5,運行小軟體.結束.

附上Git地址我的郵箱地址,

有喜歡工控軟體開發的多多溝通交流.


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

-Advertisement-
Play Games
更多相關文章
  • 行為模式:關註系統中對象之間的相互交互,研究運行時對象之間的相互通信和協作,明確對象職責 1.模板方法模式(template method) 定義了一個操作中的演算法骨架,將某些步驟延遲到子類中實現。這樣,新的子類可以在不改變一個演算法結構的前提下重新定義該演算法的某些特定步驟。 即:處理步驟父類中定義好 ...
  • 1、什麼變數 變數來源於數學,從根本上說,變數相當於是對一塊數據存儲空間的命名,程式可以通過定義一個變數來申請一塊數據存儲空間,之後可以通過引用變數名來使用這塊存儲空間。 1.1變數聲明 Go 語言變數名由字母、數字、下劃線組成,其中首個字母不能為數字。 第一種,指定變數類型,聲明後若不賦值,使用默 ...
  • 下載MyEclipse10以及破解包 "MyEclipse10:" 提取碼:020c "破解包" 提取碼:mycj 註:破解包內含有破解教程,很詳細,這裡就不多說了 MyEclipse10漢化 操作系統:win10 "漢化包" 提取碼:nbsb 操作步驟 1:將漢化包中的language的文件夾全粘 ...
  • 1 考察知識點 本題考察的知識點有以下幾個: 1. Keys 和 Scan 的區別 2. Keys 查詢的缺點 3. Scan 如何使用? 4. Scan 查詢的特點 2 解答思路 1. Keys 查詢存在的問題 2. Scan 的使用 3. Scan 的特點 3 Keys 使用相關 1)Keys ...
  • springBoot1.x升級到springBoot2.x,出現預設的mysql驅動包版本的問題,需要修改jdbc驅動類和連接字元串 ...
  • 直接使用 CIL - .NET 上的彙編語言編寫 .NET Standard 類庫 ...
  • 目 錄 1. 概述... 1 2. iNeuDaemon部署... 2 3. iNeuDaemon配置監控服務項... 3 4. 應用效果... 3 1. 概述 iNeuDaemon是Windows平臺後臺守護進程,用於監測服務進程,如果出現異常退出、遠程運維重新啟動等場景,那麼可以部署iNeuDa ...
  • 實現方式:WinForm自定義控制項,繼承系統Label控制項實現。 第1步:創建“組件”,取名為:MarkLabel 第2步:修改添加如下代碼: /* 添加命名空間引用: * using System.Windows.Forms; * using System.Drawing; * */ public ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...