首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

WCF 實戰教學 (8):以串流模式傳輸大型檔案,降低記憶體壓力

     編輯:關於.NET

前言

在 WCF (Windows Communication Foundation) 中處理大型檔案傳輸時,若採用預設的緩衝模式,伺服器與用戶端都必須將完整訊息載入記憶體,極易造成 OutOfMemoryException。本篇示範如何透過 串流模式,讓資料以區塊方式流動,大幅降低記憶體佔用,並搭配 MessageContract 實現檔案上傳功能。

核心概念

  • 傳輸模式:TransferMode.Streamed 讓訊息本文以 Stream 型別傳遞,不一次性載入。
  • 訊息契約:使用 [MessageContract] 將檔案路徑放入 SOAP Header ([MessageHeader]),檔案資料流放入 SOAP Body ([MessageBodyMember])。
  • 繫結支援:BasicHttpBinding、NetTcpBinding、NetNamedPipeBinding 均支援串流。

完整程式碼範例

1. 服務端契約與實作

IStreamed.cs - 服務契約定義

using System;
using System.IO;
using System.ServiceModel;

namespace WCF.ServiceLib.Message
{
    /// 
    /// 檔案上傳的訊息包裝類別
    /// 
    [MessageContract]
    public class FileWrapper
    {
        /// 檔案在伺服器端的儲存路徑 (放在 SOAP Header)
        [MessageHeader]
        public string FilePath;

        /// 檔案資料流 (放在 SOAP Body,且必須是唯一的 Body 成員)
        [MessageBodyMember]
        public Stream FileData;
    }

    /// 
    /// 串流檔案上傳服務介面
    /// 
    [ServiceContract]
    public interface IStreamed
    {
        /// 
        /// 上傳檔案
        /// 
        /// 
        /// 1. 僅支援可序列化的 Stream 或 MemoryStream
        /// 2. 訊息本文只能包含單一 MessageBodyMember
        /// 3. 需在繫結設定 transferMode="Streamed"
        /// 
        [OperationContract]
        void UploadFile(FileWrapper fileWrapper);
    }
}

Streamed.cs - 服務實作

using System;
using System.IO;
using WCF.ServiceLib.Message;

namespace WCF.ServiceLib.Message
{
    public class Streamed : IStreamed
    {
        public void UploadFile(FileWrapper fileWrapper)
        {
            using (var sourceStream = fileWrapper.FileData)
            using (var targetStream = new FileStream(fileWrapper.FilePath, FileMode.Create, FileAccess.Write, FileShare.None))
            {
                var buffer = new byte[4096];
                int bytesRead;
                while ((bytesRead = sourceStream.Read(buffer, 0, buffer.Length)) > 0)
                {
                    targetStream.Write(buffer, 0, bytesRead);
                }
            }
        }
    }
}

2. 宿主與設定檔

主控台宿主程式

using System;
using System.ServiceModel;

namespace WCF.ServiceHost2.Message
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var host = new ServiceHost(typeof(WCF.ServiceLib.Message.Streamed)))
            {
                host.Open();
                Console.WriteLine("服務已啟動 (WCF.ServiceLib.Message.Streamed)");
                Console.WriteLine("按 <ENTER> 停止服務...");
                Console.ReadLine();
            }
        }
    }
}

App.config - 關鍵繫結設定

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="WCF.ServiceLib.Message.Streamed" behaviorConfiguration="MessageBehavior">
        <endpoint address="Message/Streamed"
                  binding="netTcpBinding"
                  contract="WCF.ServiceLib.Message.IStreamed"
                  bindingConfiguration="StreamedBindingConfiguration" />
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
        <host>
          <baseAddresses>
            <add baseAddress="https://localhost:12345/Message/Streamed/" />
            <add baseAddress="net.tcp://localhost:54321/" />
          </baseAddresses>
        </host>
      </service>
    </services>

    <bindings>
      <netTcpBinding>
        <!-- 關鍵:啟用串流模式、放大訊息大小上限、延長逾時 -->
        <binding name="StreamedBindingConfiguration"
                 transferMode="Streamed"
                 maxReceivedMessageSize="1073741824"  <!-- 1 GB -->
                 receiveTimeout="00:10:00" />
      </netTcpBinding>
    </bindings>

    <behaviors>
      <serviceBehaviors>
        <behavior name="MessageBehavior">
          <serviceMetadata httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

3. 用戶端呼叫

using System;
using System.IO;
using System.ServiceModel;
using System.Windows.Forms;

namespace Client2.Message
{
    public class StreamedClient
    {
        public void UploadFile(string sourceFilePath, string destinationFolder)
        {
            try
            {
                var proxy = new MessageSvc.Streamed.StreamedClient();
                using (var fs = new FileStream(sourceFilePath, FileMode.Open, FileAccess.Read))
                {
                    // 檔名由用戶端決定,路徑由伺服器端設定
                    string remotePath = Path.Combine(destinationFolder, Path.GetFileName(sourceFilePath));
                    proxy.UploadFile(remotePath, fs);
                }
                proxy.Close();
                MessageBox.Show("上傳成功");
            }
            catch (Exception ex)
            {
                MessageBox.Show($"上傳失敗:{ex.Message}");
            }
        }
    }
}

用戶端 App.config

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.serviceModel>
    <client>
      <endpoint address="net.tcp://localhost:54321/Message/Streamed"
                binding="netTcpBinding"
                contract="MessageSvc.Streamed.IStreamed"
                bindingConfiguration="StreamedBindingConfiguration" />
    </client>
    <bindings>
      <netTcpBinding>
        <binding name="StreamedBindingConfiguration"
                 transferMode="Streamed"
                 sendTimeout="00:10:00" />
      </netTcpBinding>
    </bindings>
  </system.serviceModel>
</configuration>

執行結果與驗證

啟動宿主後,用戶端選擇任意大型檔案 (測試建議 > 100MB) 點擊上傳,介面顯示「上傳成功」,伺服器端對應路徑將出現完整檔案。透過工作管理員觀察記憶體用量,可見程序記憶體維持穩定,不會隨檔案大小線性增長。

常見坑點提醒

  1. 繫結雙端必須一致:服務端與用戶端 transferMode 皆需設為 Streamed,否則會拋出協定不符例外。
  2. Stream 必須可序列化:僅支援 System.IO.Stream、MemoryStream 等,自訂 Stream 類別需實作 IXmlSerializable。
  3. 訊息本文唯一性:MessageBodyMember 在訊息契約中只能有一個,若需傳遞額外參數請改放 MessageHeader。
  4. 大檔案逾時調整:sendTimeout、receiveTimeout、maxReceivedMessageSize 需依檔案大小與網速評估調大。

延伸應用

  • 斷點續傳:在 Header 加入 Offset、FileHash 等欄位,伺服器端 FileStream.Seek 續寫。
  • 多檔案批次上傳:將 FileWrapper 改為集合,或建立多個 Operation 各自上傳單一檔案。
  • 進度通知:搭配 Duplex 回呼介面,伺服器端定期回報已接收位元組數。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved