.NET 7 的 QUIC 实现 Echo 服务

2022-12-19 0 306

原副标题:.NET 7 的 QUIC 同时实现 Echo 服务项目

序言

随著去年6月末的 HTTP/3 协定的正式宣布正式宣布发布,它另一面的互联网数据传输协定 QUIC,凭借着其高工作效率的数据传输工作效率和分路mammalian的潜能,也大机率会替代他们熟识的采用了数十年的 TCP,正式成为互联网的新一代国际标准数据传输协定。

在去年 .NET 6 正式宣布发布的这时候,早已能看见 HTTP/3 和 Quic 全力支持的有关文本了,但彼时 HTTP/3 的 RFC 还没完稿,因此也而已自动更新机能,而 Quic 的 API 也没在 .NET 6 中申明。

在新一代的 .NET 7 中,.NET 项目组申明了 Quic API,它是如前所述 MSQuic 程序库同时实现的 , 提供更多了照相狸尾豆的全力支持,重新命名内部空间为 System.Net.Quic。

.NET 7 的 QUIC 实现 Echo 服务

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 的 QUIC 实现 Echo 服务

项目的版本为 .NET 7, 并且设置 EnablePreviewFeatures = true。上面创建了两个 QuicListener,窃听了本地端口 9999,指定了 ALPN 协定版本。

Console.WriteLine( “Quic Server Running…”);

// 创建 QuicListener

varlistener = awaitQuicListener.ListenAsync(newQuicListenerOptions

{

ApplicationProtocols = newList<SslApplicationProtocol> { SslApplicationProtocol.Http3 },

ListenEndPoint = newIPEndPoint(IPAddress.Loopback, 9999),

ConnectionOptionsCallback = (connection,ssl, token) => ValueTask.FromResult(newQuicServerConnectionOptions

{

DefaultStreamErrorCode =0,

DefaultCloseErrorCode = 0,

ServerAuthenticationOptions = newSslServerAuthenticationOptions

{

ApplicationProtocols =newList<SslApplicationProtocol> { SslApplicationProtocol.Http3 },

ServerCertificate = GenerateManualCertificate

}

})

});

因为 Quic 需要 TLS 加密,因此要指定两个证书,GenerateManualCertificate 方法能方便地创建两个本地的测试证书。

X509Certificate2 GenerateManualCertificate

{

X509Certificate2 cert = null;

varstore = newX509Store(“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

varnow = DateTimeOffset.UtcNow;

SubjectAlternativeNameBuilder sanBuilder = new;

sanBuilder.AddDnsName(“localhost”);

usingvarec = ECDsa.Create(ECCurve.NamedCurves.nistP256);

CertificateRequest req =new( “CN=localhost”, ec, HashAlgorithmName.SHA256);

// Adds purpose

req.CertificateExtensions.Add(newX509EnhancedKeyUsageExtension( newOidCollection

{

new( “1.3.6.1.5.5.7.3.1”) // serverAuth

}, false));

// Adds usage

req.CertificateExtensions.Add( newX509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature,false));

// Adds subject alternate names

req.CertificateExtensions.Add(sanBuilder.Build);

// Sign

usingvarcrt = 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;

varhash = SHA256.HashData(cert.RawData);

varcertStr = 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

returncert;

}

阻塞线程,直到转交到两个 Quic 相连,两个 QuicListener 能转交数个 相连。

varconnection = awaitlistener.AcceptConnectionAsync;

Console.WriteLine( $”Client [ {connection.RemoteEndPoint}]: connected” );

转交两个入站的 Quic 流, 两个 QuicConnection 能全力支持数个流。

varstream = awaitconnection.AcceptInboundStreamAsync;

Console.WriteLine($”Stream [ {stream.Id}]: created” );

Console.WriteLine;

awaitProcessLinesAsync(stream);

Console.ReadKey;

// 处理流数据

asyncTask ProcessLinesAsync( QuicStream stream)

