SuperSocket入門(五)-常用協議實現模版及FixedSizeReceiveFilter示例

来源:http://www.cnblogs.com/fly-bird/archive/2017/01/23/6345084.html
-Advertisement-
Play Games

Socket裡面的協議解析是Socket通訊程式設計中最複雜的地方,如果你的應用層協議設計或實現不佳,Socket通訊中常見的粘包,分包就難以避免。SuperSocket內置了命令行格式的協議CommandLineProtocol,如果你使用了其它格式的協議,就必須自行實現自定義協議CustomPr ...


         Socket裡面的協議解析是Socket通訊程式設計中最複雜的地方,如果你的應用層協議設計或實現不佳,Socket通訊中常見的粘包,分包就難以避免。SuperSocket內置了命令行格式的協議CommandLineProtocol,如果你使用了其它格式的協議,就必須自行實現自定義協議CustomProtocol。看了一篇文檔之後, 你可能會覺得用 SuperSocket 來實現你的自定義協議並不簡單。 為了讓這件事變得更容易一些, SuperSocket 提供了一些通用的協議解析工具, 你可以用他們簡單而且快速的實現你自己的通信協議:
  • TerminatorReceiveFilter (SuperSocket.SocketBase.Protocol.TerminatorReceiveFilter, SuperSocket.SocketBase) ---結束符協議
  • CountSpliterReceiveFilter (SuperSocket.Facility.Protocol.CountSpliterReceiveFilter, SuperSocket.Facility)---固定數量分隔符協議
  • FixedSizeReceiveFilter (SuperSocket.Facility.Protocol.FixedSizeReceiveFilter, SuperSocket.Facility)---固定請求大小協議
  • BeginEndMarkReceiveFilter (SuperSocket.Facility.Protocol.BeginEndMarkReceiveFilter, SuperSocket.Facility)---帶起止符協議
  • FixedHeaderReceiveFilter (SuperSocket.Facility.Protocol.FixedHeaderReceiveFilter, SuperSocket.Facility)---頭部格式固定並包含內容長度協議

     1、TerminatorReceiveFilter結束符協議

 結束符協議和命令行協議類似,一些協議用結束符來確定一個請求.例如, 一個協議使用兩個字元 "##" 作為結束符, 於是你可以使用類 "TerminatorReceiveFilterFactory":

           結束符協議TerminatorProtocolServer 

public class TerminatorProtocolServer : AppServer
{ 
    public TerminatorProtocolServer()
        : base(new TerminatorReceiveFilterFactory("##"))
    {

    }
}

基於TerminatorReceiveFilter實現你的接收過濾器(ReceiveFilter):

public class YourReceiveFilter : TerminatorReceiveFilter<YourRequestInfo>
{
    //More code
}

實現你的接收過濾器工廠(ReceiveFilterFactory)用於創建接受過濾器實例:

public class YourReceiveFilterFactory : IReceiveFilterFactory<YourRequestInfo>
{
    //More code
}

     2、CountSpliterReceiveFilter 固定數量分隔符協議

有些協議定義了像這樣格式的請求 "#part1#part2#part3#part4#part5#part6#part7#". 每個請求有7個由 '#' 分隔的部分. 這種協議的實現非常簡單:

/// <summary>
/// 請求格式:#part1#part2#part3#part4#part5#part6#part7#
/// </summary>
public class CountSpliterAppServer : AppServer
{
    public CountSpliterAppServer()
        : base(new CountSpliterReceiveFilterFactory((byte)'#', 8)) //8個分隔符,7個參數。除使用預設的過濾工廠,還可以參照上一個實例定製協議
    {

    }
}

     3、FixedSizeReceiveFilter 固定請求大小協議

在這種協議之中, 所有請求的大小都是相同的。如果你的每個請求都是有8個字元組成的字元串,如"HUANG LI", 你應該做的事就是想如下代碼這樣實現一個接收過濾器(ReceiveFilter):

class MyReceiveFilter : FixedSizeReceiveFilter<StringRequestInfo>
{
    public MyReceiveFilter()
        : base(8) //傳入固定的請求大小
    {

    }

    protected override StringRequestInfo ProcessMatchedRequest(byte[] buffer, int offset, int length, bool toBeCopied)
    {
        //TODO: 通過解析到的數據來構造請求實例,並返回
    }
}

