文件下載之斷點續傳(客戶端與服務端的實現)

来源:http://www.cnblogs.com/zhaopei/archive/2017/07/31/download.html
-Advertisement-
Play Games

前面講了 "文件的上傳" ,今天來聊聊文件的下載。 老規矩,還是從最簡單粗暴的開始。那麼多簡單算簡單?多粗暴算粗暴?我告訴你可以不寫一句代碼,你信嗎?直接把一個文件往IIS伺服器上一扔,就支持下載。還TM麽可以斷點續傳(IIS服務端預設支持)。 在貼代碼之前先來瞭解下什麼是斷點續傳( 這裡說的是下載 ...


前面講了文件的上傳,今天來聊聊文件的下載。

老規矩,還是從最簡單粗暴的開始。那麼多簡單算簡單?多粗暴算粗暴?我告訴你可以不寫一句代碼,你信嗎?直接把一個文件往IIS伺服器上一扔,就支持下載。還TM麽可以斷點續傳(IIS服務端預設支持)。

在貼代碼之前先來瞭解下什麼是斷點續傳(這裡說的是下載斷點續傳)?怎麼實現的斷點續傳?
斷點續傳就是下載了一半斷網或者暫停了,然後可以接著下載。不用從頭開始下載。

很神奇嗎,其實簡單得很,我們想想也是可以想到的。
首先客戶端向服務端發送一個請求(下載文件)。然後服務端響應請求,信息包含文件總大小、文件流開始和結束位置、內容大小等。那具體是怎麼實現的呢?
HTTP/1.1有個頭屬性Range。比如你發送請求的時候帶上Range:0-199,等於你是請求0到199之間的數據。然後伺服器響應請求Content-Range: bytes 0-199/250 ,表示你獲取了0到199之間的數據,總大小是250。(也就是告訴你還有數據沒有下載完)。

我們來畫個圖吧。

是不是很簡單?這麼神奇的東西也就是個“約定”而已,也就是所謂的HTTP協議。
然而,協議這東西你遵守它就存在,不遵守它就不存在。就像民國時期的錢大家都信它,它就有用。如果大部分人不信它,也就沒卵用了。
這個斷點續傳也是這樣。你服務端遵守就支持,不遵守也就不支持斷點續傳。所以我們寫下載工具的時候需要判斷響應報文里有沒有Content-Range,來確定是否支持斷點續傳。
廢話夠多了,下麵擼起袖子開乾。

文件下載-服務端

使用a標簽提供文件下載

利用a標簽來下載文件,也就是我們前面說的不寫代碼就可以實現下載。直接把文件往iis伺服器上一扔,然後把鏈接貼到a標簽上,完事。

<a href="/新建文件夾2.rar">下載</a>

簡單、粗暴不用說了。如真得這麼好那大家也不會費力去寫其他下載邏輯了。這裡有個致命的缺點。這種方式提供的下載不夠安全。誰都可以下載,沒有許可權控制,說不定還會被人文件掃描(好像csdn就出過這檔子事)。

使用Response.TransmitFile提供文件下載

上面說直接a標簽提供下載不夠安全。那我們怎麼提供相對安全的下載呢。asp.net預設App_Data文件夾是不能被直接訪問的,那我們把下載文件放這裡面。然後下載的時候我們讀取文件在返回到響應流。

//文件下載
public void FileDownload5()
{          
    //前面可以做用戶登錄驗證、用戶許可權驗證等。

    string filename = "大數據.rar";   //客戶端保存的文件名  
    string filePath = Server.MapPath("/App_Data/大數據.rar");//要被下載的文件路徑 

    Response.ContentType = "application/octet-stream";  //二進位流
    Response.AddHeader("Content-Disposition", "attachment;filename=" + filename);
    Response.TransmitFile(filePath); //將指定文件寫入 HTTP 響應輸出流
}

其他方式文件下載

在網上搜索C#文件下載一般都會搜到所謂的“四種方式”。其實那些代碼並不能拿來直接使用,有坑的。
第一種:(Response.BinaryWrite)

 public void FileDownload2()
 {
     string fileName = "新建文件夾2.rar";//客戶端保存的文件名  
     string filePath = Server.MapPath("/App_Data/新建文件夾2.rar");//要被下載的文件路徑   

     Response.ContentType = "application/octet-stream";//二進位流
     //通知瀏覽器下載文件而不是打開  
     Response.AddHeader("Content-Disposition", "attachment;  filename=" + HttpUtility.UrlEncode(fileName, System.Text.Encoding.UTF8));

     //以字元流的形式下載文件  
     using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
     {
         Response.AddHeader("Content-Length", fs.Length.ToString());
         //這裡容易記憶體溢出
         //理論上數組最大長度 int.MaxValue 2147483647 
         //(實際分不到這麼多,不同的程式能分到值也不同,本人機器,winfrom( 2147483591 相差56)、iis(也差不多2G)、iis Express(只有100多MB))
         byte[] bytes = new byte[(int)fs.Length];
         fs.Read(bytes, 0, bytes.Length);
         Response.BinaryWrite(bytes);
     }
     Response.Flush();
     Response.End();
 }

首先數組最大長度為int.MaxValue,然後正常程式是不會分這麼大記憶體,很容易搞掛伺服器。(也就是可以下載的文件,極限值最多也就2G不到。)【不推薦】

第二種:(Response.WriteFile)

public void FileDownload3()
{
    string fileName = "新建文件夾2.rar";//客戶端保存的文件名  
    string filePath = Server.MapPath("/App_Data/新建文件夾2.rar");//要被下載的文件路徑  
    FileInfo fileInfo = new FileInfo(filePath);
    Response.Clear();
    Response.ClearContent();
    Response.ClearHeaders();
    Response.AddHeader("Content-Disposition", "attachment;filename=\"" + HttpUtility.UrlEncode(fileName, System.Text.Encoding.UTF8) + "\"");
    Response.AddHeader("Content-Length", fileInfo.Length.ToString());//文件大小
    Response.AddHeader("Content-Transfer-Encoding", "binary");
    Response.ContentType = "application/octet-stream";
    Response.WriteFile(fileInfo.FullName);//大小參數必須介於零和最大的 Int32 值之間(也就是最大2G,不過這個操作非常耗記憶體)
    //這裡容易記憶體溢出
    Response.Flush();
    Response.End();
}

問題和第一種類似,也是不能下載大於2G的文件。然後下載差不多2G文件時,機器也是處在被掛的邊緣,相當恐怖。【不推薦】

第三種:(Response.OutputStream.Write)

public void FileDownload4()
{
    string fileName = "大數據.rar";//客戶端保存的文件名  
    string filePath = Server.MapPath("/App_Data/大數據.rar");//要被下載的文件路徑   

    if (System.IO.File.Exists(filePath))
    {
        const long ChunkSize = 102400; //100K 每次讀取文件,只讀取100K,這樣可以緩解伺服器的壓力  
        byte[] buffer = new byte[ChunkSize];

        Response.Clear();
        using (FileStream fileStream = System.IO.File.OpenRead(filePath))
        {
            long fileSize = fileStream.Length; //文件大小  
            Response.ContentType = "application/octet-stream"; //二進位流
            Response.AddHeader("Content-Disposition", "attachment; filename=" + HttpUtility.UrlEncode(fileName, System.Text.Encoding.UTF8));
            Response.AddHeader("Content-Length", fileStream.Length.ToString());//文件總大小
            while (fileSize > 0 && Response.IsClientConnected)//判斷客戶端是否還連接了伺服器
            {
                //實際讀取的大小  
                int readSize = fileStream.Read(buffer, 0, Convert.ToInt32(ChunkSize));
                Response.OutputStream.Write(buffer, 0, readSize);
                Response.Flush();//如果客戶端 暫停下載時,這裡會阻塞。
                fileSize = fileSize - readSize;//文件剩餘大小
            }
        }
        Response.Close();
    }
}

這裡明顯看到了是在迴圈讀取輸出,比較機智。下載大文件時沒有壓力。【推薦】

第四種:(Response.TransmitFile)
也就上開始舉例說的那種,下載大文件也沒有壓力。【推薦】

public void FileDownload5()
{          
    //前面可以做用戶登錄驗證、用戶許可權驗證等。

    string filename = "大數據.rar";   //客戶端保存的文件名  
    string filePath = Server.MapPath("/App_Data/大數據.rar");//要被下載的文件路徑 

    Response.ContentType = "application/octet-stream";  //二進位流
    Response.AddHeader("Content-Disposition", "attachment;filename=" + filename);
    Response.TransmitFile(filePath); //將指定文件寫入 HTTP 響應輸出流
}

文件下載-客戶端

上面實現了文件下載的服務端實現,接下來我們實現文件下載的客戶端實現。客戶端的下載可以直接是瀏覽器提供的下載,也可以是迅雷或者我們自己寫的下載程式。這裡為了更好的分析,我們來用winfrom程式自己寫個下載客戶端。

直接下載

private async void button1_ClickAsync(object sender, EventArgs e)
{
    using (HttpClient http = new HttpClient())
    {
        var httpResponseMessage = await http.GetAsync("http://localhost:813/新建文件夾2.rar");//發送請求 (鏈接是a標簽提供的)
        var contentLength = httpResponseMessage.Content.Headers.ContentLength;//讀取文件大小
        using (var stream = await httpResponseMessage.Content.ReadAsStreamAsync())//讀取文件流
        {
            var readLength = 1024000;//1000K  每次讀取大小
            byte[] bytes = new byte[readLength];
            int writeLength;
            while ((writeLength = stream.Read(bytes, 0, readLength)) > 0)//分塊讀取文件流
            {
                using (FileStream fs = new FileStream(Application.StartupPath + "/temp.rar", FileMode.Append, FileAccess.Write))//使用追加方式打開一個文件流
                {
                    fs.Write(bytes, 0, writeLength);//追加寫入文件
                    contentLength -= writeLength;
                    if (contentLength == 0)//如果寫入完成 給出提示
                        MessageBox.Show("下載完成");
                }
            }
        }
    } 
}

看著這麼漂亮的代碼,好像沒問題。可現實往往事與願違。

我們看到了一個異常“System.Net.Http.HttpRequestException:“不能向緩衝區寫入比所配置最大緩衝區大小 2147483647 更多的位元組。”,什麼鬼,又是2147483647這個數字。因為我們下載的文件大小超過了2G,無法緩衝下載。
可是“緩衝下載”下又是什麼鬼。我也不知道。那我們試試可以關掉這個東東呢?答案是肯定的。

var httpResponseMessage = await http.GetAsync("http://localhost:813/新建文件夾2.rar");//發送請求

改成下麵就可以了

var httpResponseMessage = await http.GetAsync("http://localhost:813/新建文件夾2.rar",HttpCompletionOption.ResponseHeadersRead);//響應一可用且標題可讀時即應完成的操作。 (尚未讀取的內容。)


我們看到枚舉HttpCompletionOption的兩個值。一個是響應讀取內容,一個是響應讀取標題(也就是Headers里的內容)。

非同步下載

我們發現在下載大文件的時候會造成界面假死。這是UI單線程程式的通病。當然,這麼差的用戶體驗是我們不能容忍的。下麵我們為下載開一個線程,避免造成UI線程的阻塞。

/// <summary>
/// 非同步下載
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void button2_ClickAsync(object sender, EventArgs e)
{
    //開啟一個非同步線程
    await Task.Run(async () =>
    {
        //非同步操作UI元素
        label1.Invoke((Action)(() =>
                {
                    label1.Text = "準備下載...";
                }));

        long downloadSize = 0;//已經下載大小
        long downloadSpeed = 0;//下載速度
        using (HttpClient http = new HttpClient())
        {
            var httpResponseMessage = await http.GetAsync("http://localhost:813/新建文件夾2.rar", HttpCompletionOption.ResponseHeadersRead);//發送請求
            var contentLength = httpResponseMessage.Content.Headers.ContentLength;   //文件大小                
            using (var stream = await httpResponseMessage.Content.ReadAsStreamAsync())
            {
                var readLength = 1024000;//1000K
                byte[] bytes = new byte[readLength];
                int writeLength;
                var beginSecond = DateTime.Now.Second;//當前時間秒
                while ((writeLength = stream.Read(bytes, 0, readLength)) > 0)
                {
                    //使用追加方式打開一個文件流
                    using (FileStream fs = new FileStream(Application.StartupPath + "/temp.rar", FileMode.Append, FileAccess.Write))
                    {
                        fs.Write(bytes, 0, writeLength);
                    }
                    downloadSize += writeLength;
                    downloadSpeed += writeLength;
                    progressBar1.Invoke((Action)(() =>
                    {
                        var endSecond = DateTime.Now.Second;
                        if (beginSecond != endSecond)//計算速度
                        {
                            downloadSpeed = downloadSpeed / (endSecond - beginSecond);
                            label1.Text = "下載速度" + downloadSpeed / 1024 + "KB/S";

                            beginSecond = DateTime.Now.Second;
                            downloadSpeed = 0;//清空
                        }
                        progressBar1.Value = Math.Max((int)(downloadSize * 100 / contentLength), 1);
                    }));
                }

                label1.Invoke((Action)(() =>
                {
                    label1.Text = "下載完成";
                }));
            }
        }
    });
}

效果圖:

斷點續傳

上面的方式我們發現,如果下載到一個半斷網了下次會重頭開始下載。這和我們今天的主題明顯不符嘛。下麵我們開始正式進入主題文件下載之斷點續傳。把前面我們說到的頭屬性Range用起來。

var request = new HttpRequestMessage { RequestUri = new Uri(url) };
request.Headers.Range = new RangeHeaderValue(rangeBegin, null); //【關鍵點】全局變數記錄已經下載了多少,然後下次從這個位置開始下載。
var httpResponseMessage = await http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);