{

varreader = PipeReader.Create(stream);

varwriter = PipeWriter.Create(stream);

while( true)

{

ReadResult result = awaitreader.ReadAsync;

ReadOnlySequence< byte> buffer = result.Buffer;

while(TryReadLine(refbuffer, outReadOnlySequence< byte> line))

{

// 读取行数据

ProcessLine(line);

// 写入 ACK 消息

awaitwriter.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”);

awaitreader.CompleteAsync;

awaitwriter.CompleteAsync;

}

boolTryReadLine( refReadOnlySequence<byte> buffer, outReadOnlySequence< byte> line )

{

SequencePosition? position = buffer.PositionOf(( byte)\n);

if(position == null)

{

line = default;

returnfalse;

}

line = buffer.Slice( 0, position.Value);

buffer = buffer.Slice(buffer.GetPosition(1, position.Value));

returntrue;

}

voidProcessLine( inReadOnlySequence< byte> buffer )

{

foreach( varsegment inbuffer)

{

Console.WriteLine( “Recevied -> “+ System.Text.Encoding.UTF8.GetString(segment.Span));

}

Console.WriteLine;

}

以上就是服务项目端的完整代码了。接下来他们看一下客户端 QuicClient 的代码。直接采用 QuicConnection.ConnectAsync 相连到服务项目端。

Console.WriteLine( “Quic Client Running…”);

awaitTask.Delay( 3000);

// 相连到服务项目端

varconnection = awaitQuicConnection.ConnectAsync( newQuicClientConnectionOptions

{

DefaultCloseErrorCode =0,

DefaultStreamErrorCode = 0,

RemoteEndPoint = newIPEndPoint(IPAddress.Loopback, 9999),

ClientAuthenticationOptions =newSslClientAuthenticationOptions

{

ApplicationProtocols = newList<SslApplicationProtocol> { SslApplicationProtocol.Http3 },

RemoteCertificateValidationCallback = (sender, certificate, chain, errors) =>

{

returntrue;

}

}

});

创建两个出站的双向流。

// 关上两个出站的双向流

varstream = awaitconnection.OpenOutboundStreamAsync(QuicStreamType.Bidirectional);

varreader = PipeReader.Create(stream);

varwriter = PipeWriter.Create(stream);

后台读取流数据,然后循环写入数据。

// 后台读取流数据

_ = ProcessLinesAsync(stream);

Console.WriteLine;

// 写入数据

for( inti = 0; i < 7; i++)

{

awaitTask.Delay( 2000);

varmessage = $”Hello Quic {i}\n” ;

Console.Write( “Send -> “+ message);

awaitwriter.WriteAsync(Encoding.UTF8.GetBytes(message));

}

awaitwriter.CompleteAsync;

Console.ReadKey;

ProcessLinesAsync 和服务项目端一样,采用 System.IO.Pipeline 读取流数据。

asyncTask ProcessLinesAsync( QuicStream stream)

{

while( true)

{

ReadResult result = awaitreader.ReadAsync;

ReadOnlySequence< byte> buffer = result.Buffer;

while(TryReadLine( refbuffer, outReadOnlySequence< byte> line))

{

// 处理行数据

ProcessLine(line);

}

reader.AdvanceTo(buffer.Start, buffer.End);

if(result.IsCompleted)

{

break;

}

}

awaitreader.CompleteAsync;

awaitwriter.CompleteAsync;

}

boolTryReadLine( refReadOnlySequence< byte> buffer, outReadOnlySequence< byte> line )

{

SequencePosition? position = buffer.PositionOf((byte) \n);

if(position == null)

{

line = default;

returnfalse;

}

line = buffer.Slice( 0, position.Value);

buffer = buffer.Slice(buffer.GetPosition( 1, position.Value));

returntrue;

}

voidProcessLine( inReadOnlySequence< byte> buffer )

{

foreach( varsegment inbuffer)

{

Console.Write(“Recevied -> “+ System.Text.Encoding.UTF8.GetString(segment.Span));

Console.WriteLine;

}

Console.WriteLine;

}

果如下

他们上面说到了两个 QuicConnection 能创建数个流,并行数据传输数据。改造一下服务项目端的代码,全力支持转交数个 Quic 流。

varcts = newCancellationTokenSource;

while(!cts.IsCancellationRequested)

{

varstream = awaitconnection.AcceptInboundStreamAsync;

Console.WriteLine($”Stream [ {stream.Id}]: created” );

Console.WriteLine;

_ = ProcessLinesAsync(stream);

}

Console.ReadKey;

对于客户端,他们用数个线程创建数个 Quic 流,并同时推送消息。默认情况下,两个 Quic 相连的流的限制是 100,当然你能设置 QuicConnectionOptions 的 MaxInboundBidirectionalStreams 和 MaxInboundUnidirectionalStreams 参数。

for( intj = 0; j < 5; j++)

{

_ = Task.Run(async=> {

// 创建两个出站的双向流

varstream = awaitconnection.OpenOutboundStreamAsync(QuicStreamType.Bidirectional);

varwriter = PipeWriter.Create(stream);

Console.WriteLine;

awaitTask.Delay( 2000);

varmessage = $”Hello Quic [{stream.Id}] \n” ;

Console.Write( “Send -> “+ message);

awaitwriter.WriteAsync(Encoding.UTF8.GetBytes(message));

awaitwriter.CompleteAsync;

});

}

最终程序的输出如下

完整的代码能在上面的 github 地址找到,希望对你有用!https://github.com/SpringLeee/PlayQuic

– EOF –

点击副标题可跳转

C# 的 async/await 其实是stackless coroutine

.NET 快速创建软件安装包 ClickOnce

C# 9.0 添加和增强的机能

看完本文有收获?请转发分享给更多人

推荐关注「DotNet」,提升.Net技能

点赞和在看就是最大的全力支持❤️

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务