然後在你的 AppServer 類中使用這個接受過濾器 (ReceiveFilter):

public class MyAppServer : AppServer
{
    public MyAppServer()
        : base(new DefaultReceiveFilterFactory<MyReceiveFilter, StringRequestInfo>()) //使用預設的接受過濾器工廠 (DefaultReceiveFilterFactory)
    {

    }
}

     4、BeginEndMarkReceiveFilter 帶起止符協議

在這類協議的每個請求之中 都有固定的開始和結束標記。例如, 我有個協議,它的所有消息都遵循這種格式 "&xxxxxxxxxxxxxx#"。因此,在這種情況下, "&" 是開始標記, "#" 是結束標記,於是你的接受過濾器可以定義成這樣:

class MyReceiveFilter : BeginEndMarkReceiveFilter<StringRequestInfo>
{
    //開始和結束標記也可以是兩個或兩個以上的位元組
    private readonly static byte[] BeginMark = new byte[] { (byte)'&' };
    private readonly static byte[] EndMark = new byte[] { (byte)'#' };

    public MyReceiveFilter()
        : base(BeginMark, EndMark) //傳入開始標記和結束標記
    {

    }

    protected override StringRequestInfo ProcessMatchedRequest(byte[] readBuffer, int offset, int length)
    {
        //TODO: 通過解析到的數據來構造請求實例,並返回
    }
}

然後在你的 AppServer 類中使用這個接受過濾器 (ReceiveFilter):

public class MyAppServer : AppServer
{
    public MyAppServer()
        : base(new DefaultReceiveFilterFactory<MyReceiveFilter, StringRequestInfo>()) //使用預設的接受過濾器工廠 (DefaultReceiveFilterFactory)
    {

    }
}

     5、FixedHeaderReceiveFilter 頭部格式固定並包含內容長度協議

這種協議將一個請求定義為兩大部分, 第一部分定義了包含第二部分長度等等基礎信息. 我們通常稱第一部分為頭部.

例如, 我們有一個這樣的協議: 頭部包含 6 個位元組, 前 4 個位元組用於存儲請求的名字, 後兩個位元組用於代表請求體的長度:

/// +-------+---+-------------------------------+
/// |request| l |                               |
/// | name  | e |    request body               |
/// |  (4)  | n |                               |
/// |       |(2)|                               |
/// +-------+---+-------------------------------+

使用 SuperSocket, 你可以非常方便的實現這種協議:

class MyReceiveFilter : FixedHeaderReceiveFilter<BinaryRequestInfo>
{
    public MyReceiveFilter()
        : base(6)
    {

    }

    protected override int GetBodyLengthFromHeader(byte[] header, int offset, int length)
    {
        return (int)header[offset + 4] * 256 + (int)header[offset + 5];
    }

    protected override BinaryRequestInfo ResolveRequestInfo(ArraySegment<byte> header, byte[] bodyBuffer, int offset, int length)
    {
        return new BinaryRequestInfo(Encoding.UTF8.GetString(header.Array, header.Offset, 4), bodyBuffer.CloneRange(offset, length));
    }
}

你需要基於類FixedHeaderReceiveFilter實現你自己的接收過濾器.

    • 傳入父類構造函數的 6 表示頭部的長度;
    • 方法"GetBodyLengthFromHeader(...)" 應該根據接收到的頭部返回請求體的長度;
    • 方法 ResolveRequestInfo(....)" 應該根據你接收到的請求頭部和請求體返回你的請求類型的實例.

    實際使用場景:

     到這裡五種協議的模板你都已經瞭解了一遍,並且知道了相關的格式處理。接下來看一個網路示例:

通訊協議格式:

      在看到上圖協議是在糾結客戶端發送16進位,伺服器怎麼接收,16進位的報文如下:

26 01 00 19 4E 4A 30 31 31 01 44 41 31 31 32 00 07 00 00 00 00 00 00 34 23

     16進位也好,10進位也好,其他的進位也好,最終都是轉換成byte[],其實在處理數據時,發送過去的數據都是可以轉換成為byte[]的,所以服務的只要解析byte[]數組就行了。按照協議來解析就能得到想要的數據。下麵使用FixedSizeReceiveFilter的例子,代碼如下:

根據上面的通訊協議,開始來實現解析:

第一步、定義一個和協議合適的數據結構

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