完整代碼:

/// <summary>
/// 是否暫停
/// </summary>
static bool isPause = true;
/// <summary>
/// 下載開始位置(也就是已經下載了的位置)
/// </summary>
static long rangeBegin = 0; //(當然,這個值也可以存為持久化。如文本、資料庫等)

private async void button3_ClickAsync(object sender, EventArgs e)
{
    isPause = !isPause;
    if (!isPause)//點擊下載
    {
        button3.Text = "暫停";

        await Task.Run(async () =>
        {
            //非同步操作UI元素
            label1.Invoke((Action)(() =>
           {
               label1.Text = "準備下載...";
           }));

            long downloadSpeed = 0;//下載速度
            using (HttpClient http = new HttpClient())
            {
                var url = "http://localhost:813/新建文件夾2.rar";
                var request = new HttpRequestMessage { RequestUri = new Uri(url) };
                request.Headers.Range = new RangeHeaderValue(rangeBegin, null); //【關鍵點】全局變數記錄已經下載了多少,然後下次從這個位置開始下載。
                var httpResponseMessage = await http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
                var contentLength = httpResponseMessage.Content.Headers.ContentLength;//本次請求的內容大小
                if (httpResponseMessage.Content.Headers.ContentRange != null) //如果為空,則說明伺服器不支持斷點續傳
                {
                    contentLength = httpResponseMessage.Content.Headers.ContentRange.Length;//伺服器上的文件大小
                }

                using (var stream = await httpResponseMessage.Content.ReadAsStreamAsync())
                {
                    var readLength = 1024000;//1000K
                    byte[] bytes = new byte[readLength];
                    int writeLength;
                    var beginSecond = DateTime.Now.Second;//當前時間秒
                    while ((writeLength = stream.Read(bytes, 0, readLength)) > 0 && !isPause)
                    {
                        //使用追加方式打開一個文件流
                        using (FileStream fs = new FileStream(Application.StartupPath + "/temp.rar", FileMode.Append, FileAccess.Write))
                        {
                            fs.Write(bytes, 0, writeLength);
                        }
                        downloadSpeed += writeLength;
                        rangeBegin += writeLength;
                        progressBar1.Invoke((Action)(() =>
                        {
                            var endSecond = DateTime.Now.Second;
                            if (beginSecond != endSecond)//計算速度
                            {
                                downloadSpeed = downloadSpeed / (endSecond - beginSecond);
                                label1.Text = "下載速度" + downloadSpeed / 1024 + "KB/S";

                                beginSecond = DateTime.Now.Second;
                                downloadSpeed = 0;//清空
                            }
                            progressBar1.Value = Math.Max((int)((rangeBegin) * 100 / contentLength), 1);
                        }));
                    }

                    if (rangeBegin == contentLength)
                    {
                        label1.Invoke((Action)(() =>
                        {
                            label1.Text = "下載完成";
                        }));
                    }
                }
            }
        });
    }
    else//點擊暫停
    {
        button3.Text = "繼續下載";
        label1.Text = "暫停下載";
    }
}

