作法:啟用資料流

Windows Communication Foundation (WCF) 可以透過緩衝處理或串流處理的傳輸來傳送訊息。 在預設的緩衝傳輸模式中,必須完整傳遞訊息,接收者才能讀取。 在資料流傳輸模式中,接收者不需等到訊息完全送達,就可以開始處理訊息。 當資訊的傳遞很漫長,但是可依序列處理時,使用資料流模式將十分有幫助。 當訊息太龐大而無法完整加以緩衝時,資料流模式也很有用處。

若要啟用資料流處理,請適當定義 OperationContract 並在傳輸層級啟用資料流處理。

若要以資料流方式處理資料

  1. 若要以資料流方式處理資料,服務的 OperationContract 必須滿足兩項需求:

    1. 用來存放要進行資料流處理之資料的參數,必須是方法中的唯一參數。 例如,如果輸入訊息是要處理成資料流的訊息,這項處理作業就必須剛好只有一個輸入參數。 同樣地,如果要將輸出訊息處理成資料流,這項作業也必須剛好只有一個輸出參數或傳回值。

    2. 至少有一個參數型別與傳回值必須是 StreamMessageIXmlSerializable

    下列為資料流處理資料合約的範例。

    [ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples")]
    public interface IStreamingSample
    {
        [OperationContract]
        Stream GetStream(string data);
        [OperationContract]
        bool UploadStream(Stream stream);
        [OperationContract]
        Stream EchoStream(Stream stream);
        [OperationContract]
        Stream GetReversedStream();
    }
    
    <ServiceContract(Namespace:="http://Microsoft.ServiceModel.Samples")> _
    Public Interface IStreamingSample
        <OperationContract()> _
        Function GetStream(ByVal data As String) As Stream
        <OperationContract()> _
        Function UploadStream(ByVal stream As Stream) As Boolean
        <OperationContract()> _
        Function EchoStream(ByVal stream As Stream) As Stream
        <OperationContract()> _
        Function GetReversedStream() As Stream
    
    End Interface
    

    GetStream 作業會接收一些緩衝處理的輸入資料做為 string (會經過緩衝處理,並傳回已經過資料流處理的 Stream)。 相反地,UploadStream 會接受 Stream (經過資料流處理) 並傳回 bool (經過緩衝處理)。 EchoStream 會接受並傳回 Stream,而這項作業也是對輸入和輸出兩種訊息都進行資料流處理的範例。 最後,GetReversedStream 不接受任何輸入,而只是傳回 Stream (經過資料流處理)。

  2. 繫結必須啟用資料流處理。 您可以設定 TransferMode 屬性,並採用下列其中一個值:

    1. Buffered,

    2. Streamed,可啟用雙向資料流通訊。

    3. StreamedRequest,只會啟用要求的資料流處理。

    4. StreamedResponse,只會啟用回應的資料流處理。

    BasicHttpBinding 會公開繫結上的 TransferMode 屬性,就像 NetTcpBindingNetNamedPipeBinding 一樣。 TransferMode 屬性也可以在傳輸繫結項目上設定,並用於自訂繫結。

    下列範例說明如何透過程式碼與藉由變更組態檔來設定 TransferMode。 這些範例同時都會將 maxReceivedMessageSize 屬性設為 64 MB,以限制允許接收的最大訊息大小。 預設的 maxReceivedMessageSize 是 64 KB,但這對資料流案例來說,通常是過低的。 適當的配額設定取決於您的應用程式預期接收的最大訊息大小。 同時請注意,maxBufferSize 會控制緩衝處理的最大大小,請適當設定。

    1. 範例中的下列組態片段示範將 TransferMode 屬性設定為會在 basicHttpBinding 和自訂 HTTP 繫結上進行資料流處理。

      <basicHttpBinding>
        <binding name="HttpStreaming" maxReceivedMessageSize="67108864"
                 transferMode="Streamed"/>
      </basicHttpBinding>
      <!-- an example customBinding using Http and streaming-->
      <customBinding>
        <binding name="Soap12">
          <textMessageEncoding messageVersion="Soap12WSAddressing10" />
          <httpTransport transferMode="Streamed" maxReceivedMessageSize="67108864"/>
        </binding>
      </customBinding>
      
    2. 下列程式碼片段示範將 TransferMode 屬性設定為會在 basicHttpBinding 和自訂 HTTP 繫結上進行資料流處理。

      public static Binding CreateStreamingBinding()
      {
          BasicHttpBinding b = new BasicHttpBinding();
          b.TransferMode = TransferMode.Streamed;
          return b;
      }
      
      Public Shared Function CreateStreamingBinding() As Binding
          Dim b As New BasicHttpBinding()
          b.TransferMode = TransferMode.Streamed
          Return b
      End Function
      
    3. 下列程式碼片段示範將 TransferMode 屬性設定為會在自訂 TCP 繫結上進行資料流處理。

      public static Binding CreateStreamingBinding()
      {
          TcpTransportBindingElement transport = new TcpTransportBindingElement();
          transport.TransferMode = TransferMode.Streamed;
          BinaryMessageEncodingBindingElement encoder = new BinaryMessageEncodingBindingElement();
          CustomBinding binding = new CustomBinding(encoder, transport);
          return binding;
      }
      
      Public Shared Function CreateStreamingBinding() As Binding
          Dim transport As New TcpTransportBindingElement()
          transport.TransferMode = TransferMode.Streamed
          Dim binding As New CustomBinding(New BinaryMessageEncodingBindingElement(), _
                                           transport)
          Return binding
      End Function
      
  3. GetStreamUploadStreamEchoStream 都會處理直接從檔案傳送資料或直接將接收的資料儲存至檔案的作業。 下列為 GetStream 程式碼。

    public Stream GetStream(string data)
    {
        //this file path assumes the image is in
        // the Service folder and the service is executing
        // in service/bin
        string filePath = Path.Combine(
            System.Environment.CurrentDirectory,
            ".\\..\\image.jpg");
        //open the file, this could throw an exception
        //(e.g. if the file is not found)
        //having includeExceptionDetailInFaults="True" in config
        // would cause this exception to be returned to the client
        try
        {
            FileStream imageFile = File.OpenRead(filePath);
            return imageFile;
        }
        catch (IOException ex)
        {
            Console.WriteLine(
                String.Format("An exception was thrown while trying to open file {0}", filePath));
            Console.WriteLine("Exception is: ");
            Console.WriteLine(ex.ToString());
            throw ex;
        }
    }
    
    Public Function GetStream(ByVal data As String) As Stream Implements IStreamingSample.GetStream
        'this file path assumes the image is in
        ' the Service folder and the service is executing
        ' in service/bin 
        Dim filePath = Path.Combine(System.Environment.CurrentDirectory, ".\..\image.jpg")
        'open the file, this could throw an exception 
        '(e.g. if the file is not found)
        'having includeExceptionDetailInFaults="True" in config 
        ' would cause this exception to be returned to the client
        Try
            Return File.OpenRead(filePath)
        Catch ex As IOException
            Console.WriteLine(String.Format("An exception was thrown while trying to open file {0}", filePath))
            Console.WriteLine("Exception is: ")
            Console.WriteLine(ex.ToString())
            Throw ex
        End Try
    End Function
    

撰寫自訂資料流

  1. 若要對每個正在傳送或接收的資料流區塊 (Chunk) 進行特殊處理,請從 Stream 衍生自訂資料流類別。 下列程式碼包含 GetReversedStream 方法和 ReverseStream 類別,是一個自訂資料流範例。

    GetReversedStream 會建立並傳回 ReverseStream 的新執行個體。 實際的處理會在系統從這個 ReverseStream 物件讀取時進行。 ReverseStream.Read 方法會從基礎檔案讀取位元組區塊,然後將其序列反轉,再傳回此相反序列的位元組。 此方法不會反轉整個檔案內容,它只是一次反轉一個位元組區塊。 下列範例說明如何在對資料流讀取內容或寫入內容時執行資料流處理。

    class ReverseStream : Stream
    {
    
        FileStream inStream;
        internal ReverseStream(string filePath)
        {
            //opens the file and places a StreamReader around it
            inStream = File.OpenRead(filePath);
        }
        public override bool CanRead
        {
            get { return inStream.CanRead; }
        }
    
        public override bool CanSeek
        {
            get { return false; }
        }
    
        public override bool CanWrite
        {
            get { return false; }
        }
    
        public override void Flush()
        {
            throw new Exception("This stream does not support writing.");
        }
    
        public override long Length
        {
            get { throw new Exception("This stream does not support the Length property."); }
        }
    
        public override long Position
        {
            get
            {
                return inStream.Position;
            }
            set
            {
                throw new Exception("This stream does not support setting the Position property.");
            }
        }
    
        public override int Read(byte[] buffer, int offset, int count)
        {
            int countRead = inStream.Read(buffer, offset, count);
            ReverseBuffer(buffer, offset, countRead);
            return countRead;
        }
    
        public override long Seek(long offset, SeekOrigin origin)
        {
            throw new Exception("This stream does not support seeking.");
        }
    
        public override void SetLength(long value)
        {
            throw new Exception("This stream does not support setting the Length.");
        }
    
        public override void Write(byte[] buffer, int offset, int count)
        {
            throw new Exception("This stream does not support writing.");
        }
        public override void Close()
        {
            inStream.Close();
            base.Close();
        }
        protected override void Dispose(bool disposing)
        {
            inStream.Dispose();
            base.Dispose(disposing);
        }
        void ReverseBuffer(byte[] buffer, int offset, int count)
        {
    
            int i, j;
    
            for (i = offset, j = offset + count - 1; i < j; i++, j--)
            {
                byte currenti = buffer[i];
                buffer[i] = buffer[j];
                buffer[j] = currenti;
            }
        }
    }
    
    Friend Class ReverseStream
        Inherits Stream
    
        Private inStream As FileStream
    
        Friend Sub New(ByVal filePath As String)
            'opens the file and places a StreamReader around it
            inStream = File.OpenRead(filePath)
        End Sub
    
        Public Overrides ReadOnly Property CanRead() As Boolean
            Get
                Return inStream.CanRead
            End Get
        End Property
    
        Public Overrides ReadOnly Property CanSeek() As Boolean
            Get
                Return False
            End Get
        End Property
    
        Public Overrides ReadOnly Property CanWrite() As Boolean
            Get
                Return False
            End Get
        End Property
    
        Public Overrides Sub Flush()
            Throw New Exception("This stream does not support writing.")
        End Sub
    
        Public Overrides ReadOnly Property Length() As Long
            Get
                Throw New Exception("This stream does not support the Length property.")
            End Get
        End Property
    
        Public Overrides Property Position() As Long
            Get
                Return inStream.Position
            End Get
            Set(ByVal value As Long)
                Throw New Exception("This stream does not support setting the Position property.")
            End Set
        End Property
    
        Public Overrides Function Read(ByVal buffer() As Byte, _
                                       ByVal offset As Integer, _
                                       ByVal count As Integer) As Integer
    
            Dim countRead = inStream.Read(buffer, _
                                          offset, _
                                          count)
            ReverseBuffer(buffer, _
                          offset, _
                          countRead)
            Return countRead
        End Function
    
        Public Overrides Function Seek(ByVal offset As Long, _
                                       ByVal origin As SeekOrigin) As Long
            Throw New Exception("This stream does not support seeking.")
        End Function
    
        Public Overrides Sub SetLength(ByVal value As Long)
            Throw New Exception("This stream does not support setting the Length.")
        End Sub
    
        Public Overrides Sub Write(ByVal buffer() As Byte, _
                                   ByVal offset As Integer, _
                                   ByVal count As Integer)
            Throw New Exception("This stream does not support writing.")
        End Sub
    
        Public Overrides Sub Close()
            inStream.Close()
            MyBase.Close()
        End Sub
    
        Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
            inStream.Dispose()
            MyBase.Dispose(disposing)
        End Sub
    
        Private Sub ReverseBuffer(ByVal buffer() As Byte, _
                                  ByVal offset As Integer, _
                                  ByVal count As Integer)
    
            Dim i = offset
            Dim j = offset + count - 1
    
            Do While i < j
                Dim currenti = buffer(i)
                buffer(i) = buffer(j)
                buffer(j) = currenti
                i += 1
                j -= 1
            Loop
    
        End Sub
    End Class
    

另請參閱