很抱歉,您提供的信息不完整,我无法直接给出答案。请您提供更具体的问题或信息,这样我才能更好地帮助您。

2026-03-30 15:171阅读0评论SEO教程
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计2650个文字,预计阅读时间需要11分钟。

很抱歉,您提供的信息不完整,我无法直接给出答案。请您提供更具体的问题或信息,这样我才能更好地帮助您。

前言:QUIC(Quick UDP Internet Connection)是谷歌制定的一种基于UDP的低延迟互联网传输层协议。

我们知道,TCP/IP协议族是互联网的基础。其中传输层协议包括TCP和UDP。

与TCP协议相比,QUIC协议具有以下特点:

前言

QUIC(Quick UDP Internet Connection)是谷歌制定的一种基于UDP的低时延的互联网传输层协议。我们知道,TCP/IP协议族是互联网的基础。其中传输层协议包括TCP和UDP协议。与TCP协议相比,UDP更为轻量,但是错误校验也要少得多。这意味着UDP往往效率更高(不经常跟服务器端通信查看数据包是否送达或者按序),但是可靠性比不上TCP。通常游戏、流媒体以及VoIP等应用均采用UDP,而网页、邮件、远程登录等大部分的应用均采用TCP。

QUIC很好地解决了当今传输层和应用层面临的各种需求,包括处理更多的连接,安全性,和低延迟。QUIC融合了包括TCP,TLS,HTTP/2等协议的特性,但基于UDP传输。QUIC的一个主要目标就是减少连接延迟,当客户端第一次连接服务器时,QUIC只需要1RTT(Round-Trip Time)的延迟就可以建立可靠安全的连接,相对于TCP+TLS的1-3次RTT要更加快捷。之后客户端可以在本地缓存加密的认证信息,再次与服务器建立连接时可以实现0-RTT的连接建立延迟。QUIC同时复用了HTTP/2协议的多路复用功能(Multiplexing),但由于QUIC基于UDP所以避免了HTTP/2的队头阻塞(Head-of-Line Blocking)问题。因为QUIC基于UDP,运行在用户域而不是系统内核,使得QUIC协议可以快速的更新和部署,从而很好地解决了TCP协议部署及更新的困难。

以下是TCP和Quic通信过程的示例图:

一、.NET 7中的Quic通信

1.下载.NET 7预览版

下载地址:dotnet.microsoft.com/zh-cn/download/dotnet/7.0

2.vs2022配置使用预览版SDK

很抱歉,您提供的信息不完整,我无法直接给出答案。请您提供更具体的问题或信息,这样我才能更好地帮助您。

3. .NET 中使用 Quic

下面是 System.Net.Quic 命名空间下,比较重要的几个类。

  • QuicConnection

表示一个 QUIC 连接,本身不发送也不接收数据,它可以打开或者接收多个QUIC 流。

  • QuicListener

用来监听入站的 Quic 连接,一个 QuicListener 可以接收多个 Quic 连接。

  • QuicStream

表示 Quic 流,它可以是单向的 (QuicStreamType.Unidirectional),只允许创建方写入数据,也可以是双向的(QuicStreamType.Bidirectional),它允许两边都可以写入数据。

4. .NET 中使用 Quic代码解析

4.1 服务端

建了一个 QuicListener,监听了本地端口 9999,指定了 ALPN 协议版本。

// 创建 QuicListener var listener = await QuicListener.ListenAsync(new QuicListenerOptions { ApplicationProtocols = new List<SslApplicationProtocol> { 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 List<SslApplicationProtocol>() { 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 expires if (DateTime.Parse(cert.GetExpirationDateString(), null) < DateTimeOffset.UtcNow) { cert = null; } } if (cert == null) { // generate a new cert var now = DateTimeOffset.UtcNow; SubjectAlternativeNameBuilder sanBuilder = new(); sanBuilder.AddDnsName("localhost"); using var ec = ECDsa.Create(ECCurve.NamedCurves.nistP256); CertificateRequest req = new("CN=localhost", ec, HashAlgorithmName.SHA256); // Adds purpose req.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new("1.3.6.1.5.5.7.3.1") // serverAuth }, false)); // Adds usage req.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, false)); // Adds subject alternate names req.CertificateExtensions.Add(sanBuilder.Build()); // Sign using var crt = req.CreateSelfSigned(now, now.AddDays(14)); // 14 days is the max duration of a certificate for this cert = new(crt.Export(X509ContentType.Pfx)); // Save store.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 connection return cert; }

