为什么 SslStream.Read 总是将 TcpClient.Available 设置为 0,而 NetworkStream.Read 却没有

Why does SslStream.Read always set TcpClient.Available to 0, when NetworkStream.Read doesn't

我有一个连接到服务器的 TcpClient 客户端,它向客户端发送回一条消息。

当使用 NetworkStream.Read class 读取此数据时,我可以使用 count 参数指定要读取的字节数,这将减少 TcpClient.Available通过 count 读取完成后。来自 docs:

count Int32
The maximum number of bytes to be read from the current stream.

例如:

public static void ReadResponse()
{
    if (client.Available > 0) // Assume client.Available is 500 here
    {
        byte[] buffer = new byte[12]; // I only want to read the first 12 bytes, this could be a header or something
        var read = 0;

        NetworkStream stream = client.GetStream();
        while (read < buffer.Length)
        {
            read = stream.Read(buffer, 0, buffer.Length);
        }
    // breakpoint
    }
}

这会将 TcpClient 上可用的 500 个字节的前 12 个字节读入 buffer,并在断点处检查 client.Available 将产生 [=24] 的(预期)结果=] (500 - 12)。

现在,当我尝试做完全相同的事情,但这次使用 SslStream 时,结果出乎我的意料。

public static void ReadResponse()
{
    if (client.Available > 0) // Assume client.Available is 500 here
    {
        byte[] buffer = new byte[12]; // I only want to read the first 12 bytes, this could be a header or something
        var read = 0;

        SslStream stream = new SslStream(client.GetStream(), false, new RemoteCertificateValidationCallback(ValidateServerCertificate), null);

        while (read < buffer.Length)
        {
            read = stream.Read(buffer, 0, buffer.Length);
        }
    // breakpoint
    }
}

此代码将按预期将前 12 个字节读入 buffer。但是,现在在断点处检查 client.Available 时将产生 0.

的结果

与正常的 NetworkStream.Read 一样,SslStream.Readdocumentation 表示 count 表示要读取的最大字节数。

count Int32
A Int32 that contains the maximum number of bytes to read from this stream.

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

SslStreamTcpClient 的文档中,我找不到任何表明使用 SslStream.Read 刷新流或清空 client.Available 的内容。这样做的原因是什么(记录在哪里)?


this question 要求相当于 TcpClient.Available不是 我要的。我想知道为什么会发生这种情况,但此处没有介绍。

请记住,SslStream 可能会立即从底层 TcpStream 读取大块数据并在内部缓冲它们,出于效率原因,或者因为解密过程不是逐字节工作,需要一个数据块来能得到的。因此,您的 TcpClient 包含 0 个可用字节这一事实毫无意义,因为这些字节可能位于 SslStream 内的缓冲区中。


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

请记住,Stream.Read 可以 return 少于您预期的 字节。对 Stream.Read 的后续调用将 return 在该调用期间 读取的字节数 ,而不是全部。

所以你需要这样的东西:

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;
}

当您从 TLS 流中读取时,它 过度读取 ,维护一个尚未解密的内部数据缓冲区 - 或者 已解密但尚未消费。这是流中使用的一种常用方法,尤其是当它们改变内容(压缩、加密等)时,因为输入和输出有效负载大小之间不一定存在 1:1 相关性, 和 [=26] =] 可能需要从源读取整个帧 - 即你不能只读取 3 个字节 - API 需要读取整个帧(比如 512 字节),解密 frame,给you你想要的3,留着剩下的509下次你问的时候给你。这意味着它通常需要从源头(在本例中为套接字)消耗比它给你更多的东西。

许多流 API 出于性能原因也做同样的事情,例如 StreamReader 从底层 Stream 过度读取并在内部维护 byteBuffer尚未解码的字节,以及 charBuffer 个解码字符可供使用。那么您的问题将类似于:

When using StreamReader, I've only read 3 characters, but my Stream has advanced 512 bytes; why?