【问题标题】:TLS/SSL with System.IO.Pipelines带有 System.IO.Pipelines 的 TLS/SSL
【发布时间】:2019-05-14 01:18:46
【问题描述】:

我注意到新的 System.IO.Pipelines 并尝试将现有的基于流的代码移植到它。流的问题很容易理解,但同时它具有相关类的丰富回声系统。

从这里提供的示例中,有一个小型 tcp echo 服务器。 https://blogs.msdn.microsoft.com/dotnet/2018/07/09/system-io-pipelines-high-performance-io-in-net/

这里附上一段sn-p的代码:

    private static async Task ProcessLinesAsync(Socket socket)
    {
        Console.WriteLine($"[{socket.RemoteEndPoint}]: connected");

        var pipe = new Pipe();
        Task writing = FillPipeAsync(socket, pipe.Writer);
        Task reading = ReadPipeAsync(socket, pipe.Reader);

        await Task.WhenAll(reading, writing);

        Console.WriteLine($"[{socket.RemoteEndPoint}]: disconnected");
    }

    private static async Task FillPipeAsync(Socket socket, PipeWriter writer)
    {
        const int minimumBufferSize = 512;

        while (true)
        {
            try
            {
                // Request a minimum of 512 bytes from the PipeWriter
                Memory<byte> memory = writer.GetMemory(minimumBufferSize);

                int bytesRead = await socket.ReceiveAsync(memory, SocketFlags.None);
                if (bytesRead == 0)
                {
                    break;
                }

                // Tell the PipeWriter how much was read
                writer.Advance(bytesRead);
            }
            catch
            {
                break;
            }

            // Make the data available to the PipeReader
            FlushResult result = await writer.FlushAsync();

            if (result.IsCompleted)
            {
                break;
            }
        }

        // Signal to the reader that we're done writing
        writer.Complete();
    }

    private static async Task ReadPipeAsync(Socket socket, PipeReader reader)
    {
        while (true)
        {
            ReadResult result = await reader.ReadAsync();

            ReadOnlySequence<byte> buffer = result.Buffer;
            SequencePosition? position = null;

            do
            {
                // Find the EOL
                position = buffer.PositionOf((byte)'\n');

                if (position != null)
                {
                    var line = buffer.Slice(0, position.Value);
                    ProcessLine(socket, line);

                    // This is equivalent to position + 1
                    var next = buffer.GetPosition(1, position.Value);

                    // Skip what we've already processed including \n
                    buffer = buffer.Slice(next);
                }
            }
            while (position != null);

            // We sliced the buffer until no more data could be processed
            // Tell the PipeReader how much we consumed and how much we left to process
            reader.AdvanceTo(buffer.Start, buffer.End);

            if (result.IsCompleted)
            {
                break;
            }
        }

        reader.Complete();
    }

    private static void ProcessLine(Socket socket, in ReadOnlySequence<byte> buffer)
    {
        if (_echo)
        {
            Console.Write($"[{socket.RemoteEndPoint}]: ");
            foreach (var segment in buffer)
            {
                Console.Write(Encoding.UTF8.GetString(segment.Span));
            }
            Console.WriteLine();
        }
    }

使用流时,您可以轻松地将 SSL/TLS 添加到您的代码中,只需将其包装在 SslStream 中即可。 Pipelines 打算如何解决这个问题?

【问题讨论】:

  • 乍一看,Kestrel 似乎使用流(包括 SslStream)来为其管道提供数据。 Mark Gravell's blog on Pipes 中大致介绍了这一点 - 请参阅“抽水管”部分和明确提到 TLS 的“这是我之前制作的”。
  • 有趣,但考虑到支持 Pipelines 的论点对 Stream 有点负面,它不是违背了它的目的吗?
  • 有几点需要注意。 1. System.IO.Pipelines 只是整体开发的第一步,因此没有 API 可用作 System.IO.Pipelines 的端点(例如直接接受数据和泵的 dotnetty、Kestrel 或 .NET 套接字它在那里)。虽然它们是为未来计划的,但 .NET Core 团队首先需要围绕它设计一个干净的 api,并找到时间来实现这些,
  • 2.管道用于解决接收需要以最有效和易于使用的方式解析的分块数据的特定问题,此 api 的用户不必担心如何合并连续缓冲区或将块粘合在一起以及它背后的内存管理。 3. 允许后台进程处理来自网络的数据(即,一旦我们得到第一个字节就开始解析头部)

