QUIC protokolü
QUIC, RFC 9000'de standartlaştırılmış bir ağ aktarım katmanı protokolüdür. UDP'yi temel alınan bir protokol olarak kullanır ve TLS 1.3 kullanımını zorunlu kılıp doğal olarak güvenlidir. Daha fazla bilgi için bkz . RFC 9001. TCP ve UDP gibi iyi bilinen aktarım protokollerinden bir diğer ilginç fark, aktarım katmanında yerleşik akış çoğullama özelliğine sahip olmasıdır. Bu, birbirini etkilemeyen birden çok, eşzamanlı, bağımsız veri akışına sahip olmayı sağlar.
QUIC, aktarım protokolü olduğundan, değiştirilen veriler için herhangi bir semantik tanımlamaz. Uygulama katmanı protokollerinde, örneğin HTTP/3'te veya QUIC üzerinden SMB'de kullanılır. Ayrıca herhangi bir özel tanımlı protokol için de kullanılabilir.
Protokol, TLS ile TCP'ye göre birçok avantaj sunar, aşağıda birkaçı yer alır:
- Daha hızlı bağlantı kurma, üzerinde TLS bulunan TCP kadar gidiş dönüş gerektirmez.
- Bir kayıp paketin diğer tüm akışların verilerini engellemediği satır başı engelleme sorunundan kaçınma.
Öte yandan, QUIC kullanırken dikkate alınması gereken olası dezavantajlar vardır. Daha yeni bir protokol olarak benimsemesi hala büyüyor ve sınırlı. Bunun dışında, QUIC trafiği bazı ağ bileşenleri tarafından bile engellenebilir.
.NET'te QUIC
QUIC uygulaması kitaplık olarak .NET 5'te System.Net.Quic
tanıtıldı. Ancak, .NET 7'ye kadar kitaplık tamamen dahiliydi ve yalnızca HTTP/3 uygulaması olarak hizmet verdi. .NET 7 ile kitaplık genel kullanıma açık hale getirildiği için API'leri kullanıma sunulur.
Not
.NET 7.0 ve 8.0'da API'ler önizleme özellikleri olarak yayımlandı. .NET 9'dan başlayarak, bu API'ler artık önizleme özellikleri olarak kabul edilmez ve artık kararlı olarak kabul edilir.
Uygulama açısından bakıldığında, System.Net.Quic
QUIC protokolünün yerel uygulaması olan MsQuic'e bağlıdır. Sonuç olarak, System.Net.Quic
platform desteği ve bağımlılıkları MsQuic'ten devralınır ve Platform bağımlılıkları bölümünde belgelenir. Kısacası, MsQuic kitaplığı Windows için .NET'in bir parçası olarak gönderilir. Ancak Linux için uygun bir paket yöneticisi aracılığıyla el ile yüklemeniz libmsquic
gerekir. Diğer platformlar için, ister SChannel ister OpenSSL'ye karşı el ile MsQuic derlemek ve ile System.Net.Quic
kullanmak mümkündür. Ancak bu senaryolar test matrisimizin bir parçası değildir ve öngörülemeyen sorunlar oluşabilir.
Platform bağımlılıkları
Aşağıdaki bölümlerde .NET'te QUIC için platform bağımlılıkları açıklanmaktadır.
Windows
- Windows 11, Windows Server 2022 veya üzeri. (Önceki Windows sürümlerinde QUIC'yi desteklemek için gereken şifreleme API'leri eksik.)
Windows'da, msquic.dll .NET çalışma zamanının bir parçası olarak dağıtılır ve yüklemek için başka bir adım gerekmez.
Linux
Not
.NET 7+, libmsquic'in yalnızca 2.2+ sürümüyle uyumludur.
libmsquic
Paket Linux üzerinde gereklidir. Bu paket Microsoft'un resmi Linux paket deposunda https://packages.microsoft.com yayımlanır ve Alpine Packages - libmsquic gibi bazı resmi depolarda da kullanılabilir.
Microsoft'un resmi Linux paketi deposundan yükleme libmsquic
Paketi yüklemeden önce bu depoyu paket yöneticinize eklemeniz gerekir. Daha fazla bilgi için bkz . Microsoft Ürünleri için Linux Yazılım Deposu.
Dikkat
Microsoft paket deposunu eklemek, dağıtımınızın deposu .NET ve diğer Microsoft paketlerini sağladığında dağıtımınızın deposuyla çakışabilir. Paket karmalarını önlemek veya sorunlarını gidermek için Linux'ta eksik dosyalarla ilgili .NET hatalarını giderme makalesini gözden geçirin.
Örnekler
Yüklemek için libmsquic
paket yöneticisi kullanmanın bazı örnekleri aşağıda verilmiştir:
APT
sudo apt-get install libmsquic
APK
sudo apk add libmsquic
DNF
sudo dnf install libmsquic
zypper
sudo zypper install libmsquic
YUM
sudo yum install libmsquic
Dağıtım Paketi Deposundan Yükleme libmsquic
Dağıtım paketi deposundan yükleme libmsquic
de mümkündür, ancak şu anda bu yalnızca için Alpine
kullanılabilir.
Örnekler
Yüklemek için libmsquic
paket yöneticisi kullanmanın bazı örnekleri aşağıda verilmiştir:
- Alpine 3.21 ve üzeri
apk add libmsquic
- Alpine 3.20 ve üzeri
# Get libmsquic from community repository edge branch.
apk add --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community/ libmsquic
Libmsquic bağımlılıkları
Aşağıdaki tüm bağımlılıklar paket bildiriminde libmsquic
belirtilir ve paket yöneticisi tarafından otomatik olarak yüklenir:
OpenSSL 3+ veya 1.1 - dağıtım sürümü için varsayılan OpenSSL sürümüne bağlıdır; örneğin, Ubuntu 22 için OpenSSL 3 ve Ubuntu 20 için OpenSSL 1.1.
libnuma1
macOS
QUIC artık bazı sınırlamalarla standart olmayan bir Homebrew paket yöneticisi aracılığıyla macOS'ta kısmen desteklenmektedir. Aşağıdaki komutla Homebrew kullanarak macOS'a yükleyebilirsiniz libmsquic
:
brew install libmsquic
kullanan libmsquic
bir .NET uygulamasını çalıştırmak için ortam değişkenini çalıştırmadan önce ayarlamanız gerekir. Bu, uygulamanın çalışma zamanı dinamik yüklemesi sırasında kitaplığı bulabilmesini libmsquic
sağlar. Bunu, ana komutunuzdan önce aşağıdaki komutu ekleyerek yapabilirsiniz:
DYLD_FALLBACK_LIBRARY_PATH=$DYLD_FALLBACK_LIBRARY_PATH:$(brew --prefix)/lib dotnet run
veya
DYLD_FALLBACK_LIBRARY_PATH=$DYLD_FALLBACK_LIBRARY_PATH:$(brew --prefix)/lib ./binaryname
Alternatif olarak, ortam değişkenini şu şekilde ayarlayabilirsiniz:
export DYLD_FALLBACK_LIBRARY_PATH=$DYLD_FALLBACK_LIBRARY_PATH:$(brew --prefix)/lib
ve ardından ana komutunuzu çalıştırın:
./binaryname
API’ye genel bakış
System.Net.Quic QUIC protokolünün kullanımını etkinleştiren üç ana sınıf getirir:
- QuicListener - gelen bağlantıları kabul etmek için sunucu tarafı sınıfı.
- QuicConnection- RFC 9000 Bölüm 5'e karşılık gelen QUIC bağlantısı.
- QuicStream- RFC 9000 Bölüm 2'ye karşılık gelen QUIC akışı.
Ancak bu sınıfları kullanmadan önce kodunuz QUIC'nin şu anda desteklenip desteklenmediğini libmsquic
denetlemelidir. Eksik olabilir veya TLS 1.3 desteklenmeyebilir. Bunun için hem hem de QuicListener
QuicConnection
statik bir özelliği IsSupported
kullanıma sunar:
if (QuicListener.IsSupported)
{
// Use QuicListener
}
else
{
// Fallback/Error
}
if (QuicConnection.IsSupported)
{
// Use QuicConnection
}
else
{
// Fallback/Error
}
Bu özellikler aynı değeri rapor eder, ancak gelecekte değişebilir. Sunucu senaryolarını ve IsSupported istemci senaryolarını denetlemenizi IsSupported öneririz.
QuicListener
QuicListener istemcilerinden gelen bağlantıları kabul eden bir sunucu tarafı sınıfını temsil eder. Dinleyici, statik bir yöntemle ListenAsync(QuicListenerOptions, CancellationToken)oluşturulur ve başlatılır. yöntemi, dinleyiciyi QuicListenerOptions başlatmak ve gelen bağlantıları kabul etmek için gereken tüm ayarları içeren bir sınıf örneğini kabul eder. Bundan sonra, dinleyici aracılığıyla AcceptConnectionAsync(CancellationToken)bağlantıları dışarı aktarmaya hazırdır. Bu yöntem tarafından döndürülen bağlantılar her zaman tamamen bağlanır, yani TLS el sıkışması tamamlanır ve bağlantı kullanılmaya hazırdır. Son olarak, dinlemeyi durdurmak ve tüm kaynakları DisposeAsync() serbest bırakmak için çağrılmalıdır.
Aşağıdaki QuicListener
örnek kodu göz önünde bulundurun:
using System.Net.Quic;
// First, check if QUIC is supported.
if (!QuicListener.IsSupported)
{
Console.WriteLine("QUIC is not supported, check for presence of libmsquic and support of TLS 1.3.");
return;
}
// Share configuration for each incoming connection.
// This represents the minimal configuration necessary.
var serverConnectionOptions = new QuicServerConnectionOptions
{
// Used to abort stream if it's not properly closed by the user.
// See https://www.rfc-editor.org/rfc/rfc9000#section-20.2
DefaultStreamErrorCode = 0x0A, // Protocol-dependent error code.
// Used to close the connection if it's not done by the user.
// See https://www.rfc-editor.org/rfc/rfc9000#section-20.2
DefaultCloseErrorCode = 0x0B, // Protocol-dependent error code.
// Same options as for server side SslStream.
ServerAuthenticationOptions = new SslServerAuthenticationOptions
{
// Specify the application protocols that the server supports. This list must be a subset of the protocols specified in QuicListenerOptions.ApplicationProtocols.
ApplicationProtocols = [new SslApplicationProtocol("protocol-name")],
// Server certificate, it can also be provided via ServerCertificateContext or ServerCertificateSelectionCallback.
ServerCertificate = serverCertificate
}
};
// Initialize, configure the listener and start listening.
var listener = await QuicListener.ListenAsync(new QuicListenerOptions
{
// Define the endpoint on which the server will listen for incoming connections. The port number 0 can be replaced with any valid port number as needed.
ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0),
// List of all supported application protocols by this listener.
ApplicationProtocols = [new SslApplicationProtocol("protocol-name")],
// Callback to provide options for the incoming connections, it gets called once per each connection.
ConnectionOptionsCallback = (_, _, _) => ValueTask.FromResult(serverConnectionOptions)
});
// Accept and process the connections.
while (isRunning)
{
// Accept will propagate any exceptions that occurred during the connection establishment,
// including exceptions thrown from ConnectionOptionsCallback, caused by invalid QuicServerConnectionOptions or TLS handshake failures.
var connection = await listener.AcceptConnectionAsync();
// Process the connection...
}
// When finished, dispose the listener.
await listener.DisposeAsync();
öğesinin QuicListener
nasıl tasarlandığı hakkında daha fazla bilgi için bkz . API teklifi.
QuicConnection
QuicConnection hem sunucu hem de istemci tarafı QUIC bağlantıları için kullanılan bir sınıftır. Sunucu tarafı bağlantıları, dinleyici tarafından dahili olarak oluşturulur ve aracılığıyla AcceptConnectionAsync(CancellationToken)dağıtılır. İstemci tarafı bağlantıları açılmalıdır ve sunucuya bağlanmalıdır. Dinleyicide olduğu gibi bağlantının örneğini oluşturan ve bağlayan statik bir yöntem ConnectAsync(QuicClientConnectionOptions, CancellationToken) vardır. için benzer bir sınıfın QuicClientConnectionOptionsörneğini QuicServerConnectionOptionskabul eder. Bundan sonra, bağlantıyla yapılan çalışma istemci ile sunucu arasında farklılık göstermez. Giden akışları açabilir ve gelen akışları kabul edebilir. Ayrıca, bağlantı hakkında , RemoteEndPointveya RemoteCertificategibi LocalEndPointbilgiler de sağlar.
Bağlantıyla çalışma tamamlandığında kapatılması ve atılması gerekir. QUIC protokolü, hemen kapatma için bir uygulama katmanı kodu kullanmayı zorunlu hale gelmiştir. Bkz . RFC 9000 Bölüm 10.2. Bunun için, CloseAsync(Int64, CancellationToken) uygulama katmanı kodu ile çağrılabilir veya çağrılmadıysa, DisposeAsync() içinde DefaultCloseErrorCodesağlanan kodu kullanır. Her iki durumda da, DisposeAsync() tüm ilişkili kaynakları tamamen serbest bırakmak için bağlantıyla çalışmanın sonunda çağrılmalıdır.
Aşağıdaki QuicConnection
örnek kodu göz önünde bulundurun:
using System.Net.Quic;
// First, check if QUIC is supported.
if (!QuicConnection.IsSupported)
{
Console.WriteLine("QUIC is not supported, check for presence of libmsquic and support of TLS 1.3.");
return;
}
// This represents the minimal configuration necessary to open a connection.
var clientConnectionOptions = new QuicClientConnectionOptions
{
// End point of the server to connect to.
RemoteEndPoint = listener.LocalEndPoint,
// Used to abort stream if it's not properly closed by the user.
// See https://www.rfc-editor.org/rfc/rfc9000#section-20.2
DefaultStreamErrorCode = 0x0A, // Protocol-dependent error code.
// Used to close the connection if it's not done by the user.
// See https://www.rfc-editor.org/rfc/rfc9000#section-20.2
DefaultCloseErrorCode = 0x0B, // Protocol-dependent error code.
// Optionally set limits for inbound streams.
MaxInboundUnidirectionalStreams = 10,
MaxInboundBidirectionalStreams = 100,
// Same options as for client side SslStream.
ClientAuthenticationOptions = new SslClientAuthenticationOptions
{
// List of supported application protocols.
ApplicationProtocols = [new SslApplicationProtocol("protocol-name")],
// The name of the server the client is trying to connect to. Used for server certificate validation.
TargetHost = ""
}
};
// Initialize, configure and connect to the server.
var connection = await QuicConnection.ConnectAsync(clientConnectionOptions);
Console.WriteLine($"Connected {connection.LocalEndPoint} --> {connection.RemoteEndPoint}");
// Open a bidirectional (can both read and write) outbound stream.
// Opening a stream reserves it but does not notify the peer or send any data. If you don't send data, the peer
// won't be informed about the stream, which can cause AcceptInboundStreamAsync() to hang. To avoid this, ensure
// you send data on the stream to properly initiate communication.
var outgoingStream = await connection.OpenOutboundStreamAsync(QuicStreamType.Bidirectional);
// Work with the outgoing stream ...
// To accept any stream on a client connection, at least one of MaxInboundBidirectionalStreams or MaxInboundUnidirectionalStreams of QuicConnectionOptions must be set.
while (isRunning)
{
// Accept an inbound stream.
var incomingStream = await connection.AcceptInboundStreamAsync();
// Work with the incoming stream ...
}
// Close the connection with the custom code.
await connection.CloseAsync(0x0C);
// Dispose the connection.
await connection.DisposeAsync();
öğesinin QuicConnection
nasıl tasarlandığı hakkında daha fazla bilgi için bkz . API teklifi.
QuicStream
QuicStream , QUIC protokolünde veri göndermek ve almak için kullanılan gerçek türdür. Sıradan Stream türetilir ve bu şekilde kullanılabilir, ancak QUIC protokolüne özgü çeşitli özellikler de sunar. İlk olarak, BIR QUIC akışı tek yönlü veya çift yönlü olabilir, bkz . RFC 9000 Bölüm 2.1. Çift yönlü akış her iki tarafa da veri gönderip alabilirken, tek yönlü akış yalnızca başlatan taraftan yazıp kabul edeni okuyabilir. Her eş, her türün kabul etmek, görmek MaxInboundBidirectionalStreams ve MaxInboundUnidirectionalStreamsiçin istekli olduğu eşzamanlı akış sayısını sınırlayabilir.
QUIC akışının bir diğer özelliği de akışın ortasında yazma tarafını açıkça kapatabilmek, bağımsız değişkenle completeWrites
görmek CompleteWrites() veya WriteAsync(ReadOnlyMemory<Byte>, Boolean, CancellationToken) aşırı yüklemektir. Yazma tarafının kapatılması, eşe başka veri gelemeyeceklerini bildirir, ancak eş yine de göndermeye devam edebilir (çift yönlü akış söz konusu olduğunda). Bu, istemci isteği gönderdiğinde ve sunucuya istek içeriğinin sonu olduğunu bildirmek için yazma tarafını kapattığında HTTP isteği/yanıt değişimi gibi senaryolarda kullanışlıdır. Sunucu bundan sonra yanıt göndermeye devam eder, ancak istemciden başka veri gelemeyeceğini bilir. Hatalı durumlar için, akışın yazma veya okuma tarafı durdurulabilir, bkz Abort(QuicAbortDirection, Int64). .
Not
Akışın açılması yalnızca veri göndermeden bunu ayırır. Bu yaklaşım, neredeyse boş çerçevelerin iletimini önleyerek ağ kullanımını iyileştirmek için tasarlanmıştır. Gerçek veriler gönderilene kadar eşe bildirilmediğinden, akış eş açısından etkin değildir. Veri göndermezseniz, eş akışı tanımaz ve bu da anlamlı bir akış beklerken yanıt vermemeye neden AcceptInboundStreamAsync()
olabilir. Düzgün iletişim sağlamak için akışı açtıktan sonra veri göndermeniz gerekir.
Her akış türü için tek tek yöntemlerin davranışı aşağıdaki tabloda özetlenmiştir (hem istemcinin hem de sunucunun akışları açabildiğine ve kabul edebildiğine dikkat edin):
Metot | Eş açma akışı | Akışı kabul eden eş |
---|---|---|
CanRead |
çift yönlü: true tek yönlü: false |
true |
CanWrite |
true |
çift yönlü: true tek yönlü: false |
ReadAsync |
çift yönlü: verileri okur tek yönlü: InvalidOperationException |
verileri okur |
WriteAsync |
veri gönderir => eş okuma, verileri döndürür | çift yönlü: veri gönderir => eş okuma verileri döndürür tek yönlü: InvalidOperationException |
CompleteWrites |
yazma tarafını kapatır => eş okuma 0 döndürür | çift yönlü: yazma tarafını kapatır => eş okuma 0 döndürür tek yönlü: işlem yok |
Abort(QuicAbortDirection.Read) |
çift yönlü: STOP_SENDING => eş yazma akışları QuicException(QuicError.OperationAborted) tek yönlü: işlem yok |
STOP_SENDING => eş yazma akışlarıQuicException(QuicError.OperationAborted) |
Abort(QuicAbortDirection.Write) |
RESET_STREAM => eş okuma akışlarıQuicException(QuicError.OperationAborted) |
çift yönlü: RESET_STREAM => eş okuma oluşturmaları QuicException(QuicError.OperationAborted) tek yönlü: işlem yok |
Bu yöntemlerin yanı sıra, QuicStream
akışın okuma veya yazma tarafı kapatıldıktan sonra bildirim almak için iki özel özellik sunar: ReadsClosed ve WritesClosed. Her ikisi de başarılı veya iptal olsun, ilgili tarafı kapatılırken tamamlanan bir Task
döndürür; bu durumda Task
uygun özel durum içerecektir. Bu özellikler, kullanıcı kodunun veya WriteAsync
çağrısı ReadAsync
göndermeden akış tarafının kapatılmasını bilmesi gerektiğinde kullanışlıdır.
Son olarak, akışla çalışma tamamlandığında ile DisposeAsync()atılması gerekir. dispose, akış türüne bağlı olarak hem okuma hem de/veya yazma tarafının kapatıldığından emin olur. Akış sonuna kadar düzgün bir şekilde okunmamışsa dispose, eşdeğerini Abort(QuicAbortDirection.Read)
yayınlar. Ancak, akış yazma tarafı kapatılmamışsa, ile CompleteWrites
olduğu gibi düzgün bir şekilde kapatılır. Bu farkın nedeni, sıradan Stream
bir senaryoyla çalışan senaryoların beklendiği gibi davrandığından ve başarılı bir yola yol açtığından emin olmaktır. Aşağıdaki örneği inceleyin:
// Work done with all different types of streams.
async Task WorkWithStreamAsync(Stream stream)
{
// This will dispose the stream at the end of the scope.
await using (stream)
{
// Simple echo, read data and send them back.
byte[] buffer = new byte[1024];
int count = 0;
// The loop stops when read returns 0 bytes as is common for all streams.
while ((count = await stream.ReadAsync(buffer)) > 0)
{
await stream.WriteAsync(buffer.AsMemory(0, count));
}
}
}
// Open a QuicStream and pass to the common method.
var quicStream = await connection.OpenOutboundStreamAsync(QuicStreamType.Bidirectional);
await WorkWithStreamAsync(quicStream);
İstemci senaryosunda örnek kullanımı QuicStream
:
// Consider connection from the connection example, open a bidirectional stream.
await using var stream = await connection.OpenOutboundStreamAsync(QuicStreamType.Bidirectional, cancellationToken);
// Send some data.
await stream.WriteAsync(data, cancellationToken);
await stream.WriteAsync(data, cancellationToken);
// End the writing-side together with the last data.
await stream.WriteAsync(data, completeWrites: true, cancellationToken);
// Or separately.
stream.CompleteWrites();
// Read data until the end of stream.
while (await stream.ReadAsync(buffer, cancellationToken) > 0)
{
// Handle buffer data...
}
// DisposeAsync called by await using at the top.
Sunucu senaryosunda örnek kullanımı QuicStream
:
// Consider connection from the connection example, accept a stream.
await using var stream = await connection.AcceptInboundStreamAsync(cancellationToken);
if (stream.Type != QuicStreamType.Bidirectional)
{
Console.WriteLine($"Expected bidirectional stream, got {stream.Type}");
return;
}
// Read the data.
while (stream.ReadAsync(buffer, cancellationToken) > 0)
{
// Handle buffer data...
// Client completed the writes, the loop might be exited now without another ReadAsync.
if (stream.ReadsCompleted.IsCompleted)
{
break;
}
}
// Listen for Abort(QuicAbortDirection.Read) from the client.
var writesClosedTask = WritesClosedAsync(stream);
async ValueTask WritesClosedAsync(QuicStream stream)
{
try
{
await stream.WritesClosed;
}
catch (Exception ex)
{
// Handle peer aborting our writing side ...
}
}
// DisposeAsync called by await using at the top.
öğesinin QuicStream
nasıl tasarlandığı hakkında daha fazla bilgi için bkz . API teklifi.