阻塞线程,直到接收到一个 Quic 连接,一个 QuicListener 可以接收多个连接。并接收一个入站的 Quic 流, 一个 QuicConnection 可以支持多个流。

var connection = await listener.AcceptConnectionAsync(); Console.WriteLine($"Client [{connection.RemoteEndPoint}]: connected"); var stream = await connection.AcceptInboundStreamAsync(); Console.WriteLine($"Stream [{stream.Id}]: created");

使用 System.IO.Pipeline 处理流数据,读取行数据,并回复一个 ack 消息。

await ProcessLinesAsync(stream); // 处理流数据 async Task ProcessLinesAsync(QuicStream stream) { var reader = PipeReader.Create(stream); var writer = PipeWriter.Create(stream); while (true) { ReadResult result = await reader.ReadAsync(); ReadOnlySequence<byte> buffer = result.Buffer; while (TryReadLine(ref buffer, out ReadOnlySequence<byte> line)) { // Process the line. ProcessLine(line); // Ack //await writer.WriteAsync(System.Text.Encoding.UTF8.GetBytes($"ack: {DateTime.Now.ToString("HH:mm:ss")} \n")); } // Tell the PipeReader how much of the buffer has been consumed. reader.AdvanceTo(buffer.Start, buffer.End); // Stop reading if there's no more data coming. if (result.IsCompleted) { break; } } Console.WriteLine($"Stream [{stream.Id}]: completed"); await reader.CompleteAsync(); await writer.CompleteAsync(); } bool TryReadLine(ref ReadOnlySequence<byte> buffer, out ReadOnlySequence<byte> line) { // Look for a EOL in the buffer. SequencePosition? position = buffer.PositionOf((byte)'\n'); if (position == null) { line = default; return false; } // Skip the line + the \n. line = buffer.Slice(0, position.Value); buffer = buffer.Slice(buffer.GetPosition(1, position.Value)); return true; } void ProcessLine(in ReadOnlySequence<byte> buffer) { foreach (var segment in buffer) { Console.WriteLine("Recevied -> " + System.Text.Encoding.UTF8.GetString(segment.Span)); } Console.WriteLine(); }

4.2 客户端

4.2.1 单个流

直接使用 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 List<SslApplicationProtocol> { 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(); ReadOnlySequence<byte> buffer = result.Buffer; while (TryReadLine(ref buffer, out ReadOnlySequence<byte> line)) { // 处理行数据 ProcessLine(line); } reader.AdvanceTo(buffer.Start, buffer.End); if (result.IsCompleted) { break; } } await reader.CompleteAsync(); await writer.CompleteAsync(); } bool TryReadLine(ref ReadOnlySequence<byte> buffer, out ReadOnlySequence<byte> 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 ReadOnlySequence<byte> buffer) { foreach (var segment in buffer) { Console.Write("Recevied -> " + System.Text.Encoding.UTF8.GetString(segment.Span)); Console.WriteLine(); } Console.WriteLine(); }

到这里,客户端和服务端的代码都完成了,客户端使用 Quic 流发送了一些消息给服务端,服务端收到消息后在控制台输出,并回复一个 Ack 消息,因为我们创建了一个双向流。

4.2.2 多个流

我们上面说到了一个 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(); }); }

目录

完整服务端:

