web-dev-qa-db-ja.com

System.IO.Pipelinesを使用したTLS / SSL

新しいSystem.IO.Pipelinesに気づき、既存のストリームベースのコードをそれに移植しようとしています。ストリームの問題はよく理解されていますが、同時に、関連するクラスの豊富なエコーシステムを備えています。

ここで提供されている例から、小さなtcpエコーサーバーがあります。 https://blogs.msdn.Microsoft.com/dotnet/2018/07/09/system-io-pipelines-high-performance-io-in-net/

コードのスニペットはここに添付されています:

    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でラップするだけで簡単にコードに追加できます。これはパイプラインでどのように解決されることを意図していますか?

10
agnsaft

名前付きパイプは、HTTP、FTP、SMTPと同様に、ネットワークプロトコルです。いくつかの簡単な例については、.netFrameworkを見てみましょう。

  • SSLは、ベースURIに応じて、HTTP接続によって自動的に利用されます。 URIが「HTTPS:」の場合、SSLが使用されます。
  • SSLは、GetResponse()を呼び出す前にEnableSslプロパティをtrueに設定することにより、FTP接続によって手動で利用されます。
  • SSLは、FTPと同じ方法でSMTPによって利用されます。

しかし、パイプなどの別のネットワークプロトコルを使用している場合はどうなりますか?すぐに、「HTTPS」プレフィックスに似たものはないことがわかります。さらに、System.IO.Piplinesのドキュメントを読んで、「EnableSsl」メソッドがないことを確認できます。ただし、.NETFrameworkと.NETCoreの両方で、SslStreamクラスを使用できます。このクラスを使用すると、利用可能なほぼすべてのストリームからSslStreamを構築できます。

.NETFrameworkと.NETCoreの両方で、System.IO.Pipes名前空間も使用できます。 Pipes名前空間で利用可能なクラスは非常に役立ちます。

  • AnonymousPipeClientStream
  • AnonymousPipeServerStream
  • NamedPipeClientStream
  • NamedPipeServerStream
  • PipeStream

これらのクラスはすべて、Streamから継承するある種のオブジェクトを返すため、SslStreamのコンストラクターで使用できます。

これは、System.IO.Piplines名前空間とどのように関連していますか?ええと...そうではありません。 System.IO.Pipelines名前空間で定義されたクラス、構造体、またはインターフェイスはいずれもStreamから継承しません。したがって、SslStreamクラスを直接使用することはできません。

代わりに、PipeReadersとPipeWritersにアクセスできます。これらのうち1つしか使用できない場合もありますが、両方に同時にアクセスできるように、双方向パイプについて考えてみましょう。

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は、これについてはるかに詳細な説明を書いています。 3つの部分の最初のものはここにあります: https://blog.marcgravell.com/2018/07/pipe-dreams-part-1.html

さらに、言及されているさまざまな.NETクラスについて読むことができます。

4
Daniel Lambert