/****************************************************************
*   作者:黃昏前黎明後
*   CLR版本:4.0.30319.42000
*   創建時間:2017-01-23 21:12:30
*   2017
*   描述說明:協議數據包
*
*   修改歷史:
*
*
*****************************************************************/
namespace SuperSocketDemo
{
   public class HLData
    {
        /// <summary>
        /// 開始符號
        /// </summary>
        public char Head { get; set; }
        /// <summary>
        /// 協議包數據
        /// </summary>
        public byte Ping { get; set; }
        /// <summary>
        /// 數據長度
        /// </summary>
        public ushort Lenght { get; set; }
        /// <summary>
        /// 終端ID
        /// </summary>
        public uint FID { get; set; }
        /// <summary>
        /// 目標類型
        /// </summary>
        public byte Type { get; set; }
        /// <summary>
        /// 轉發終端ID
        /// </summary>
        public uint SID { get; set; }
        /// <summary>
        /// 發送計數
        /// </summary>
        public ushort SendCount { get; set; }
       /// <summary>
       /// 保留欄位
       /// </summary>
        public byte[] Retain { get; set; }
        /// <summary>
        /// 異或校驗
        /// </summary>
        public byte Check { get; set; }
        /// <summary>
        /// 結束符號
        /// </summary>
        public char End { get; set; }
        
        public override string ToString()
        {
            return string.Format("開始符號:{0},包數據:{1},數據長度:{2},終端ID:{3},目標類型:{4},轉發終端ID:{5},發送包計數:{6},保留欄位:{7},異或校驗:{8},結束符號:{9}",
                Head, Ping, Lenght, FID, Type, SID, SendCount, Retain, Check, End);
        }


    }
}
HLData

      第二步、建立一個RequestInfo來給server數據接收

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SuperSocket.SocketBase.Protocol;

/****************************************************************
*   作者:黃昏前黎明後
*   CLR版本:4.0.30319.42000
*   創建時間:2017-01-22 21:03:31
*   2017
*   描述說明:數據請求
*
*   修改歷史:
*
*
*****************************************************************/
namespace SuperSocketDemo
{
   public class HLProtocolRequestInfo : RequestInfo<HLData>
    {
        public HLProtocolRequestInfo(HLData hlData)
        {
            //如果需要使用命令行協議的話,那麼命令類名稱HLData相同
            Initialize("HLData", hlData);
        }
    }
}
HLProtocolRequestInfo 類

     第三步、FixedSize協議解析

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SuperSocket.SocketBase.Protocol;
using SuperSocket.Facility.Protocol;
using SuperSocket.Common;

/****************************************************************
*   作者:黃昏前黎明後
*   CLR版本:4.0.30319.42000
*   創建時間:2017-01-22 21:06:01
*   2017
*   描述說明:協議解析類,固定請求大小的協議
*
*   修改歷史:
*
*
*****************************************************************/
namespace SuperSocketDemo
{
    /// <summary>
    /// 固定請求大小的協議,(幀格式為HLProtocolRequestInfo)
    /// </summary>
    public class HLProtocolReceiveFilter : FixedSizeReceiveFilter<HLProtocolRequestInfo>
    {
        public HLProtocolReceiveFilter() : base(25)//總的位元組長度 1+1+2+5+1+5+2+6+1+1 = 25
        {
        }

        protected override HLProtocolRequestInfo ProcessMatchedRequest(byte[] buffer, int offset, int length, bool toBeCopied)
        {
            var HLData = new HLData();
            HLData.Head = (char)buffer[offset];//開始標識的解析,1個位元組
            HLData.Ping = buffer[offset + 1];//數據,從第2位起,只有1個位元組
            HLData.Lenght = BitConverter.ToUInt16(buffer, offset + 2);//數據長度,從第3位開始,2個位元組
            HLData.FID = BitConverter.ToUInt32(buffer, offset + 4);//本終端ID,從第5位開始,5個位元組
            HLData.Type = buffer[offset + 9];//目標類型,從第10位開始,1個位元組
            HLData.SID = BitConverter.ToUInt32(buffer, offset + 10);//轉發終端ID,從第11位開始,5個位元組
            HLData.SendCount = BitConverter.ToUInt16(buffer, offset + 15);//發送包計數,從第16位開始,2個位元組
            HLData.Retain = buffer.CloneRange(offset + 17, 6);//保留欄位,從18位開始,6個位元組
            HLData.Check = buffer[offset + 23];//異或校驗,從24位開始,1個位元組
            HLData.End = (char)buffer[offset + 24];//結束符號,從第25位開始,一個位元組
            return new HLProtocolRequestInfo(HLData);
        }
    }
}
HLProtocolReceiveFilter類

     第四步、建立協議工廠HLReceiveFilterFactory

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Protocol;
using System.Net;

