新しい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でラップするだけで簡単にコードに追加できます。これはパイプラインでどのように解決されることを意図していますか?
名前付きパイプは、HTTP、FTP、SMTPと同様に、ネットワークプロトコルです。いくつかの簡単な例については、.netFrameworkを見てみましょう。
しかし、パイプなどの別のネットワークプロトコルを使用している場合はどうなりますか?すぐに、「HTTPS」プレフィックスに似たものはないことがわかります。さらに、System.IO.Piplinesのドキュメントを読んで、「EnableSsl」メソッドがないことを確認できます。ただし、.NETFrameworkと.NETCoreの両方で、SslStreamクラスを使用できます。このクラスを使用すると、利用可能なほぼすべてのストリームからSslStreamを構築できます。
.NETFrameworkと.NETCoreの両方で、System.IO.Pipes名前空間も使用できます。 Pipes名前空間で利用可能なクラスは非常に役立ちます。
これらのクラスはすべて、Streamから継承するある種のオブジェクトを返すため、SslStreamのコンストラクターで使用できます。
これは、System.IO.Piplines名前空間とどのように関連していますか?ええと...そうではありません。 System.IO.Pipelines名前空間で定義されたクラス、構造体、またはインターフェイスはいずれもStreamから継承しません。したがって、SslStreamクラスを直接使用することはできません。
代わりに、PipeReadersとPipeWritersにアクセスできます。これらのうち1つしか使用できない場合もありますが、両方に同時にアクセスできるように、双方向パイプについて考えてみましょう。
System.IO.Piplines名前空間は、IDuplexPipeインターフェイスを提供します。 PipeReaderとPipeWritersをSSLストリームでラップする場合は、IDuplexPipeを実装する新しいタイプを定義する必要があります。
この新しいタイプでは:
擬似コードの例を次に示します。
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クラスについて読むことができます。