c# - 带有 System.IO.Pipelines 的 TLS/SSL
问题描述
我注意到新的 System.IO.Pipelines 并试图将现有的、基于流的代码移植到它上面。流的问题很好理解,但同时它具有相关类的丰富回声系统。
从这里提供的示例中,有一个小型 tcp echo 服务器。 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 中即可轻松将其添加到代码中。Pipelines 打算如何解决这个问题?
解决方案
命名管道是一种网络协议,就像 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。
System.IO.Pipes 命名空间也可用于 .NET Framework 和 .NET Core。Pipes 命名空间中可用的类非常有用。
- 匿名管道客户端流
- 匿名管道服务器流
- NamedPipeClientStream
- 命名管道服务器流
- 管道流
所有这些类都返回某种从 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 类:
- https://docs.microsoft.com/en-us/dotnet/api/system.io.pipelines.pipe?view=dotnet-plat-ext-2.1
- https://docs.microsoft.com/en-us/dotnet/framework/network-programming/using-secure-sockets-layer
- https://docs.microsoft.com/en-us/dotnet/api/system.net.security.sslstream?view=netcore-2.2
- https://docs.microsoft.com/en-us/dotnet/api/system.io.pipes?view=netcore-2.2
推荐阅读
- c# - 如何正确延长 cookie 过期时间?用户在不到一个小时内被自动踢出
- single-page-application - 谷歌标签管理器单页应用程序窗口加载和历史更改
- c# - iText 7 从 Asp.Net WebApi 返回 Pdf
- python - 熊猫数据框从元素频率大于1的列创建唯一ID
- windows - 排序对象对 Get-EventLog 没有影响
- python - mpi 插槽不可用
- node.js - 如何处理运行时间超过 cron 间隔的 pm2 cron 作业?
- python - Pandas str.contains 超过一个列表?
- maven - 如何在提交/拉取请求后更新 maven 包
- haskell - 使用“否则”时如何获得“非详尽模式”异常