2023年01月 .NET CORE工具案例-.NET 7中的WebTransport通信
Posted 微软MVP Eleven
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2023年01月 .NET CORE工具案例-.NET 7中的WebTransport通信相关的知识,希望对你有一定的参考价值。
文章目录
前言
1.技术背景
如今对于网络进行实时通讯的要求越来越高,相关于网络进行实时通讯技术应运而生,如WebRTC,QUIC,HTTP3,WebTransport,WebAssembly,WebCodecs等。
2.QUIC相关概念
QUIC相关文章请看:https://blog.csdn.net/aa2528877987/article/details/127744186
3.HTTP/3.0
- 基于QUIC协议:天然复用QUIC的各种优势;
- 新的HTTP头压缩机制:QPACK,是对HTTP/2中使用的HPACK的增强。在QPACK下,HTTP头可以在不同的QUIC流中不按顺序到达。
一、WebTransport
1.WebTransport概念
WebTransport 是一个协议框架,该协议使客户端与远程服务器在安全模型下通信,并且采用安全多路复用传输技术。最新版的WebTransport草案中,该协议是基于HTTP3的,即WebTransport可天然复用QUIC和HTTP3的特性。
WebTransport 是一个新的 Web API,使用 HTTP/3 协议来支持双向传输。它用于 Web 客户端和 HTTP/3 服务器之间的双向通信。它支持通过 不可靠的 Datagrams API 发送数据,也支持可靠的 Stream API 发送数据。
WebTransport 支持三种不同类型的流量:数据报(datagrams) 以及单向流和双向流。
WebTransport 的设计基于现代 Web 平台基本类型(比如 Streams API)。它在很大程度上依赖于 promise,并且可以很好地与 async 和 await 配合使用。
2.WebTransport在js中的使用
W3C的文档关于支持WebTransport的后台服务进行通信:https://www.w3.org/TR/webtransport/
let transport = new WebTransport("https://x.x.x.x");
await transport.ready;
let stream = await transport.createBidirectionalStream();
let encoder = new TextEncoder();
let writer = stream.writable.getWriter();
await writer.write(encoder.encode("Hello, world! What's your name?"))
writer.close();
console.log(await new Response(stream.readable).text());
3.WebTransport在.NET 7中的使用
3.1 创建项目
新建一个.NET Core 的空项目,修改 csproj 文件
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<AspNetCoreHostingModel>OutOfProcess</AspNetCoreHostingModel>
<!-- 1.Turn on preview features -->
<EnablePreviewFeatures>True</EnablePreviewFeatures>
</PropertyGroup>
<ItemGroup>
<!-- 2.Turn on the WebTransport AppContext switch -->
<RuntimeHostConfigurationOption Include="Microsoft.AspNetCore.Server.Kestrel.Experimental.WebTransportAndH3Datagrams" Value="true" />
</ItemGroup>
</Project>
3.2 侦听HTTP/3端口
要设置 WebTransport 连接,首先需要配置 Web 主机并通过 HTTP/3 侦听端口:
// 配置端口
builder.WebHost.ConfigureKestrel((context, options) =>
// 配置web站点端口
options.Listen(IPAddress.Any, 5551, listenOptions =>
listenOptions.UseHttps();
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
);
// 配置webtransport端口
options.Listen(IPAddress.Any, 5552, listenOptions =>
listenOptions.UseHttps(certificate);
listenOptions.UseConnectionLogging();
listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
);
);
3.3 获取WebTransport会话
app.Run(async (context) =>
var feature = context.Features.GetRequiredFeature<IHttpWebTransportFeature>();
if (!feature.IsWebTransportRequest)
return;
var session = await feature.AcceptAsync(CancellationToken.None);
);
await app.RunAsync();
3.4 监听WebTransport请求
var stream = await session.AcceptStreamAsync(CancellationToken.None);
var inputPipe = stream.Transport.Input;
var outputPipe = stream.Transport.Output;
4.WebTransport在javascript中使用
4.1 创建 WebTransport 实例
传入服务地址并创建 WebTransport 实例, transport.ready 完成,此时连接就可以使用了。
const url = 'https://localhost:5002';
const transport = new WebTransport(url);
await transport.ready;
4.2 通信测试
// Send two Uint8Arrays to the server.
const stream = await transport.createSendStream();
const writer = stream.writable.getWriter();
const data1 = new Uint8Array([65, 66, 67]);
const data2 = new Uint8Array([68, 69, 70]);
writer.write(data1);
writer.write(data2);
try
await writer.close();
console.log('All data has been sent.');
catch (error)
console.error(`An error occurred: $error`);
二、WebTransport通信完整源码
1.服务端
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable enable
using System.Net;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core;
var builder = WebApplication.CreateBuilder(args);
// 生成密钥
var certificate = GenerateManualCertificate();
var hash = SHA256.HashData(certificate.RawData);
var certStr = Convert.ToBase64String(hash);
// 配置端口
builder.WebHost.ConfigureKestrel((context, options) =>
// 配置web站点端口
options.Listen(IPAddress.Any, 5551, listenOptions =>
listenOptions.UseHttps();
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
);
// 配置webtransport端口
options.Listen(IPAddress.Any, 5552, listenOptions =>
listenOptions.UseHttps(certificate);
listenOptions.UseConnectionLogging();
listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
);
);
var app = builder.Build();
// 使可index.html访问
app.UseFileServer();
app.Use(async (context, next) =>
// 配置/certificate.js以注入证书哈希
if (context.Request.Path.Value?.Equals("/certificate.js") ?? false)
context.Response.ContentType = "application/javascript";
await context.Response.WriteAsync($"var CERTIFICATE = 'certStr';");
// 配置服务器端应用程序
else
//判断是否是WebTransport请求
var feature = context.Features.GetRequiredFeature<IHttpWebTransportFeature>();
if (!feature.IsWebTransportRequest)
await next(context);
//获取判断是否是WebTransport请求会话
var session = await feature.AcceptAsync(CancellationToken.None);
if (session is null)
return;
while (true)
ConnectionContext? stream = null;
IStreamDirectionFeature? direction = null;
// 等待消息
stream = await session.AcceptStreamAsync(CancellationToken.None);
if (stream is not null)
direction = stream.Features.GetRequiredFeature<IStreamDirectionFeature>();
if (direction.CanRead && direction.CanWrite)
//单向流
_ = handleBidirectionalStream(session, stream);
else
//双向流
_ = handleUnidirectionalStream(session, stream);
);
await app.RunAsync();
static async Task handleUnidirectionalStream(IWebTransportSession session, ConnectionContext stream)
var inputPipe = stream.Transport.Input;
// 将流中的一些数据读入内存
var memory = new Memory<byte>(new byte[4096]);
while (!stream.ConnectionClosed.IsCancellationRequested)
var length = await inputPipe.AsStream().ReadAsync(memory);
var message = Encoding.Default.GetString(memory[..length].ToArray());
await ApplySpecialCommands(session, message);
Console.WriteLine("RECEIVED FROM CLIENT:");
Console.WriteLine(message);
static async Task handleBidirectionalStream(IWebTransportSession session, ConnectionContext stream)
var inputPipe = stream.Transport.Input;
var outputPipe = stream.Transport.Output;
// 将流中的一些数据读入内存
var memory = new Memory<byte>(new byte[4096]);
while (!stream.ConnectionClosed.IsCancellationRequested)
var length = await inputPipe.AsStream().ReadAsync(memory);
// 切片以仅保留内存的相关部分
var outputMemory = memory[..length];
// 处理特殊命令
await ApplySpecialCommands(session, Encoding.Default.GetString(outputMemory.ToArray()));
// 对数据内容进行一些操作
outputMemory.Span.Reverse();
// 将数据写回流
await outputPipe.WriteAsync(outputMemory);
memory.Span.Fill(0);
static async Task ApplySpecialCommands(IWebTransportSession session, string message)
switch (message)
case "Initiate Stream":
var stream = await session.OpenUnidirectionalStreamAsync();
if (stream is not null)
await stream.Transport.Output.WriteAsync(new("Created a new stream from the client and sent this message then closing the stream."u8.ToArray()));
break;
case "Abort":
session.Abort(256 /*No error*/);
break;
default:
break; // in all other cases the string is not a special command
// Adapted from: https://github.com/wegylexy/webtransport
// We will need to eventually merge this with existing Kestrel certificate generation
// tracked in issue #41762
static X509Certificate2 GenerateManualCertificate()
X509Certificate2 cert;
var store = new X509Store("KestrelSampleWebTransportCertificates", 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)
store.Close();
return cert;
// 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();
return cert;
2.客户端
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>WebTransport Test Page</title>
</head>
<body>
<script src="certificate.js"></script>
<div id="panel">
<h1 id="title">WebTransport Test Page</h1>
<h3 id="stateLabel">Ready to connect...</h3>
<div>
<label for="connectionUrl">WebTransport Server URL:</label>
<input id="connectionUrl" value="https://127.0.0.1:5552" disabled />
<p>Due to the need to synchronize certificates, you cannot modify the url at this time.</p>
<button id="connectButton" type="submit" onclick="connect()">Connect</button>
</div>
<div id="communicationPanel" hidden>
<div>
<button id="closeStream" type="submit" onclick="closeActiveStream()">Close active stream</button>
<button id="closeConnection" type="submit" onclick="closeConnection()">Close connection</button>
<button id="createBidirectionalStream" type="submit" onclick="createStream('bidirectional')">Create bidirectional stream</button>
<button id="createUnidirectionalStream" type="submit" onclick="createStream('output unidirectional')">Create unidirectional stream</button>
</div>
<h2>Open Streams</h2>
<div id="streamsList">
<p id="noStreams">Empty</p>
</div>
<div>
<h2>Send Message</h2>
<textarea id="messageInput" name="text" rows="12" cols="50"></textarea>
<button id="sendMessageButton" type="submit" onclick="sendMessage()">Send</button>
</div>
</div>
</div>
<div id="communicationLogPanel"&g以上是关于2023年01月 .NET CORE工具案例-.NET 7中的WebTransport通信的主要内容,如果未能解决你的问题,请参考以下文章
#yyds干货盘点#愚公系列2023年03月 .NET CORE工具案例-.NET Core使用QuestPDF
愚公系列2023年02月 .NET CORE工具案例-使用MailKit使用SMTP协议进行邮件发送
愚公系列2023年02月 .NET CORE工具案例-使用MailKit使用POP3协议进行邮件读取
#yyds干货盘点#愚公系列2023年03月 .NET CORE工具案例-StackExchange.Redis代码变量方式实现商品秒杀