12380网站建设情况报告,可以找网图的软件,农副产品网站建设目标,网页设计的费用前言随着今年6月份的 HTTP/3 协议的正式发布#xff0c;它背后的网络传输协议 QUIC#xff0c;凭借其高效的传输效率和多路并发的能力#xff0c;也大概率会取代我们熟悉的使用了几十年的 TCP#xff0c;成为互联网的下一代标准传输协议。在去年 .NET 6 发布的时候#xf…前言随着今年6月份的 HTTP/3 协议的正式发布它背后的网络传输协议 QUIC凭借其高效的传输效率和多路并发的能力也大概率会取代我们熟悉的使用了几十年的 TCP成为互联网的下一代标准传输协议。在去年 .NET 6 发布的时候已经可以看到 HTTP/3 和 Quic 支持的相关内容了但是当时 HTTP/3 的 RFC 还没有定稿所以也只是预览功能而 Quic 的 API 也没有在 .NET 6 中公开。在最新的 .NET 7 中.NET 团队公开了 Quic API它是基于 MSQuic 库来实现的 提供了开箱即用的支持命名空间为 System.Net.Quic。Quic API下面的内容中我会介绍如何在 .NET 中使用 Quic。下面是 System.Net.Quic 命名空间下比较重要的几个类。QuicConnection表示一个 QUIC 连接本身不发送也不接收数据它可以打开或者接收多个QUIC 流。QuicListener用来监听入站的 Quic 连接一个 QuicListener 可以接收多个 Quic 连接。QuicStream表示 Quic 流它可以是单向的 QuicStreamType.Unidirectional只允许创建方写入数据也可以是双向的QuicStreamType.Bidirectional它允许两边都可以写入数据。小试牛刀下面是一个客户端和服务端应用使用 Quic 通信的示例。1. 分别创建了 QuicClient 和 QuicServer 两个控制台程序。项目的版本为 .NET 7 并且设置 EnablePreviewFeatures true。下面创建了一个 QuicListener监听了本地端口 9999指定了 ALPN 协议版本。Console.WriteLine(Quic Server Running...);// 创建 QuicListener
var listener await QuicListener.ListenAsync(new QuicListenerOptions
{ ApplicationProtocols new ListSslApplicationProtocol { SslApplicationProtocol.Http3 },ListenEndPoint new IPEndPoint(IPAddress.Loopback,9999), ConnectionOptionsCallback (connection,ssl, token) ValueTask.FromResult(new QuicServerConnectionOptions(){DefaultStreamErrorCode 0,DefaultCloseErrorCode 0,ServerAuthenticationOptions new SslServerAuthenticationOptions(){ApplicationProtocols new ListSslApplicationProtocol() { SslApplicationProtocol.Http3 },ServerCertificate GenerateManualCertificate()}})
});因为 Quic 需要 TLS 加密所以要指定一个证书GenerateManualCertificate 方法可以方便地创建一个本地的测试证书。X509Certificate2 GenerateManualCertificate()
{X509Certificate2 cert null;var store new X509Store(KestrelWebTransportCertificates, StoreLocation.CurrentUser);store.Open(OpenFlags.ReadWrite);if (store.Certificates.Count 0){cert store.Certificates[^1];// rotate key after it expiresif (DateTime.Parse(cert.GetExpirationDateString(), null) DateTimeOffset.UtcNow){cert null;}}if (cert null){// generate a new certvar now DateTimeOffset.UtcNow;SubjectAlternativeNameBuilder sanBuilder new();sanBuilder.AddDnsName(localhost);using var ec ECDsa.Create(ECCurve.NamedCurves.nistP256);CertificateRequest req new(CNlocalhost, ec, HashAlgorithmName.SHA256);// Adds purposereq.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection{new(1.3.6.1.5.5.7.3.1) // serverAuth}, false));// Adds usagereq.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, false));// Adds subject alternate namesreq.CertificateExtensions.Add(sanBuilder.Build());// Signusing var crt req.CreateSelfSigned(now, now.AddDays(14)); // 14 days is the max duration of a certificate for thiscert new(crt.Export(X509ContentType.Pfx));// Savestore.Add(cert);}store.Close();var hash SHA256.HashData(cert.RawData);var certStr Convert.ToBase64String(hash);//Console.WriteLine($\n\n\n\n\nCertificate: {certStr}\n\n\n\n); // -- you will need to put this output into the JS API call to allow the connectionreturn cert;
}阻塞线程直到接收到一个 Quic 连接一个 QuicListener 可以接收多个 连接。var connection await listener.AcceptConnectionAsync();Console.WriteLine($Client [{connection.RemoteEndPoint}]: connected);接收一个入站的 Quic 流, 一个 QuicConnection 可以支持多个流。var stream await connection.AcceptInboundStreamAsync();Console.WriteLine($Stream [{stream.Id}]: created);接下来使用 System.IO.Pipeline 处理流数据读取行数据并回复一个 ack 消息。Console.WriteLine();await ProcessLinesAsync(stream);Console.ReadKey(); // 处理流数据
async Task ProcessLinesAsync(QuicStream stream)
{var reader PipeReader.Create(stream); var writer PipeWriter.Create(stream);while (true){ReadResult result await reader.ReadAsync();ReadOnlySequencebyte buffer result.Buffer;while (TryReadLine(ref buffer, out ReadOnlySequencebyte line)){// 读取行数据ProcessLine(line);// 写入 ACK 消息await writer.WriteAsync(Encoding.UTF8.GetBytes($Ack: {DateTime.Now.ToString(HH:mm:ss)} \n));} reader.AdvanceTo(buffer.Start, buffer.End);if (result.IsCompleted){break;} }Console.WriteLine($Stream [{stream.Id}]: completed);await reader.CompleteAsync(); await writer.CompleteAsync();
} bool TryReadLine(ref ReadOnlySequencebyte buffer, out ReadOnlySequencebyte line)
{ SequencePosition? position buffer.PositionOf((byte)\n);if (position null){line default;return false;} line buffer.Slice(0, position.Value);buffer buffer.Slice(buffer.GetPosition(1, position.Value));return true;
} void ProcessLine(in ReadOnlySequencebyte buffer)
{foreach (var segment in buffer){Console.WriteLine(Recevied - System.Text.Encoding.UTF8.GetString(segment.Span));}Console.WriteLine();
}以上就是服务端的完整代码了。接下来我们看一下客户端 QuicClient 的代码。直接使用 QuicConnection.ConnectAsync 连接到服务端。Console.WriteLine(Quic Client Running...);await Task.Delay(3000);// 连接到服务端
var connection await QuicConnection.ConnectAsync(new QuicClientConnectionOptions
{DefaultCloseErrorCode 0,DefaultStreamErrorCode 0,RemoteEndPoint new IPEndPoint(IPAddress.Loopback, 9999),ClientAuthenticationOptions new SslClientAuthenticationOptions{ApplicationProtocols new ListSslApplicationProtocol { SslApplicationProtocol.Http3 },RemoteCertificateValidationCallback (sender, certificate, chain, errors) {return true;}}
});创建一个出站的双向流。// 打开一个出站的双向流
var stream await connection.OpenOutboundStreamAsync(QuicStreamType.Bidirectional); var reader PipeReader.Create(stream);
var writer PipeWriter.Create(stream);后台读取流数据然后循环写入数据。// 后台读取流数据
_ ProcessLinesAsync(stream);Console.WriteLine(); // 写入数据
for (int i 0; i 7; i)
{await Task.Delay(2000);var message $Hello Quic {i} \n;Console.Write(Send - message); await writer.WriteAsync(Encoding.UTF8.GetBytes(message));
}await writer.CompleteAsync(); Console.ReadKey();ProcessLinesAsync 和服务端一样使用 System.IO.Pipeline 读取流数据。async Task ProcessLinesAsync(QuicStream stream)
{while (true){ReadResult result await reader.ReadAsync();ReadOnlySequencebyte buffer result.Buffer;while (TryReadLine(ref buffer, out ReadOnlySequencebyte line)){ // 处理行数据ProcessLine(line);}reader.AdvanceTo(buffer.Start, buffer.End); if (result.IsCompleted){break;}}await reader.CompleteAsync();await writer.CompleteAsync();} bool TryReadLine(ref ReadOnlySequencebyte buffer, out ReadOnlySequencebyte line)
{ SequencePosition? position buffer.PositionOf((byte)\n);if (position null){line default;return false;}line buffer.Slice(0, position.Value);buffer buffer.Slice(buffer.GetPosition(1, position.Value));return true;
}void ProcessLine(in ReadOnlySequencebyte buffer)
{foreach (var segment in buffer){Console.Write(Recevied - System.Text.Encoding.UTF8.GetString(segment.Span));Console.WriteLine();}Console.WriteLine();
}到这里客户端和服务端的代码都完成了客户端使用 Quic 流发送了一些消息给服务端服务端收到消息后在控制台输出并回复一个 Ack 消息因为我们创建了一个双向流。程序的运行结果如下我们上面说到了一个 QuicConnection 可以创建多个流并行传输数据。改造一下服务端的代码支持接收多个 Quic 流。var cts new CancellationTokenSource();while (!cts.IsCancellationRequested)
{var stream await connection.AcceptInboundStreamAsync();Console.WriteLine($Stream [{stream.Id}]: created);Console.WriteLine();_ ProcessLinesAsync(stream);
} Console.ReadKey();对于客户端我们用多个线程创建多个 Quic 流并同时发送消息。默认情况下一个 Quic 连接的流的限制是 100当然你可以设置 QuicConnectionOptions 的 MaxInboundBidirectionalStreams 和 MaxInboundUnidirectionalStreams 参数。for (int j 0; j 5; j)
{_ Task.Run(async () {// 创建一个出站的双向流var stream await connection.OpenOutboundStreamAsync(QuicStreamType.Bidirectional); var writer PipeWriter.Create(stream); Console.WriteLine();await Task.Delay(2000);var message $Hello Quic [{stream.Id}] \n;Console.Write(Send - message);await writer.WriteAsync(Encoding.UTF8.GetBytes(message));await writer.CompleteAsync(); });
}最终程序的输出如下完整的代码可以在下面的 github 地址找到希望对您有用https://github.com/SpringLeee/PlayQuic