日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > asp.net >内容正文

asp.net

【愚公系列】2022年11月 .NET CORE工具案例-.NET 7中的WebTransport通信

發布時間:2024/3/13 asp.net 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【愚公系列】2022年11月 .NET CORE工具案例-.NET 7中的WebTransport通信 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • 前言
    • 1.技術背景
    • 2.QUIC相關概念
    • 3.HTTP/3.0
  • 一、WebTransport
    • 1.WebTransport概念
    • 2.WebTransport在js中的使用
    • 3.WebTransport在.NET 7中的使用
      • 3.1 創建項目
      • 3.2 偵聽HTTP/3端口
      • 3.3 獲取WebTransport會話
      • 3.4 監聽WebTransport請求
    • 4.WebTransport在JavaScript中使用
      • 4.1 創建 WebTransport 實例
      • 4.2 通信測試
  • 二、WebTransport通信完整源碼
    • 1.服務端
    • 2.客戶端
    • 3.效果


前言

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 enableusing 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 expiresif (DateTime.Parse(cert.GetExpirationDateString(), null) >= DateTimeOffset.UtcNow){store.Close();return cert;}}// generate a new certvar 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 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();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"><h2 style="text-align: center;">Communication Log</h2><table style="overflow: auto; max-height: 1000px;"><thead><tr><td>Timestamp</td><td>From</td><td>To</td><td>Data</td></tr></thead><tbody id="commsLog"></tbody></table></div><script>let connectionUrl = document.getElementById("connectionUrl");let messageInput = document.getElementById("messageInput");let stateLabel = document.getElementById("stateLabel");let commsLog = document.getElementById("commsLog");let streamsList = document.getElementById("streamsList");let communicationPanel = document.getElementById("communicationPanel");let noStreamsText = document.getElementById("noStreams");let session;let connected = false;let openStreams = [];async function connect() {if (connected) {alert("Already connected!");return;}communicationPanel.hidden = true;stateLabel.innerHTML = "Ready to connect...";session = new WebTransport(connectionUrl.value, {serverCertificateHashes: [{algorithm: "sha-256",value: Uint8Array.from(atob(CERTIFICATE), c => c.charCodeAt(0))}]});stateLabel.innerHTML = "Connecting...";await session.ready;startListeningForIncomingStreams();setConnection(true);}async function closeConnection() {if (!connected) {alert("Not connected!");return;}for (let i = 0; i < openStreams.length; i++) {await closeStream(i);}await session.close();setConnection(false);openStreams = [];updateOpenStreamsList();}function setConnection(state) {connected = state;communicationPanel.hidden = !state;let msg = state ? "Connected!" : "Disconnected!";stateLabel.innerHTML = msg;addToCommsLog("Client", "Server", msg);}function updateOpenStreamsList() {streamsList.innerHTML = "";for (let i = 0; i < openStreams.length; i++) {streamsList.innerHTML += '<div>' +`<input type="radio" name = "streams" value = "${i}" id = "${i}" >` +`<label for="${i}">${i} - ${openStreams[i].type}</label>` +'</div >';}noStreamsText.hidden = openStreams.length > 0;}async function closeActiveStream() {let streamIndex = getActiveStreamIndex();await closeStream(streamIndex);}async function closeStream(index) {let stream = openStreams[index].stream;if (!stream) {return;}// close the writable part of a stream if it existsif (stream.writable) {await stream.writable.close();}// close the readable part of a stream if it existsif (stream.cancelReaderAndClose) {await stream.cancelReaderAndClose();}// close the stream if it can be closed manuallyif (stream.close) {await stream.close();}// remove from the list of open streamsopenStreams.splice(index, 1);updateOpenStreamsList();}async function startListeningForIncomingStreams() {try {let streamReader = session.incomingUnidirectionalStreams.getReader();let stream = await streamReader.read();if (stream.value && confirm("New incoming stream. Would you like to accept it?")) {startListeningForIncomingData(stream.value, openStreams.length, "input unidirectional");// we don't add to the stream list here as the only stream type that we can receive is// input. As we can't send data over these streams, there is no point in showing them to the user}} catch {alert("Failed to accept incoming stream");}}async function startListeningForIncomingData(stream, streamId, type) {let reader = isBidirectional(type) ? stream.readable.getReader() : stream.getReader();// define a function that we can use to abort the reading on this streamvar closed = false;stream.cancelReaderAndClose = () => {console.log(reader);reader.cancel();reader.releaseLock();closed = true;}// read loop for the streamtry {while (true) {let data = await reader.read();let msgOut = "";data.value.forEach(x => msgOut += String.fromCharCode(x));addToCommsLog("Server", "Client", `RECEIVED FROM STREAM ${streamId}: ${msgOut}`);}} catch {alert(`Stream ${streamId} ${closed ? "was closed" : "failed to read"}. Ending reading from it.`);}}async function createStream(type) {if (!connected) {return;}let stream;switch (type) {case 'output unidirectional':stream = await session.createUnidirectionalStream();break;case 'bidirectional':stream = await session.createBidirectionalStream();startListeningForIncomingData(stream, openStreams.length, "bidirectional");break;default:alert("Unknown stream type");return;}addStream(stream, type);}function addStream(stream, type) {openStreams.push({ stream: stream, type: type });updateOpenStreamsList();addToCommsLog("Client", "Server", `CREATING ${type} STREAM WITH ID ${openStreams.length}`);}async function sendMessage() {if (!connected) {return;}let activeStreamIndex = getActiveStreamIndex();if (activeStreamIndex == -1) {alert((openStreams.length > 0) ? "Please select a stream first" : "Please create a stream first");}let activeStreamObj = openStreams[activeStreamIndex];let activeStream = activeStreamObj.stream;let activeStreamType = activeStreamObj.type;let writer = isBidirectional(activeStreamType) ? activeStream.writable.getWriter() : activeStream.getWriter();let msg = messageInput.value.split("").map(x => (x).charCodeAt(0));await writer.write(new Uint8Array(msg));writer.releaseLock();addToCommsLog("Client", "Server", `SENDING OVER STREAM ${activeStreamIndex}: ${messageInput.value}`);}function isBidirectional(type) {return type === "bidirectional";}function getActiveStream() {let index = getActiveStreamIndex();return (index === -1) ? null : openStreams[index].stream;}function getActiveStreamIndex() {let allStreams = document.getElementsByName("streams");for (let i = 0; i < allStreams.length; i++) {if (allStreams[i].checked) {return i;}}return -1;}function addToCommsLog(from, to, data) {commsLog.innerHTML += '<tr>' +`<td>${getTimestamp()}</td>` +`<td>${from}</td>` +`<td>${to}</td>` +`<td>${data}</td>`'</tr>';}function getTimestamp() {let now = new Date();return `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}.${now.getMilliseconds()}`;}</script> </body> </html> <style>html,body {background-color: #459eda;padding: 0;margin: 0;height: 100%;}body {display: flex;flex-direction: row;}#panel {background-color: white;padding: 12px;margin: 0;border-top-right-radius: 12px;border-bottom-right-radius: 12px;border-right: #0d6cad 5px solid;max-width: 400px;min-width: 200px;flex: 1;min-height: 1200px;overflow: auto;}#panel > * {text-align: center;}#communicationLogPanel {padding: 24px;flex: 1;}#communicationLogPanel > * {color: white;width: 100%;}#connectButton {background-color: #2e64a7;}#messageInput {max-width: 100%;}#streamsList {max-height: 400px;overflow: auto;}input {padding: 6px;margin-bottom: 8px;margin-right: 0;}button {background-color: #459eda;border-radius: 5px;border: 0;text-align: center;color: white;padding: 8px;margin: 4px;width: 100%;} </style>

3.效果

總結

以上是生活随笔為你收集整理的【愚公系列】2022年11月 .NET CORE工具案例-.NET 7中的WebTransport通信的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。