【Socket】蒼老師有了丈夫,我也有了SAEA

来源:https://www.cnblogs.com/lovecsharp094/archive/2018/01/02/8177146.html
-Advertisement-
Play Games

一、前言 時間過得真是快,轉眼就2018年了。首先祝各位博友,軟體開發者新年新氣象,事業有成,身體健康,闔家幸福!最近看到園子里好多關於自己的2017年度總結以及對自己新一年的願景,覺得咱園子的氛圍是真的好。這三天假期我也沒閑著,一邊看OB海鮮團吃雞一邊寫Socket SocketAsyncEven ...


一、前言

       時間過得真是快,轉眼就2018年了。首先祝各位博友,軟體開發者新年新氣象,事業有成,身體健康,闔家幸福!最近看到園子里好多關於自己的2017年度總結以及對自己新一年的願景,覺得咱園子的氛圍是真的好。這三天假期我也沒閑著,一邊看OB海鮮團吃雞一邊寫Socket SocketAsyncEventArgs 代碼。我上一篇博客已經用APM的方式實現了客戶端與伺服器端的Socket通信,並具有了一定的併發能力。所以這三天我就決定對伺服器代碼進行改造,使用MS在4.0時發佈的SocketAsyncEventArgs(SAEA)寫法。為了方便的進行伺服器端兩種寫法的對比,我客戶端的代碼沒有進行變化,依然使用APM方式。代碼已經上傳至Github,鏈接會在文末貼出。

 

二、我的業務功能

       我的業務功能依然是實現從伺服器多線程下載更新文件。下載之前的那些操作我基本就不講了,上一篇博文里的都有,本文還是回到Socket下載文件上。具體流程如下:

       在我寫SAEA代碼之前,我仔細搜了一下網上的資源:MSDN、CNBLOG、CSDN、CodeProject。這四種來源的代碼示例的主要流程是這樣的:

      對比我的流程,您會發現少了一半的通信過程。客戶端的代碼好寫,但是伺服器端如何發送完數據之後再接收數據?這中間的銜接過程還是有點門道的。特別是SAEA的代碼採用了Buffer池化以及SAEA池化之後,裡面有些小的細節就要想清楚了。下麵就是具體的代碼,我會以我自己的視角去論述APM與SAEA到底有什麼區別。

 