标签: c# ssl .net-core tls1.2


【解决方案1】:

命名管道是一种网络协议,就像 HTTP、FTP 和 SMTP 一样。让我们看看 .net 框架中的一些简单示例:

  • HTTP 连接自动利用 SSL, 取决于基本 URI。如果 URI 带有“HTTPS:”,则 SSL 为 用过的。
  • FTP 连接手动利用 SSL,通过设置 在调用 GetResponse() 之前将 EnableSsl 属性设置为 true。
  • SMTP 以与 FTP 相同的方式利用 SSL。

但是,如果我们使用不同的网络协议,例如管道,该怎么办?我们马上就知道没有类似于“HTTPS”前缀的东西。此外,我们可以阅读 System.IO.Piplines 的文档,发现没有“EnableSsl”方法。但是,在 .NET Framework 和 .NET Core 中,都可以使用 SslStream 类。此类允许您从几乎任何可用的 Stream 中构建 SslStream。

在 .NET Framework 和 .NET Core 中也可以使用 System.IO.Pipes 命名空间。 Pipes 命名空间中可用的类非常有用。

  • AnonymousPipeClientStream
  • 匿名管道服务器流
  • NamedPipeClientStream
  • NamedPipeServerStream
  • 管道流

所有这些类都返回某种从 Stream 继承的对象,因此可以在 SslStream 的构造函数中使用。

这与 System.IO.Piplines 命名空间有何关联?嗯......它没有。 System.IO.Pipelines 命名空间中定义的类、结构或接口都不是从 Stream 继承的。所以我们不能直接使用 SslStream 类。

相反,我们可以访问 PipeReaders 和 PipeWriters。有时我们只有其中一个可用,但让我们考虑一个双向管道,以便我们可以同时访问两者。

System.IO.Piplines 命名空间提供了一个 IDuplexPipe 接口。如果我们想将 PipeReader 和 PipeWriters 包装在 SSL 流中,我们将需要定义一个实现 IDuplexPipe 的新类型。

在这种新类型中:

  • 我们将定义一个 SslStream。
  • 我们将使用通用管道作为输入和输出缓冲区。
  • PipeReader 将使用输入缓冲区的读取器。我们将使用这个输入缓冲区从 SSL 流中获取数据。
  • PipeWriter 将使用输出缓冲区的写入器。我们将使用此输出缓冲区将数据发送到 SSL 流。

这是一个伪代码示例:

SslStreamDuplexPipe : IDuplexPipe
{ 
    SslStream sslStream;
    Pipe inputBuffer;
    Pipe outputBuffer;

    public PipeReader Input = inputBuffer.Reader;
    public PipeWriter Output = outputBuffer.Writer;

    ReadDataFromSslStream()
    {
        int bytes = sslStream.Read(new byte[2048], 0, 2048);
        inputBuffer.Writer.Advance(bytes)
        inputBuffer.Writer.Flush();
    }

    //and the reverse to write to the SslStream 
 }

如您所见,我们仍在使用 System.Net.Security 命名空间中的 SslStream 类,只是多走了几步。

这是否意味着您基本上仍在使用流?是的!但是,一旦你完全实现了你的 SslStreamDuplexPipe 类,你就可以只使用管道了。无需将 SslStream 包裹在所有内容中。

Marc Gravell 对此写了非常非常详细的解释。三个部分中的第一个可以在这里找到:https://blog.marcgravell.com/2018/07/pipe-dreams-part-1.html

此外,您还可以阅读所提到的各种 .NET 类:

【讨论】:

    猜你喜欢
    • 2012-06-23
    • 2021-09-15
    • 2016-06-02
    • 2021-10-03
    • 2015-12-06
    • 2017-01-11
    • 1970-01-01
    • 2017-11-08
    • 2019-03-13
    相关资源
    最近更新 更多