/****************************************************************
*   作者:黃昏前黎明後
*   CLR版本:4.0.30319.42000
*   創建時間:2017-01-23 :22:01:25
*   2017
*   描述說明:協議工廠
*
*   修改歷史:
*
*
*****************************************************************/
namespace SuperSocketDemo
{
    public class HLReceiveFilterFactory: IReceiveFilterFactory<HLProtocolRequestInfo>
    {
        public IReceiveFilter<HLProtocolRequestInfo> CreateFilter(IAppServer appServer, IAppSession appSession, IPEndPoint remoteEndPoint)
        {
            return new HLBeginEndMarkReceiveFilter();
        
        }
    }
}
HLReceiveFilterFactory類

     第五步、自定義HLProtocolSession繼承AppSession

using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Protocol;
using System;
/****************************************************************
*   作者:黃昏前黎明後
*   CLR版本:4.0.30319.42000
*   創建時間:2017-01-22 21:15:11
*   2017
*   描述說明:自定義HLProtocolSession
*
*   修改歷史:
*
*
*****************************************************************/
namespace SuperSocketDemo
{
    public class HLProtocolSession : AppSession<HLProtocolSession, HLProtocolRequestInfo>
    {
        protected override void HandleException(Exception e)
        {

        }
    
    }

}
HLProtocolSession類

     第六步、自定義HLProtocolServer繼承AppServer

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Protocol;

/****************************************************************
*   作者:黃昏前黎明後
*   CLR版本:4.0.30319.42000
*   創建時間:2017-01-22 21:16:57
*   2017
*   描述說明:自定義server
*
*   修改歷史:
*
*
*****************************************************************/
namespace SuperSocketDemo
{
  public class HLProtocolServer : AppServer<HLProtocolSession, HLProtocolRequestInfo>
    {
        /// <summary>
        /// 使用自定義協議工廠
        /// </summary>
        public HLProtocolServer()
            : base(new HLReceiveFilterFactory()) 
        {
        }
  
     
    }
}
HLProtocolServer類   

     第七步、加上起止符協議HLBeginEndMarkReceiveFilter

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SuperSocket.Common;
using SuperSocket.Facility.Protocol;

/****************************************************************
*   作者:黃昏前黎明後
*   CLR版本:4.0.30319.42000
*   創建時間:2017-01-23 22:07:03
*   2017
*   描述說明:帶起止符的協議, "&" 是開始標記, "#" 是結束標記,開始結束標記由自己定義
*
*   修改歷史:
*
*
*****************************************************************/
namespace SuperSocketDemo
{
    public class HLBeginEndMarkReceiveFilter : BeginEndMarkReceiveFilter<HLProtocolRequestInfo>
    {
        private readonly static char strBegin = '&';
        private readonly static char strEnd = '#';
        //開始和結束標記也可以是兩個或兩個以上的位元組
        private readonly static byte[] BeginMark = new byte[] { (byte)strBegin };
        private readonly static byte[] EndMark = new byte[] { (byte)strEnd };

        public HLBeginEndMarkReceiveFilter() : base(BeginMark, EndMark)
        {

        }

