在 WCF (Windows Communication Foundation) 中處理大型檔案傳輸時,若採用預設的緩衝模式,伺服器與用戶端都必須將完整訊息載入記憶體,極易造成 OutOfMemoryException。本篇示範如何透過 串流模式,讓資料以區塊方式流動,大幅降低記憶體佔用,並搭配 MessageContract 實現檔案上傳功能。
TransferMode.Streamed 讓訊息本文以 Stream 型別傳遞,不一次性載入。[MessageContract] 將檔案路徑放入 SOAP Header ([MessageHeader]),檔案資料流放入 SOAP Body ([MessageBodyMember])。BasicHttpBinding、NetTcpBinding、NetNamedPipeBinding 均支援串流。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);
}
}
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);
}
}
}
}
}
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();
}
}
}
}
<?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>
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}");
}
}
}
}
<?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) 點擊上傳,介面顯示「上傳成功」,伺服器端對應路徑將出現完整檔案。透過工作管理員觀察記憶體用量,可見程序記憶體維持穩定,不會隨檔案大小線性增長。
transferMode 皆需設為 Streamed,否則會拋出協定不符例外。System.IO.Stream、MemoryStream 等,自訂 Stream 類別需實作 IXmlSerializable。MessageBodyMember 在訊息契約中只能有一個,若需傳遞額外參數請改放 MessageHeader。sendTimeout、receiveTimeout、maxReceivedMessageSize 需依檔案大小與網速評估調大。Offset、FileHash 等欄位,伺服器端 FileStream.Seek 續寫。FileWrapper 改為集合,或建立多個 Operation 各自上傳單一檔案。Duplex 回呼介面,伺服器端定期回報已接收位元組數。