using System; using System.Buffers; using System.IO.Pipelines; using System.IO.Pipes; using System.Net; using System.Net.Quic; using System.Net.Security; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; Console.WriteLine("Quic Server Running..."); // 创建 QuicListener var listener = await QuicListener.ListenAsync(new QuicListenerOptions { ApplicationProtocols = new List<SslApplicationProtocol> { 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 List<SslApplicationProtocol>() { SslApplicationProtocol.Http3 }, ServerCertificate = 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 expires if (DateTime.Parse(cert.GetExpirationDateString(), null) < DateTimeOffset.UtcNow) { cert = null; } } if (cert == null) { // generate a new cert var now = DateTimeOffset.UtcNow; SubjectAlternativeNameBuilder sanBuilder = new(); sanBuilder.AddDnsName("localhost"); using var ec = ECDsa.Create(ECCurve.NamedCurves.nistP256); CertificateRequest req = new("CN=localhost", ec, HashAlgorithmName.SHA256); // Adds purpose req.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new("1.3.6.1.5.5.7.3.1") // serverAuth }, false)); // Adds usage req.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, false)); // Adds subject alternate names req.CertificateExtensions.Add(sanBuilder.Build()); // Sign using var crt = req.CreateSelfSigned(now, now.AddDays(14)); // 14 days is the max duration of a certificate for this cert = new(crt.Export(X509ContentType.Pfx)); // Save store.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 connection return cert; } var connection = await listener.AcceptConnectionAsync(); Console.WriteLine($"Client [{connection.RemoteEndPoint}]: connected"); var cts = new CancellationTokenSource(); while (!cts.IsCancellationRequested) { var stream = await connection.AcceptInboundStreamAsync(); Console.WriteLine($"Stream [{stream.Id}]: created"); Console.WriteLine(); _ = 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(); ReadOnlySequence<byte> buffer = result.Buffer; while (TryReadLine(ref buffer, out ReadOnlySequence<byte> line)) { // Process the line. ProcessLine(line); // Ack //await writer.WriteAsync(System.Text.Encoding.UTF8.GetBytes($"ack: {DateTime.Now.ToString("HH:mm:ss")} \n")); } // Tell the PipeReader how much of the buffer has been consumed. reader.AdvanceTo(buffer.Start, buffer.End); // Stop reading if there's no more data coming. if (result.IsCompleted) { break; } } Console.WriteLine($"Stream [{stream.Id}]: completed"); await reader.CompleteAsync(); await writer.CompleteAsync(); } bool TryReadLine(ref ReadOnlySequence<byte> buffer, out ReadOnlySequence<byte> line) { // Look for a EOL in the buffer. SequencePosition? position = buffer.PositionOf((byte)'\n'); if (position == null) { line = default; return false; } // Skip the line + the \n. line = buffer.Slice(0, position.Value); buffer = buffer.Slice(buffer.GetPosition(1, position.Value)); return true; } void ProcessLine(in ReadOnlySequence<byte> buffer) { foreach (var segment in buffer) { Console.WriteLine("Recevied -> " + System.Text.Encoding.UTF8.GetString(segment.Span)); } Console.WriteLine(); }

完整客户端:

using System.Buffers; using System.IO; using System.IO.Pipelines; using System.IO.Pipes; using System.Net; using System.Net.Quic; using System.Net.Security; using System.Reflection.PortableExecutable; using System.Text; using System.Xml.Linq; 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 List<SslApplicationProtocol> { SslApplicationProtocol.Http3 }, RemoteCertificateValidationCallback = (sender, certificate, chain, errors) => { return true; } } }); 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(); }); } Console.ReadKey();

本文共计2650个文字,预计阅读时间需要11分钟。

很抱歉,您提供的信息不完整,我无法直接给出答案。请您提供更具体的问题或信息,这样我才能更好地帮助您。

前言:QUIC(Quick UDP Internet Connection)是谷歌制定的一种基于UDP的低延迟互联网传输层协议。

我们知道,TCP/IP协议族是互联网的基础。其中传输层协议包括TCP和UDP。

与TCP协议相比,QUIC协议具有以下特点:

前言

QUIC(Quick UDP Internet Connection)是谷歌制定的一种基于UDP的低时延的互联网传输层协议。我们知道,TCP/IP协议族是互联网的基础。其中传输层协议包括TCP和UDP协议。与TCP协议相比,UDP更为轻量,但是错误校验也要少得多。这意味着UDP往往效率更高(不经常跟服务器端通信查看数据包是否送达或者按序),但是可靠性比不上TCP。通常游戏、流媒体以及VoIP等应用均采用UDP,而网页、邮件、远程登录等大部分的应用均采用TCP。

QUIC很好地解决了当今传输层和应用层面临的各种需求,包括处理更多的连接,安全性,和低延迟。QUIC融合了包括TCP,TLS,HTTP/2等协议的特性,但基于UDP传输。QUIC的一个主要目标就是减少连接延迟,当客户端第一次连接服务器时,QUIC只需要1RTT(Round-Trip Time)的延迟就可以建立可靠安全的连接,相对于TCP+TLS的1-3次RTT要更加快捷。之后客户端可以在本地缓存加密的认证信息,再次与服务器建立连接时可以实现0-RTT的连接建立延迟。QUIC同时复用了HTTP/2协议的多路复用功能(Multiplexing),但由于QUIC基于UDP所以避免了HTTP/2的队头阻塞(Head-of-Line Blocking)问题。因为QUIC基于UDP,运行在用户域而不是系统内核,使得QUIC协议可以快速的更新和部署,从而很好地解决了TCP协议部署及更新的困难。

以下是TCP和Quic通信过程的示例图:

一、.NET 7中的Quic通信

1.下载.NET 7预览版

