2016-05-12 46 views
13

我只需要從SslStream中讀取多達N字節,但如果在超時之前沒有收到字節,請取消,同時使流處於有效狀態以便稍後再試。 (*)如何(重複)從.NET SslStream中讀取超時?

只需使用其ReadTimeout屬性即可使非SSL流即NetworkStream輕鬆完成此操作,該屬性將使流在超時時拋出異常。不幸的是這種方法不會對SslStream按照官方的文檔工作:

SslStream假定與任何其他IOException異常沿超時當一個人從內流拋出將通過它的調用者致命的治療。超時後重新使用SslStream實例將返回垃圾。應用程序應關閉SslStream並在這些情況下拋出異常。

[更新1]我想這樣一種不同的方法:

task = stream->ReadAsync(buffer, 0, buffer->Length); 
if (task->Wait(timeout_ms)) { 
    count = task->Result; 
    ... 
} 

但是,這並不工作,如果Wait()返回false:再次調用ReadAsync()時後,它拋出一個異常:

異常拋出:System.dll中的'System.NotSupportedException' Tests.exe警告:0:從s讀取失敗ocket:System.NotSupportedException:當另一個讀操作掛起時,不能調用BeginRead方法。

[更新2]我嘗試另一種方法通過調用底層TcpClient插座Poll(timeout, ...READ)實現超時:如果返回true,然後調用Read()SSlStream,或者如果它返回false然後我們有一個時間到。這也不起作用:因爲SslStream可能使用自己的內部中間緩衝區,所以即使在SslStream中有數據需要讀取,Poll()也可以返回false

[更新3]另一種可能性是編寫自定義Stream子類,它會坐下NetworkStreamSslStream之間並捕獲超時異常並返回0字節而不是SslStream。我不知道如何做到這一點,更重要的是,我不知道如果返回0字節讀取到SslStream仍然不會以某種方式損壞它。 (*)我試圖這樣做的原因是,從非安全或安全套接字同步讀取超時是我已經在iOS,OS X,Linux和Android上使用的模式跨平臺的代碼。它適用於.NET中的非安全套接字,因此唯一的情況是SslStream

+0

一個相關的問題在這裏:http://stackoverflow.com/questions/24198290/net-4-5-sslstream-cancel-a-asynchronous-read-write-call – Pol

回答

6

你當然可以讓方法#1工作。您只需跟蹤任務並繼續等待,而無需再次調用ReadAsync。所以,很不客氣:

private Task readTask;  // class level variable 
... 
    if (readTask == null) readTask = stream->ReadAsync(buffer, 0, buffer->Length); 
    if (task->Wait(timeout_ms)) { 
    try { 
     count = task->Result; 
     ... 
    } 
    finally { 
     task = null; 
    } 
    } 

需要特別充實出了一點,所以調用者可以看到的是讀操作尚未完成,但片段是太小,無法提供具體的建議。

+0

有趣的想法,但這意味着當應用程序想要終止連接時,可能會有一個異步讀取任務仍在進行中。將關閉'SslStream'中止異步讀取? – Pol

1

我也遇到過這個問題,SslStream在超時後在讀取上返回5個字節的垃圾數據,我分別提出了一個類似於OP的Update#3的解決方案。

我創建了一個包裝類,它包裝Tcp NetworkStream對象,因爲它傳遞到SslStream構造函數。包裝類將所有調用傳遞到底層NetworkStream,只是Read()方法包含額外的try ... catch來抑制Timeout異常並返回0字節。

SslStream在此實例中正常工作,包括在套接字關閉時引發相應的IOException。請注意,從Read()返回0的Stream不同於從Read()返回0的TcpClient或Socket(通常意味着套接字斷開連接)。

class SocketTimeoutSuppressedStream : Stream 
{ 
    NetworkStream mStream; 

    public SocketTimeoutSuppressedStream(NetworkStream pStream) 
    { 
     mStream = pStream; 
    } 

    public override int Read(byte[] buffer, int offset, int count) 
    { 
     try 
     { 
      return mStream.Read(buffer, offset, count); 
     } 
     catch (IOException lException) 
     { 
      SocketException lInnerException = lException.InnerException as SocketException; 
      if (lInnerException != null && lInnerException.SocketErrorCode == SocketError.TimedOut) 
      { 
       // Normally, a simple TimeOut on the read will cause SslStream to flip its lid 
       // However, if we suppress the IOException and just return 0 bytes read, this is ok. 
       // Note that this is not a "Socket.Read() returning 0 means the socket closed", 
       // this is a "Stream.Read() returning 0 means that no data is available" 
       return 0; 
      } 
      throw; 
     } 
    } 


    public override bool CanRead => mStream.CanRead; 
    public override bool CanSeek => mStream.CanSeek; 
    public override bool CanTimeout => mStream.CanTimeout; 
    public override bool CanWrite => mStream.CanWrite; 
    public virtual bool DataAvailable => mStream.DataAvailable; 
    public override long Length => mStream.Length; 
    public override IAsyncResult BeginRead(byte[] buffer, int offset, int size, AsyncCallback callback, object state) => mStream.BeginRead(buffer, offset, size, callback, state); 
    public override IAsyncResult BeginWrite(byte[] buffer, int offset, int size, AsyncCallback callback, object state) => mStream.BeginWrite(buffer, offset, size, callback, state); 
    public void Close(int timeout) => mStream.Close(timeout); 
    public override int EndRead(IAsyncResult asyncResult) => mStream.EndRead(asyncResult); 
    public override void EndWrite(IAsyncResult asyncResult) => mStream.EndWrite(asyncResult); 
    public override void Flush() => mStream.Flush(); 
    public override Task FlushAsync(CancellationToken cancellationToken) => mStream.FlushAsync(cancellationToken); 
    public override long Seek(long offset, SeekOrigin origin) => mStream.Seek(offset, origin); 
    public override void SetLength(long value) => mStream.SetLength(value); 
    public override void Write(byte[] buffer, int offset, int count) => mStream.Write(buffer, offset, count); 

    public override long Position 
    { 
     get { return mStream.Position; } 
     set { mStream.Position = value; } 
    } 

    public override int ReadTimeout 
    { 
     get { return mStream.ReadTimeout; } 
     set { mStream.ReadTimeout = value; } 
    } 

    public override int WriteTimeout 
    { 
     get { return mStream.WriteTimeout; } 
     set { mStream.WriteTimeout = value; } 
    } 
} 

這可以再通過包裝之前它傳遞給SslStream的的TcpClient的NetworkStream對象使用,具體如下:

NetworkStream lTcpStream = lTcpClient.GetStream(); 
SocketTimeoutSuppressedStream lSuppressedStream = new SocketTimeoutSuppressedStream(lTcpStream); 
using (lSslStream = new SslStream(lSuppressedStream, true, ServerCertificateValidation, SelectLocalCertificate, EncryptionPolicy.RequireEncryption)) 

的問題歸結到SslStream從任何異常破壞其內部狀態底層流,甚至是無害的超時。奇怪的是,下一個read()返回的數據的五個(左右)字節實際上是來自導線的TLS加密有效載荷數據的開始。

希望這有助於