debian环境安装 quic支持
# 1. 添加unstable仓库(如果您使用的是Debian的不稳定分支)
sudo apt install apt-transport-https ca-certificates
sudo wget -O /etc/apt/trusted.gpg.d/microsoft.gpg https://packages.microsoft.com/keys/microsoft.asc
echo "deb [arch=amd64] https://packages.microsoft.com/debian/$(lsb_release -cs) prod-unstable main" | \sudo tee /etc/apt/sources.list.d/msprod.list# 2. 更新包列表
sudo apt update# 3. 安装libmsquic
sudo apt install libmsquic
window环境要求
要求Windows 11、Windows Server 2022 或更新版本
代码
using System.Net.Quic;
using System.Net.Security;
using System.Net;
using System.Runtime.Versioning;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography;
using System.Buffers;
using System.Reflection.PortableExecutable;
using System.IO.Pipelines;
using System.Text;namespace ConsoleApp1
{internal class Program{[SupportedOSPlatform("linux")][SupportedOSPlatform("windows")][RequiresPreviewFeatures]static async Task Main(string[] args){// 创建 QuicListenervar 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()//生成证书}})});_ = Task.Run(async () =>{Console.WriteLine("Quic Client Running...");await Task.Delay(1000);// 连接到服务端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);stream.ReadTimeout = 16000;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();});var connection = await listener.AcceptConnectionAsync();Console.WriteLine($"Client [{connection.RemoteEndPoint}]: connected");var stream = await connection.AcceptInboundStreamAsync();Console.WriteLine($"Stream [{stream.Id}]: created");await ProcessLinesAsync(stream);}// 处理流数据[SupportedOSPlatform("linux")][SupportedOSPlatform("windows")][RequiresPreviewFeatures]static 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();}static 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;}static void ProcessLine(in ReadOnlySequence<byte> buffer){foreach (var segment in buffer){Console.WriteLine("Recevied -> " + System.Text.Encoding.UTF8.GetString(segment.Span));}Console.WriteLine();}static 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("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();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;}}
}