效果圖:

到現在為止,你以為我們的斷點續傳就完成了嗎?
錯,你有沒有發現我們使用的下載鏈接是a標簽的。也就是我們自己寫服務端提供的下載鏈接是不是也可以支持斷點續傳呢?下麵我換個下載鏈接試試便知。

斷點續傳(服務端的支持)

測試結果如下:

發現並不支持斷點續傳。為什麼a標簽鏈接可以直接支持,我們寫的下載卻不支持呢。
a標簽的鏈接指向的直接是iis上的文件(iis預設支持),而我們寫的卻沒有做響應報文表頭Range的處理。(沒想象中的那麼智能嘛 >_<)

前面我們說過,斷線續傳是HTTP的一個協議。我們遵守它,它就存在,我們不遵守它也就不存在。
那下麵我們修改前面的文件下載代碼(服務端):

public void FileDownload5()
{          
    //前面可以做用戶登錄驗證、用戶許可權驗證等。

    string filename = "大數據.rar";   //客戶端保存的文件名  
    string filePath = Server.MapPath("/App_Data/大數據.rar");//要被下載的文件路徑 

    var range = Request.Headers["Range"];
    if (!string.IsNullOrWhiteSpace(range))//如果遵守協議,支持斷點續傳
    {
        var fileLength = new FileInfo(filePath).Length;//文件的總大小
        long begin;//文件的開始位置
        long end;//文件的結束位置
        long.TryParse(range.Split('=')[1].Split('-')[0], out begin);
        long.TryParse(range.Split('-')[1], out end);
        end = end - begin > 0 ? end : (fileLength - 1);// 如果沒有結束位置,那我們讀剩下的全部

        //表頭 表明  下載文件的開始、結束位置 和文件總大小
        Response.AddHeader("Content-Range", "bytes " + begin + "-" + end + "/" + fileLength);
        Response.ContentType = "application/octet-stream";
        Response.AddHeader("Content-Disposition", "attachment;filename=" + filename);
        Response.TransmitFile(filePath, begin, (end - begin));//發送 文件開始位置讀取的大小
    }
    else
    {
        Response.ContentType = "application/octet-stream";
        Response.AddHeader("Content-Disposition", "attachment;filename=" + filename);
        Response.TransmitFile(filePath);
    }
}