三、對比

     其實對於伺服器端的APM,我覺得最重要的並不是代碼中的BeginXXX或者是EndXXX,因為這就是APM寫法的特征,BeginXXX或者EndXXX然後裡面有一個回調函數,在回調函數里去做一些業務上的事情。最重要的是要有一個線程等待的概念,也就是代碼中的ManualResetEvent這個東西,它就像地鐵閘機一樣,處理好一個再放一個進去。APM寫法的好處是顯而易見的,就是代碼看起來十分的簡單。缺點依照MS的說法就是如果有過多的客-服交流,可能會產生較多的IAsyncResult對象,這樣會增加伺服器的開銷。  

     伺服器端的APM寫法:

  1 using System;
  2 using System.IO;
  3 using System.Linq;
  4 using System.Net;
  5 using System.Net.Sockets;
  6 using System.Threading;
  7 using UpdaterShare.GlobalSetting;
  8 using UpdaterShare.Model;
  9 using UpdaterShare.Utility;
 10 
 11 namespace UpdaterServerAPM
 12 {  
 13     public static class ServerSocket
 14     {
 15         private static int _downloadChannelsCount;
 16         private static string _serverPath;
 17         private static readonly ManualResetEvent AllDone = new ManualResetEvent(false);
 18 
 19         public static void StartServer(int port, int backlog)
 20         {         
 21             _downloadChannelsCount = DownloadSetting.DownloadChannelsCount;
 22             try
 23             {
 24                 IPAddress ipAddress = IPAddress.Any;
 25                 IPEndPoint localEndPoint = new IPEndPoint(ipAddress, port);
 26                 Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
 27                 listener.Bind(localEndPoint);
 28                 listener.Listen(backlog);
 29 
 30                 while (true)
 31                 {
 32                     AllDone.Reset();
 33                     listener.BeginAccept(AcceptCallback, listener);
 34                     AllDone.WaitOne();
 35                 }
 36             }
 37             catch (Exception ex)
 38             {
 39                 var path = $"{AppDomain.CurrentDomain.BaseDirectory}\\RunLog.txt";
 40                 File.AppendAllText(path, ex.Message);
 41             }
 42         }
 43 
 44 
 45         private static void AcceptCallback(IAsyncResult ar)
 46         {
 47             AllDone.Set();
 48             Socket listener = (Socket)ar.AsyncState;
 49             Socket handler = listener.EndAccept(ar);
 50             ComObject state = new ComObject { WorkSocket = handler };
 51             handler.BeginReceive(state.Buffer, 0, ComObject.BufferSize, 0, FindUpdateFileCallback, state);
 52         }
 53 
 54 
 55         private static void FindUpdateFileCallback(IAsyncResult ar)
 56         {
 57             ComObject state = (ComObject)ar.AsyncState;
 58             Socket handler = state.WorkSocket;
 59             int bytesRead = handler.EndReceive(ar);
 60             if (bytesRead > 0)
 61             {
 62                 var receiveData = state.Buffer.Take(bytesRead).ToArray();
 63                 var dataList = PacketUtils.SplitBytes(receiveData, PacketUtils.ClientFindFileInfoTag());
 64                 if (dataList != null && dataList.Any())
 65                 {
 66                     var request = PacketUtils.GetData(PacketUtils.ClientFindFileInfoTag(), dataList.FirstOrDefault());
 67                     string str = System.Text.Encoding.UTF8.GetString(request);
 68                     var infos = str.Split('_');
 69                     var productName = infos[0];
 70                     var revitVersion = infos[1];
 71                     var currentVersion = infos[2];
 72 
 73                     var mainFolder = AppDomain.CurrentDomain.BaseDirectory.Replace("bin", "TestFile");
 74                     var serverFileFolder = Path.Combine(mainFolder, "Server");
 75                     var serverFileFiles = new DirectoryInfo(serverFileFolder).GetFiles();
 76                    
 77                     var updatefile  = serverFileFiles.FirstOrDefault(x=>x.Name.Contains(productName) && x.Name.Contains(revitVersion) && x.Name.Contains(currentVersion));
 78                     if (updatefile != null)
 79                     {
 80                         if (string.IsNullOrEmpty(updatefile.FullName) || !File.Exists(updatefile.FullName)) return;
 81                         _serverPath = updatefile.FullName;
 82                         FoundUpdateFileResponse(handler);
 83                     }
 84                 }
 85             }
 86         }
 87 
 88 
 89         private static void FoundUpdateFileResponse(Socket handler)
 90         {
 91             byte[] foundUpdateFileData = PacketUtils.PacketData(PacketUtils.ServerFoundFileInfoTag(),null);
 92             ComObject state = new ComObject { WorkSocket = handler };
 93             handler.BeginSend(foundUpdateFileData, 0, foundUpdateFileData.Length, 0, HasFoundUpdateFileCallback, state);
 94         }
 95 
 96 
 97         private static void HasFoundUpdateFileCallback(IAsyncResult ar)
 98         {
 99             ComObject state = (ComObject)ar.AsyncState;
100             Socket handler = state.WorkSocket;
101             handler.EndSend(ar);
102             handler.BeginReceive(state.Buffer, 0, ComObject.BufferSize, 0, ReadFilePositionRequestCallback, state);
103         }
104 
105 
106         private static void ReadFilePositionRequestCallback(IAsyncResult ar)
107         {
108             ComObject state = (ComObject)ar.AsyncState;
109             Socket handler = state.WorkSocket;
110             int bytesRead = handler.EndReceive(ar);
111             if (bytesRead > 0)
112             {
113                 var receiveData = state.Buffer.Take(bytesRead).ToArray();
114                 var dataList = PacketUtils.SplitBytes(receiveData, PacketUtils.ClientRequestFileTag());
115                 if (dataList != null)
116                 {
117                     foreach (var request in dataList)
118                     {
119                         if (PacketUtils.IsPacketComplete(request))
120                         {
121                             int startPosition = PacketUtils.GetRequestFileStartPosition(request); 
122                             SendFileResponse(handler, startPosition);
123                         }
124                     }
125                 }
126             }
127         }
128 
129         private static void SendFileResponse(Socket handler, int startPosition)
130         {
131             var packetSize = PacketUtils.GetPacketSize(_serverPath, _downloadChannelsCount);
132             if (packetSize != 0)
133             {
134                 byte[] filedata = FileUtils.GetFile(_serverPath, startPosition, packetSize);
135                 byte[] packetNumber = BitConverter.GetBytes(startPosition/packetSize);
136                 if (filedata != null)
137                 {
138                     byte[] segmentedFileResponseData = PacketUtils.PacketData(PacketUtils.ServerResponseFileTag(), filedata, packetNumber);
139                     ComObject state = new ComObject {WorkSocket = handler};
140                     handler.BeginSend(segmentedFileResponseData, 0, segmentedFileResponseData.Length, 0, SendFileResponseCallback, state);
141                 }
142             }
143             else
144             {               
145                 handler.Shutdown(SocketShutdown.Both);
146                 handler.Close();
147             }       
148         }
149 
150 
151         private static void SendFileResponseCallback(IAsyncResult ar)
152         {
153             try
154             {
155                 ComObject state = (ComObject)ar.AsyncState;
156                 Socket handler = state.WorkSocket;
157                 handler.EndSend(ar);
158                 handler.Shutdown(SocketShutdown.Both);
159                 handler.Close();             
160             }
161             catch (Exception e)
162             {
163 
164             }
165         }
166     }
167 }

        說到SAEA,我覺得初入的小伙伴一定要先看MSDN上的實例,特別是它的BufferManager以及SocketAsyncEventArgsPool是怎麼寫的,到底是乾什麼用的。這裡我可以簡單的說下:SocketAsyncEventArgsPool是用來存放SAEA對象的,其個數依賴於你伺服器所能承擔的隊列長度,比如說我伺服器能承擔100個客戶的等待,我就在伺服器端生成100個SAEA對象放在池子里,當有客戶來連接時,我從池子里取出一個來和他對接。客戶走了,我再扔到池子里去。BufferManager則是對池子里的SAEA對象進行Buffer分配的,也相當於一個池子,這個池子的大小是隊列長度*通信緩存長度*2,乘以2是因為讀與寫是分開的。通信緩存長度很好理解,客戶端要傳個2G的信息給伺服器端不可能一下子接收2G,肯定是一口一口吃,那麼這一口的大小就是通信緩存長度。那麼分配給每個SAEA的緩存是多大呢?當然就是通信緩存長度的大小咯。註意!!註意!!註意!!既然是池化了,所有關於Buffer的操作都要圍繞分配給SAEA的Buffer去操作!見148-149行當伺服器拿著分配到的Buffer去接收信息後,如果再要發送信息,所要做的第一件事就是先清空分配的Buffer再使用,BufferManager給你分配哪段你就用哪段,別使用錯了。有幾個參數需要註意:e.Offset(偏移),e.Count(大小),e.Buffer(緩存位元組數組), e.BytesTransferred(通信傳輸的位元組長度)。如果伺服器端要發送數據,一定要用Array.Copy將信息寫入對應分配的Buffer中。

     說完池化,接著就是寫法上的小區別,我覺得區別並不大,無非就是委托換了個寫法。當然還要判斷下是否為非同步操作,如果是否則需要進行同步操作,見82-85行代碼。

       伺服器的SAEA寫法:

  1 using System;
  2 using System.IO;
  3 using System.Linq;
  4 using System.Net;
  5 using System.Net.Sockets;
  6 using System.Threading;
  7 using UpdaterShare.GlobalSetting;
  8 using UpdaterShare.Model;
  9 using UpdaterShare.Utility;
 10 
 11 namespace UpdaterServerSAEA
 12 {
 13     public class ServerSocket
 14     {
 15         private readonly int _port;
 16         private readonly int _backlog;
 17         private Socket _listenSocket;
 18         private const int _opsToPreAlloc = 2;
 19         private readonly BufferManager _bufferManager;
 20         private readonly SocketAsyncEventArgsPool _readWritePool;
 21         private readonly Semaphore _maxNumberAcceptedClients;
 22 
 23         private string _serverPath;
 24         private static readonly int _downloadChannelsCount = DownloadSetting.DownloadChannelsCount;
 25 
 26         public ServerSocket(int port, int backlog)
 27         {
 28             _port = port;
 29             _backlog = backlog;
 30 
 31             _bufferManager = new BufferManager(ComObject.BufferSize * backlog * _opsToPreAlloc, ComObject.BufferSize);
 32             _readWritePool = new SocketAsyncEventArgsPool(backlog);
 33             _maxNumberAcceptedClients = new Semaphore(backlog, backlog);
 34         }
 35 
 36 
 37         private void Init()
 38         {
 39             _bufferManager.InitBuffer();
 40 
 41             for (var i = 0; i < _backlog; i++)
 42             {
 43                 var readWriteEventArg = new SocketAsyncEventArgs();
 44                 _bufferManager.SetBuffer(readWriteEventArg);
 45                 _readWritePool.Push(readWriteEventArg);
 46             }
 47         }
 48 
 49 
 50         public void StartServer()
 51         {
 52             try
 53             {
 54                 Init();
 55                 IPAddress ipAddress = IPAddress.Any;
 56                 IPEndPoint localEndPoint = new IPEndPoint(ipAddress, _port);
 57                 _listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
 58                 _listenSocket.Bind(localEndPoint);
 59                 _listenSocket.Listen(_backlog);
 60                 StartAccept(null);
 61             }
 62             catch (Exception ex)
 63             {
 64                 Console.WriteLine(ex.Message);
 65             }
 66         }
 67 
 68         private void StartAccept(SocketAsyncEventArgs acceptEventArg)
 69         {
 70             if (acceptEventArg == null)
 71             {
 72                 acceptEventArg = new SocketAsyncEventArgs();
 73                 acceptEventArg.Completed += StartAccept_Completed;
 74             }
 75             else
 76             {
 77                 acceptEventArg.AcceptSocket = null;
 78             }
 79 
 80             _maxNumberAcceptedClients.WaitOne();
 81 
 82             if (!_listenSocket.AcceptAsync(acceptEventArg))
 83             {
 84                 ProcessAccept(acceptEventArg);
 85             }
 86         }
 87 
 88         private void StartAccept_Completed(object sender, SocketAsyncEventArgs e)
 89         {
 90             ProcessAccept(e);
 91         }
 92 
 93 
 94         private void ProcessAccept(SocketAsyncEventArgs e)
 95         {
 96             if (e.SocketError == SocketError.Success)
 97             {
 98                 var socket = e.AcceptSocket;
 99                 if (socket.Connected)
100                 {
101                     SocketAsyncEventArgs readEventArgs = _readWritePool.Pop();
102                     readEventArgs.AcceptSocket = socket;
103                     readEventArgs.Completed += ProcessAccept_Completed;
104                     if (!socket.ReceiveAsync(readEventArgs))
105                     {
106                         ProcessReceiveFindFileRequest(readEventArgs);
107                     }
108                     StartAccept(e);
109                 }
110             }
111         }
112 
113         private void ProcessAccept_Completed(object sender, SocketAsyncEventArgs e)
114         {
115             ProcessReceiveFindFileRequest(e);
116         }
117 
118 
119         private void ProcessReceiveFindFileRequest(SocketAsyncEventArgs e)
120         {
121             var bytesRead = e.BytesTransferred;
122             if (bytesRead > 0 && e.SocketError == SocketError.Success)
123             {
124                 var receiveData = e.Buffer.Skip(e.Offset).Take(bytesRead).ToArray();
125                 var dataList = PacketUtils.SplitBytes(receiveData, PacketUtils.ClientFindFileInfoTag());
126                 if (dataList != null && dataList.Any())
127                 {
128                     var request = PacketUtils.GetData(PacketUtils.ClientFindFileInfoTag(), dataList.FirstOrDefault());
129                     string str = System.Text.Encoding.UTF8.GetString(request);
130                     var infos = str.Split('_');
131                     var productName = infos[0];
132                     var revitVersion = infos[1];
133                     var currentVersion = infos[2];
134 
135                     var mainFolder = AppDomain.CurrentDomain.BaseDirectory.Replace("bin", "TestFile");
136                     var serverFileFolder = Path.Combine(mainFolder, "Server");
137                     var serverFileFiles = new DirectoryInfo(serverFileFolder).GetFiles();
138 
139                     var updatefile = serverFileFiles.FirstOrDefault(x => x.Name.Contains(productName) && x.Name.Contains(revitVersion) && x.Name.Contains(currentVersion));
140                     if (updatefile != null)
141                     {
142                         if (string.IsNullOrEmpty(updatefile.FullName) || !File.Exists(updatefile.FullName)) return;
143                         _serverPath = updatefile.FullName;
144 
145                         //ready to send back to Client
146                         byte[] foundUpdateFileData = PacketUtils.PacketData(PacketUtils.ServerFoundFileInfoTag(), null);
147 
148                         Array.Clear(e.Buffer, e.Offset, e.Count);
149                         Array.Copy(foundUpdateFileData, 0, e.Buffer, e.Offset, foundUpdateFileData.Length);
150 
151                         e.Completed -= ProcessAccept_Completed;
152                         e.Completed += ProcessReceiveFindFileRequest_Completed;
153 
154                         if (!e.AcceptSocket.SendAsync(e))
155                         {
156                             ProcessFilePosition(e);
157                         }
158                     }
159                 }
160             }
161         }
162 
163 
164         private void ProcessReceiveFindFileRequest_Completed(object sender, SocketAsyncEventArgs e)
165         {
166             ProcessFilePosition(e);
167         }
168 
169 
170         private void ProcessFilePosition(SocketAsyncEventArgs e)
171         {
172             if (e.SocketError == SocketError.Success)
173             {
174                 var socket = e.AcceptSocket;
175                 if (socket.Connected)
176                 {
177                     //clear buffer
178                     Array.Clear(e.Buffer, e.Offset, e.Count);
179 
180                     e.Completed -= ProcessReceiveFindFileRequest_Completed;
181                     e.Completed += ProcessFilePosition_Completed;
182 
183                     if (!socket.ReceiveAsync(e))
184                     {
185                         ProcessSendFile(e);
186                     }
187                 }
188             }
189         }
190 
191         private void ProcessFilePosition_Completed(object sender, SocketAsyncEventArgs e)
192         {
193             ProcessSendFile(e);
194         }
195 
196         private void ProcessSendFile(SocketAsyncEventArgs e)
197         {
198             var bytesRead = e.BytesTransferred;
199             if (bytesRead > 0 && e.SocketError == SocketError.Success)
200             {
201                 var receiveData = e.Buffer.Skip(e.Offset).Take(bytesRead).ToArray();
202                 var dataList = PacketUtils.SplitBytes(receiveData, PacketUtils.ClientRequestFileTag());
203                 if (dataList != null)
204                 {
205                     foreach (var request in dataList)
206                     {
207                         if (PacketUtils.IsPacketComplete(request))
208                         {
209                             int startPosition = PacketUtils.GetRequestFileStartPosition(request);
210 
211                             var packetSize = PacketUtils.GetPacketSize(_serverPath, _downloadChannelsCount);
212                             if (packetSize != 0)
213                             {
214                                 byte[] filedata = FileUtils.GetFile(_serverPath, startPosition, packetSize);
215                                 byte[] packetNumber = BitConverter.GetBytes(startPosition / packetSize);
216 
217                                 Console.WriteLine("Receive File Request PacketNumber: "+startPosition / packetSize);
218 
219                                 if (filedata != null)
220                                 {
221                                     //ready to send back to Client
222                                     byte[] segmentedFileResponseData = PacketUtils.PacketData(PacketUtils.ServerResponseFileTag(), filedata, packetNumber);
223 
224                                     Array.Clear(e.Buffer, e.Offset, e.Count);
225                                     Array.Copy(segmentedFileResponseData, 0, e.Buffer, e.Offset, segmentedFileResponseData.Length);
226 
227                                     e.Completed -= ProcessFilePosition_Completed;
228                                     e.Completed += ProcessSendFile_Completed;
229 
230                                     if (!e.AcceptSocket.SendAsync(e))
231                                     {
232                                         CloseClientSocket(e);
233                                     }
234                                 }
235                             }
236                         }
237                     }
238                 }
239             }
240             else
241             {
242                 CloseClientSocket(e);
243             }
244         }
245 
246 
247         private void ProcessSendFile_Completed(object sender, SocketAsyncEventArgs e)
248         {
249             CloseClientSocket(e);
250         }
251 
252 
253         private void CloseClientSocket(SocketAsyncEventArgs e)
254         {
255             try
256             {
257                 e.AcceptSocket.Shutdown(SocketShutdown.Both);
258                 e.AcceptSocket.Close();
259             }
260             catch (Exception ex)
261             {
262                 Console.WriteLine(ex.Message);
263             }
264             finally
265             {
266                 _maxNumberAcceptedClients.Release();
267              

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

-Advertisement-
Play Games
更多相關文章
  • 題目描述 棟棟最近迷上了隨機演算法,而隨機數是生成隨機演算法的基礎。棟棟準備使用線性同餘法(Linear Congruential Method)來生成一個隨機數列,這種方法需要設置四個非負整數參數m,a,c,X[0],按照下麵的公式生成出一系列隨機數{Xn}: 其中mod m表示前面的數除以m的餘數。 ...
  • 作為複習總結的筆記,我羅列了幾個jdbc步驟,後邊舉個簡單的例子,其中的try塊請讀者自行處理. /* * 1.下載驅動包:com.mysql.jdbc.Driver;網上很多下載資源,自己找度娘,此處不再提供; * * 2.將驅動包導入項目,並add to build path,具體步驟去問度娘. ...
  • 一 概述 在我們講解NuGet前,我們先來看看一個例子。 1.例子: 假設現在開發一套系統,其中前端框架我們選擇Bootstrap,由於選擇Bootstrap作為前端框架,因此,在項目中,我們需要引入Bootstrap相關文件,大致會執行如下操作。 步驟一:下載Bootstrap文件 輸入Boots ...
  • 示例代碼: 執行結果圖: ...
  • 最近因為業務需求開發了一個介面用於接收數據,但是總有一些數據報出ORA-01704:字元串文字太長錯誤。仔細排查後發現,竟然是NCLOB類型欄位提示這個錯誤。NCLOB存儲空間有4G,怎麼也想不明白為什麼會報這個錯誤。原來因為介面插入數據採用字元串拼接的方式。 而oracle中會把字元串轉為varc ...
  • 1.定義一個枚舉,樂器類型 #region 樂器定義 public enum MusicNo:uint { /// <summary> /// //大鋼琴 /// </summary> AcousticGrandPiano = 0, /// <summary> ///明亮的鋼琴 /// </summ ...
  • 一、新建項目 打開vs2017,新建一個項目,命名為orm1 二、新建資料庫 打開 SqlServer資料庫,新建資料庫 orm1,並新建表 student 。 三、新建 ADO.NET 實體數據模型 這裡點擊 新建連接,新建資料庫連接。其實伺服器名輸入 . 代表本地伺服器,身份驗證選擇預設的Win ...
  • 寫在前面 整個項目都托管在了 Github 上:https://github.com/ikesnowy/Algorithms-4th-Edition-in-Csharp 這一節內容可能會用到的庫文件有 Sort和 SortData,同樣在 Github 上可以找到。 善用 Ctrl + F 查找題目 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...