下载地址:dotnet.microsoft.com/zh-cn/download/dotnet/7.0

2.vs2022配置使用预览版SDK

很抱歉,您提供的信息不完整,我无法直接给出答案。请您提供更具体的问题或信息,这样我才能更好地帮助您。

3. .NET 中使用 Quic

下面是 System.Net.Quic 命名空间下,比较重要的几个类。

  • QuicConnection

表示一个 QUIC 连接,本身不发送也不接收数据,它可以打开或者接收多个QUIC 流。

  • QuicListener

用来监听入站的 Quic 连接,一个 QuicListener 可以接收多个 Quic 连接。

  • QuicStream

表示 Quic 流,它可以是单向的 (QuicStreamType.Unidirectional),只允许创建方写入数据,也可以是双向的(QuicStreamType.Bidirectional),它允许两边都可以写入数据。

4. .NET 中使用 Quic代码解析

4.1 服务端

建了一个 QuicListener,监听了本地端口 9999,指定了 ALPN 协议版本。

// 创建 QuicListener var listener = await QuicListener.ListenAsync(new QuicListenerOptions { ApplicationProtocols = new List<SslApplicationProtocol> { 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 List<SslApplicationProtocol>() { 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 expires if (DateTime.Parse(cert.GetExpirationDateString(), null) < DateTimeOffset.UtcNow) { cert = null; } } if (cert == null) { // generate a new cert var now = DateTimeOffset.UtcNow; SubjectAlternativeNameBuilder sanBuilder = new(); sanBuilder.AddDnsName("localhost"); using var ec = ECDsa.Create(ECCurve.NamedCurves.nistP256); CertificateRequest req = new("CN=localhost", ec, HashAlgorithmName.SHA256); // Adds purpose req.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new("1.3.6.1.5.5.7.3.1") // serverAuth }, false)); // Adds usage req.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, false)); // Adds subject alternate names req.CertificateExtensions.Add(sanBuilder.Build()); // Sign using var crt = req.CreateSelfSigned(now, now.AddDays(14)); // 14 days is the max duration of a certificate for this cert = new(crt.Export(X509ContentType.Pfx)); // Save store.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 connection return cert; }

阻塞线程,直到接收到一个 Quic 连接,一个 QuicListener 可以接收多个连接。并接收一个入站的 Quic 流, 一个 QuicConnection 可以支持多个流。

var connection = await listener.AcceptConnectionAsync(); Console.WriteLine($"Client [{connection.RemoteEndPoint}]: connected"); var stream = await connection.AcceptInboundStreamAsync(); Console.WriteLine($"Stream [{stream.Id}]: created");

使用 System.IO.Pipeline 处理流数据,读取行数据,并回复一个 ack 消息。

await ProcessLinesAsync(stream); // 处理流数据 async Task ProcessLinesAsync(QuicStream stream) { var reader = PipeReader.Create(stream); var writer = PipeWriter.Create(stream); while (true) { ReadResult result = await reader.ReadAsync(); ReadOnlySequence<byte> buffer = result.Buffer; while (TryReadLine(ref buffer, out ReadOnlySequence<byte> line)) { // Process the line. ProcessLine(line); // Ack //await writer.WriteAsync(System.Text.Encoding.UTF8.GetBytes($"ack: {DateTime.Now.ToString("HH:mm:ss")} \n")); } // Tell the PipeReader how much of the buffer has been consumed. reader.AdvanceTo(buffer.Start, buffer.End); // Stop reading if there's no more data coming. if (result.IsCompleted) { break; } } Console.WriteLine($"Stream [{stream.Id}]: completed"); await reader.CompleteAsync(); await writer.CompleteAsync(); } bool TryReadLine(ref ReadOnlySequence<byte> buffer, out ReadOnlySequence<byte> line) { // Look for a EOL in the buffer. SequencePosition? position = buffer.PositionOf((byte)'\n'); if (position == null) { line = default; return false; } // Skip the line + the \n. line = buffer.Slice(0, position.Value); buffer = buffer.Slice(buffer.GetPosition(1, position.Value)); return true; } void ProcessLine(in ReadOnlySequence<byte> buffer) { foreach (var segment in buffer) { Console.WriteLine("Recevied -> " + System.Text.Encoding.UTF8.GetString(segment.Span)); } Console.WriteLine(); }

4.2 客户端

4.2.1 单个流