然後再測試斷點續傳,完美支持。

多線程同時下載(分片下載)

文件的斷點續傳已經分析完了。不過中間有些細節的東西你可以根據實際需求去完善。如:文件命名、斷點續傳的文件是否發生了改變、下載完成後驗證文件和伺服器上的是否一致。
還有我們可以根據表頭屬性Range來實現多線程下載,不過這裡就不貼代碼了,貼個效果圖吧。和上一篇文件上傳里的多線程上傳同理。您也可以根據提供的demo代碼下載查看,內有完整實現。

 

參考資料

demo


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

-Advertisement-
Play Games
更多相關文章
  • nop支持多店,結合NivoSlider插件介紹下如何開發支持多商城的小部件 ...
  • IdentityServer4 是一個提供 認證服務,單點登錄/登出(SSO),API訪問控制,聯合認證通道的可定製、免費商業支持的框架。 ...
  • 首先,本文所有 代碼已經提交到github,需要的可以直接從github獲取:https://github.com/starts2000/CefSharp,希望可以幫助到有需要的朋友們。 CEF 簡介 CEF is a BSD-licensed open source project founded ...
  • #region 執行cmd命令 /// <summary> /// 執行cmd命令 /// </summary> /// <param name="commandText"></param> /// <returns></returns> private string ExeCommand(stri ...
  • 最近工作中老是遇到 button的MouseLeftButtonDown事件不觸發的現象 然後就用了MouseLeftButtonUp事件 或者是隧道事件 就解決了不觸發了問題 然後網上也有人 建議是重寫MouseLeftButtonDown的事件 修改那個Handled 屬性 自己試了一下 真的很 ...
  • 首先看測試代碼: 1 public class StringSpeedTest 2 { 3 private readonly static string _testStr = "0123456789"; 4 5 public string StringAdd(int count) 6 { 7 str ...
  • 原文:https://www.stevejgordon.co.uk/asp-net-core-mvc-anatomy-addmvccore發佈於:2017年3月環境:ASP.NET Core 1.1 歡迎閱讀新系列的第一部分,我將剖析MVC源代碼,給大家展示隱藏在錶面之下的工作機制。此系列將分析MV ...
  • 1.什麼是委托? 1.1委托是一種引用類型,該類型適用於將方法用特定的簽名封裝,我們可以將委托理解為一個封裝了函數指針和方法的特殊類型。 1.2使用委托我們可以將方法封裝在委托中,然後調用委托就可以調用其中的方法 2.委托的基本聲明格式 修飾符 delegate 返回類型 委托名(參數列表)——返回 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...