C# 当NetworkStream.Read不';T

C# 当NetworkStream.Read不';T,c#,tcpclient,networkstream,sslstream,C#,Tcpclient,Networkstream,Sslstream,我有一个连接到服务器的TcpClient客户机,它将消息发送回客户机 使用NetworkStream.Read类读取此数据时,我可以使用count参数指定要读取的字节数,这将在读取完成后将TcpClient.Available减少count。从: 计数Int32 要从当前流中读取的最大字节数 例如: publicstaticvoidreadresponse() { if(client.Available>0)//此处假定client.Available为500 { byte[]buffer=ne

我有一个连接到服务器的
TcpClient
客户机,它将消息发送回客户机

使用
NetworkStream.Read
类读取此数据时,我可以使用
count
参数指定要读取的字节数,这将在读取完成后将
TcpClient.Available
减少
count
。从:

计数
Int32

要从当前流中读取的最大字节数

例如:

publicstaticvoidreadresponse()
{
if(client.Available>0)//此处假定client.Available为500
{
byte[]buffer=new byte[12];//我只想读取前12个字节,这可能是头或其他内容
var read=0;
NetworkStream=client.GetStream();
while(读取<缓冲区长度)
{
read=stream.read(buffer,0,buffer.Length);
}
//断点
}
}
这会将
TcpClient
上可用的500个字节中的前12个字节读入
缓冲区
,并检查
客户端。断点处可用的
将产生
488
(500-12)的(预期)结果

现在,当我尝试做完全相同的事情,但是这次使用
SslStream
时,结果对我来说是相当意外的

publicstaticvoidreadresponse()
{
if(client.Available>0)//此处假定client.Available为500
{
byte[]buffer=new byte[12];//我只想读取前12个字节,这可能是头或其他内容
var read=0;
SslStream stream=新的SslStream(client.GetStream(),false,新的RemoteCertificateValidationCallback(ValidateServerCertificate),null);
while(读取<缓冲区长度)
{
read=stream.read(buffer,0,buffer.Length);
}
//断点
}
}
此代码将按预期将前12个字节读入
缓冲区
。但是,在断点处检查
客户端时,现在可用的
将产生
0
的结果

与正常的
NetworkStream.Read
for
SslStream.Read
表示
count
表示要读取的最大字节数

计数
Int32

一种
Int32
,包含从该流读取的最大字节数

虽然它只读取这12个字节,但我不知道剩下的488个字节去了哪里

SslStream
TcpClient
的文档中,我找不到任何指示使用
SslStream.Read
刷新流或以其他方式清空
客户端的内容。可用
。这样做的原因是什么(在哪里有记录)



有一种方法需要一个等价的
TcpClient.Available
,这不是我想要的。我想知道为什么会发生这种情况,这里没有介绍。

请记住,ssl流可能会立即从底层TcpStream读取大数据块,并在内部对其进行缓冲,这是出于效率原因,或者是因为解密过程无法逐字节工作,需要一个可用的数据块。因此,TcpClient包含0个可用字节这一事实并不意味着什么,因为这些字节可能位于SslStream内的缓冲区中


此外,读取12字节的代码不正确,这可能会影响您看到的内容

请记住,
Stream.Read
返回的字节数比预期的要少。对
Stream.Read的后续调用将返回该调用期间读取的字节数,而不是总字节数

所以你需要这样的东西:

int read = 0;
while (read < buffer.Length)
{
    int readThisTime = stream.Read(buffer, read, buffer.Length - read);
    if (readThisTime == 0)
    {
        // The end of the stream has been reached: throw an error?
    }
    read += readThisTime;
}
int read=0;
while(读取<缓冲区长度)
{
int readThisTime=stream.Read(buffer,Read,buffer.Length-Read);
如果(readThistTime==0)
{
//已到达流的结尾:抛出错误?
}
read+=readthist;
}

当您从TLS流中读取数据时,它会进行过度读取,从而保留一个内部缓冲区,用于存储尚未解密或已解密但尚未使用的数据。这是流中使用的常用方法,尤其是当它们改变内容(压缩、加密等)时,因为输入和输出有效负载大小之间不一定存在1:1的相关性,并且可能需要从源读取整个帧(即,您不能仅读取3个字节),API需要读取整个帧(比如,512字节),解密帧,给你想要的3个,保留剩余的509以便下次你提问时给你。这意味着它通常需要从源(本例中是套接字)消耗比它给你更多的信息

许多流式API出于性能原因也会这样做,例如,
StreamReader
从底层
进行过度读取,并在内部维护未解码字节的
byteBuffer
,以及可供消费的解码字符的
charBuffer
勒托:

使用
StreamReader
时,我只读取了3个字符,但我的
具有高级512字节;为什么


我使用您提供的方法进行了尝试,得到了完全相同的结果(尽管正如您所说的,现在可以防止读取的字节数少于预期)在只读取了
n
字节中的12个字节后,它仍然会清除
可用的
。我明白了。因此,创建
新的SslStream
会复制
TcpClient.GetStream()的可用缓冲区
进入它自己的缓冲区,清除客户端的流,使其返回0。@Remy是的。我的意思是,不能保证它读取整个缓冲区:只是碰巧它这次决定读取至少500字节。比