我只需要从N
读取最多SslStream
个字节,但如果在超时之前没有收到任何字节,则取消,同时让流保持有效状态以便稍后再试。 (*)
对于非SSL流(即NetworkStream
,只需使用其ReadTimeout
属性即可轻松完成此操作,这将使流在超时时抛出异常。不幸的是,根据官方文档,这种方法不适用于SslStream
:
SslStream假定当从内部流抛出一个IOException时,超时以及任何其他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:从套接字读取失败:System.NotSupportedException:当另一个读操作挂起时,无法调用BeginRead方法。
[更新2] 我尝试了另一种方法,通过在基础Poll(timeout, ...READ)
套接字上调用TcpClient
来实现超时:如果它返回true
,则调用Read()
上的SSlStream
,或者如果它返回false
,那么我们会超时。这也不起作用:因为SslStream
可能使用自己的内部中间缓冲区,Poll()
可以返回false
,即使还有SslStream
中的数据被读取。 / p>
[更新3] 另一种可能性是编写一个位于Stream
和NetworkStream
之间的自定义SslStream
子类,并捕获超时异常并返回0字节而不是SslStream
。我不知道如何做到这一点,更重要的是,我不知道是否将0字节读取返回SslStream
仍然不会以某种方式破坏它。
(*)我试图这样做的原因是,从非安全或安全套接字的超时同步读取是我已经在iOS,OS X,Linux和Android上使用的模式 - 平台代码。它适用于.NET中的非安全套接字,因此剩下的唯一情况是SslStream
。
答案 0 :(得分:7)
你当然可以让方法#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;
}
}
需要充实一点,以便调用者可以看到读取尚未完成,但片段太小而无法提供具体的建议。
答案 1 :(得分:4)
我也遇到了这个问题,SslStream在超时后读取了五个字节的垃圾数据,并且我单独提出了一个类似于OP的更新#3的解决方案。
我创建了一个包装类,它在传递给SslStream构造函数时包装Tcp NetworkStream对象。包装类将所有调用传递给底层NetworkStream,除了Read()方法包含一个额外的try ... catch来抑制Timeout异常并返回0字节。
SslStream在此实例中正常工作,包括在套接字关闭时引发相应的IOException。请注意,我们的Stream从Read()返回0不同于TcpClient或Socket从Read()返回0(这通常意味着套接字断开连接)。
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; }
}
}
然后可以通过在将TcpClient NetworkStream对象传递给SslStream之前将其包装起来来使用它,如下所示:
NetworkStream lTcpStream = lTcpClient.GetStream();
SocketTimeoutSuppressedStream lSuppressedStream = new SocketTimeoutSuppressedStream(lTcpStream);
using (lSslStream = new SslStream(lSuppressedStream, true, ServerCertificateValidation, SelectLocalCertificate, EncryptionPolicy.RequireEncryption))
问题归结为SslStream在基础流的任何异常中破坏其内部状态,甚至是无害的超时。奇怪的是,下一个read()返回的五个(左右)字节数据实际上是来自线路的TLS加密有效载荷数据的开始。
希望这会有所帮助