直接使用 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 List<SslApplicationProtocol> { 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(); ReadOnlySequence<byte> buffer = result.Buffer; while (TryReadLine(ref buffer, out ReadOnlySequence<byte> line)) { // 处理行数据 ProcessLine(line); } reader.AdvanceTo(buffer.Start, buffer.End); if (result.IsCompleted) { break; } } await reader.CompleteAsync(); await writer.CompleteAsync(); } bool TryReadLine(ref ReadOnlySequence<byte> buffer, out ReadOnlySequence<byte> 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 ReadOnlySequence<byte> buffer) { foreach (var segment in buffer) { Console.Write("Recevied -> " + System.Text.Encoding.UTF8.GetString(segment.Span)); Console.WriteLine(); } Console.WriteLine(); }

到这里,客户端和服务端的代码都完成了,客户端使用 Quic 流发送了一些消息给服务端,服务端收到消息后在控制台输出,并回复一个 Ack 消息,因为我们创建了一个双向流。

4.2.2 多个流

我们上面说到了一个 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(); }); }

目录

完整服务端:

using System; using System.Buffers; using System.IO.Pipelines; using System.IO.Pipes; using System.Net; using System.Net.Quic; using System.Net.Security; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; Console.WriteLine("Quic Server Running..."); // 创建 QuicListener var listener = await QuicListener.ListenAsync(new QuicListenerOptions { ApplicationProtocols = new List<SslApplicationProtocol> { 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 List<SslApplicationProtocol>() { SslApplicationProtocol.Http3 }, ServerCertificate = 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 expires if (DateTime.Parse(cert.GetExpirationDateString(), null) < DateTimeOffset.UtcNow) { cert = null; } } if (cert == null) { // generate a new cert var now = DateTimeOffset.UtcNow; SubjectAlternativeNameBuilder sanBuilder = new(); sanBuilder.AddDnsName("localhost"); using var ec = ECDsa.Create(ECCurve.NamedCurves.nistP256); CertificateRequest req = new("CN=localhost", ec, HashAlgorithmName.SHA256); // Adds purpose req.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new("1.3.6.1.5.5.7.3.1") // serverAuth }, false)); // Adds usage req.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, false)); // Adds subject alternate names req.CertificateExtensions.Add(sanBuilder.Build()); // Sign using var crt = req.CreateSelfSigned(now, now.AddDays(14)); // 14 days is the max duration of a certificate for this cert = new(crt.Export(X509ContentType.Pfx)); // Save store.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 connection return cert; } var connection = await listener.AcceptConnectionAsync(); Console.WriteLine($"Client [{connection.RemoteEndPoint}]: connected"); var cts = new CancellationTokenSource(); while (!cts.IsCancellationRequested) { var stream = await connection.AcceptInboundStreamAsync(); Console.WriteLine($"Stream [{stream.Id}]: created"); Console.WriteLine(); _ = 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(); ReadOnlySequence<byte> buffer = result.Buffer; while (TryReadLine(ref buffer, out ReadOnlySequence<byte> line)) { // Process the line. ProcessLine(line); // Ack //await writer.WriteAsync(System.Text.Encoding.UTF8.GetBytes($"ack: {DateTime.Now.ToString("HH:mm:ss")} \n")); } // Tell the PipeReader how much of the buffer has been consumed. reader.AdvanceTo(buffer.Start, buffer.End); // Stop reading if there's no more data coming. if (result.IsCompleted) { break; } } Console.WriteLine($"Stream [{stream.Id}]: completed"); await reader.CompleteAsync(); await writer.CompleteAsync(); } bool TryReadLine(ref ReadOnlySequence<byte> buffer, out ReadOnlySequence<byte> line) { // Look for a EOL in the buffer. SequencePosition? position = buffer.PositionOf((byte)'\n'); if (position == null) { line = default; return false; } // Skip the line + the \n. line = buffer.Slice(0, position.Value); buffer = buffer.Slice(buffer.GetPosition(1, position.Value)); return true; } void ProcessLine(in ReadOnlySequence<byte> buffer) { foreach (var segment in buffer) { Console.WriteLine("Recevied -> " + System.Text.Encoding.UTF8.GetString(segment.Span)); } Console.WriteLine(); }

完整客户端:

using System.Buffers; using System.IO; using System.IO.Pipelines; using System.IO.Pipes; using System.Net; using System.Net.Quic; using System.Net.Security; using System.Reflection.PortableExecutable; using System.Text; using System.Xml.Linq; 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 List<SslApplicationProtocol> { SslApplicationProtocol.Http3 }, RemoteCertificateValidationCallback = (sender, certificate, chain, errors) => { return true; } } }); 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(); }); } Console.ReadKey();