        /// <summary>
        /// 這裡解析的到的數據是會把頭和尾部都給去掉的
        /// </summary>
        /// <param name="readBuffer"></param>
        /// <param name="offset"></param>
        /// <param name="length"></param>
        /// <returns></returns>
        protected override HLProtocolRequestInfo ProcessMatchedRequest(byte[] readBuffer, int offset, int length)
        {
            var HLData = new HLData();
            HLData.Head = strBegin;//自己定義開始符號
            HLData.Ping = readBuffer[offset];//數據,從第1位起,只有1個位元組
            HLData.Lenght = BitConverter.ToUInt16(readBuffer, offset + 1);//數據長度,從第2位開始,2個位元組
            HLData.FID = BitConverter.ToUInt32(readBuffer, offset + 3);//本終端ID,從第4位開始,5個位元組
            HLData.Type = readBuffer[offset + 8];//目標類型,從第9位開始,1個位元組
            HLData.SID = BitConverter.ToUInt32(readBuffer, offset + 9);//轉發終端ID,從第10位開始,5個位元組
            HLData.SendCount = BitConverter.ToUInt16(readBuffer, offset + 14);//發送包計數,從第15位開始,2個位元組
            HLData.Retain = readBuffer.CloneRange(offset + 16, 6);//保留欄位,從17位開始,6個位元組
            HLData.Check = readBuffer[offset + 22];//異或校驗,從23位開始,1個位元組
            HLData.End = strEnd;//結束符號,自己定義
            return new HLProtocolRequestInfo(HLData);
        }
    }
}
HLBeginEndMarkReceiveFilter類

     第八步、服務啟動和停止

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Protocol;
using SuperSocket.SocketEngine;
/****************************************************************
*   作者:黃昏前黎明後
*   CLR版本:4.0.30319.42000
*   創建時間:2017-01-19 00:02:17
*   2017
*   描述說明:服務啟動和停止入口  
*
*   修改歷史: 2017 -01-19  調整自定義mysession和myserver
*              2017 -01-23  通訊協議解析,直接使用入口註冊事件
*
*****************************************************************/
namespace SuperSocketDemo
{
    class Program
    {
        /// <summary>
        /// SuperSocket服務啟動或停止
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            Console.WriteLine("請按任何鍵進行啟動SuperSocket服務!");
            Console.ReadKey();
            Console.WriteLine();

            var HLProtocolServer = new HLProtocolServer();
           // 設置埠號
            int port = 2017;
            //啟動應用服務埠
            if (!HLProtocolServer.Setup(port)) //啟動時監聽埠2017
            {
                Console.WriteLine("服務埠啟動失敗!");
                Console.ReadKey();
                return;
            }

            Console.WriteLine();
            //註冊連接事件
            HLProtocolServer.NewSessionConnected += HLProtocolServer_NewSessionConnected;
            //註冊請求事件
            HLProtocolServer.NewRequestReceived += HLProtocolServer_NewRequestReceived;
            //註冊Session關閉事件
            HLProtocolServer.SessionClosed += HLProtocolServer_SessionClosed;
            //嘗試啟動應用服務
            if (!HLProtocolServer.Start())
            {
                Console.WriteLine("服務啟動失敗!");
                Console.ReadKey();
                return;
            }
   
            Console.WriteLine("伺服器狀態:" + HLProtocolServer.State.ToString());
         
            Console.WriteLine("服務啟動成功,請按'E'停止服務!");

            while (Console.ReadKey().KeyChar != 'E')
            {
                Console.WriteLine();
                continue;
            }

            //停止服務
            HLProtocolServer.Stop();
            Console.WriteLine("服務已停止!");
            Console.ReadKey();
        }
      

        static void HLProtocolServer_SessionClosed(HLProtocolSession session, SuperSocket.SocketBase.CloseReason value)
        {
            Console.WriteLine(session.RemoteEndPoint.ToString() + "連接斷開. 斷開原因:" + value);
        }
        static void HLProtocolServer_NewSessionConnected(HLProtocolSession session)
        {
            Console.WriteLine(session.RemoteEndPoint.ToString() + " 已連接.");
        }
        /// <summary>
        /// 協議並沒有什麼太多複雜邏輯,不需要用到命令模式,直接用這種方式就可以了
        /// </summary>
        /// <param name="session"></param>
        /// <param name="requestInfo"></param>
        private static void HLProtocolServer_NewRequestReceived(HLProtocolSession session, HLProtocolRequestInfo requestInfo)
        {
            Console.WriteLine();
            Console.WriteLine("數據來源: " + session.RemoteEndPoint.ToString());
            Console.WriteLine("接收數據內容:"+requestInfo.Body);

        }
    }
}
Program類

     通訊協議需要使用小工具進行調試,本人使用的是TCP/UDP埠調試工具SocketTool V2.大家可以直接進行下載。使用HEX模式進行發送16進位報文,伺服器輸出結果:

 

本文參考官方文檔 內置的常用協議實現模版


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

-Advertisement-
Play Games
更多相關文章
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...