From 9c5e47d2612176b1d8545dbf2eab9e928eea48fc Mon Sep 17 00:00:00 2001 From: mockingjay <8400684@qq.com> Date: Sat, 4 May 2024 21:27:25 +0800 Subject: [PATCH 1/8] Add support for quic --- SuperSocket.sln | 30 ++++ .../QuicPipeConnection.cs | 55 ++++++ .../QuicPipeStream.cs | 66 ++++++++ .../SuperSocket.Quic.Connection.csproj | 12 ++ src/SuperSocket.Quic/QuicConnectionFactory.cs | 29 ++++ .../QuicConnectionFactoryBuilder.cs | 13 ++ .../QuicConnectionListener.cs | 157 ++++++++++++++++++ .../QuicConnectionListenerFactory.cs | 72 ++++++++ .../QuicServerHostBuilderExtensions.cs | 44 +++++ src/SuperSocket.Quic/QuicTransportOptions.cs | 27 +++ src/SuperSocket.Quic/SuperSocket.Quic.csproj | 15 ++ test/SuperSocket.Tests/ClientTest.cs | 3 + .../SuperSocket.Tests/QuicHostConfigurator.cs | 148 +++++++++++++++++ .../SuperSocket.Tests.csproj | 1 + 14 files changed, 672 insertions(+) create mode 100644 src/SuperSocket.Quic.Connection/QuicPipeConnection.cs create mode 100644 src/SuperSocket.Quic.Connection/QuicPipeStream.cs create mode 100644 src/SuperSocket.Quic.Connection/SuperSocket.Quic.Connection.csproj create mode 100644 src/SuperSocket.Quic/QuicConnectionFactory.cs create mode 100644 src/SuperSocket.Quic/QuicConnectionFactoryBuilder.cs create mode 100644 src/SuperSocket.Quic/QuicConnectionListener.cs create mode 100644 src/SuperSocket.Quic/QuicConnectionListenerFactory.cs create mode 100644 src/SuperSocket.Quic/QuicServerHostBuilderExtensions.cs create mode 100644 src/SuperSocket.Quic/QuicTransportOptions.cs create mode 100644 src/SuperSocket.Quic/SuperSocket.Quic.csproj create mode 100644 test/SuperSocket.Tests/QuicHostConfigurator.cs diff --git a/SuperSocket.sln b/SuperSocket.sln index 834eed086..5ba66e3f4 100644 --- a/SuperSocket.sln +++ b/SuperSocket.sln @@ -39,6 +39,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SuperSocket.Connection", "s EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SuperSocket.Kestrel", "src\SuperSocket.Kestrel\SuperSocket.Kestrel.csproj", "{8C8507D6-903F-4786-8F18-ACA54257454B}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SuperSocket.Quic", "src\SuperSocket.Quic\SuperSocket.Quic.csproj", "{AC215A13-27AE-4417-A66A-6894BB732D2B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SuperSocket.Quic.Connection", "src\SuperSocket.Quic.Connection\SuperSocket.Quic.Connection.csproj", "{8EE55816-8775-4A04-A3C3-05CCBD21F66D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -241,6 +245,30 @@ Global {8C8507D6-903F-4786-8F18-ACA54257454B}.Release|x64.Build.0 = Release|Any CPU {8C8507D6-903F-4786-8F18-ACA54257454B}.Release|x86.ActiveCfg = Release|Any CPU {8C8507D6-903F-4786-8F18-ACA54257454B}.Release|x86.Build.0 = Release|Any CPU + {AC215A13-27AE-4417-A66A-6894BB732D2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AC215A13-27AE-4417-A66A-6894BB732D2B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AC215A13-27AE-4417-A66A-6894BB732D2B}.Debug|x64.ActiveCfg = Debug|Any CPU + {AC215A13-27AE-4417-A66A-6894BB732D2B}.Debug|x64.Build.0 = Debug|Any CPU + {AC215A13-27AE-4417-A66A-6894BB732D2B}.Debug|x86.ActiveCfg = Debug|Any CPU + {AC215A13-27AE-4417-A66A-6894BB732D2B}.Debug|x86.Build.0 = Debug|Any CPU + {AC215A13-27AE-4417-A66A-6894BB732D2B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AC215A13-27AE-4417-A66A-6894BB732D2B}.Release|Any CPU.Build.0 = Release|Any CPU + {AC215A13-27AE-4417-A66A-6894BB732D2B}.Release|x64.ActiveCfg = Release|Any CPU + {AC215A13-27AE-4417-A66A-6894BB732D2B}.Release|x64.Build.0 = Release|Any CPU + {AC215A13-27AE-4417-A66A-6894BB732D2B}.Release|x86.ActiveCfg = Release|Any CPU + {AC215A13-27AE-4417-A66A-6894BB732D2B}.Release|x86.Build.0 = Release|Any CPU + {8EE55816-8775-4A04-A3C3-05CCBD21F66D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8EE55816-8775-4A04-A3C3-05CCBD21F66D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8EE55816-8775-4A04-A3C3-05CCBD21F66D}.Debug|x64.ActiveCfg = Debug|Any CPU + {8EE55816-8775-4A04-A3C3-05CCBD21F66D}.Debug|x64.Build.0 = Debug|Any CPU + {8EE55816-8775-4A04-A3C3-05CCBD21F66D}.Debug|x86.ActiveCfg = Debug|Any CPU + {8EE55816-8775-4A04-A3C3-05CCBD21F66D}.Debug|x86.Build.0 = Debug|Any CPU + {8EE55816-8775-4A04-A3C3-05CCBD21F66D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8EE55816-8775-4A04-A3C3-05CCBD21F66D}.Release|Any CPU.Build.0 = Release|Any CPU + {8EE55816-8775-4A04-A3C3-05CCBD21F66D}.Release|x64.ActiveCfg = Release|Any CPU + {8EE55816-8775-4A04-A3C3-05CCBD21F66D}.Release|x64.Build.0 = Release|Any CPU + {8EE55816-8775-4A04-A3C3-05CCBD21F66D}.Release|x86.ActiveCfg = Release|Any CPU + {8EE55816-8775-4A04-A3C3-05CCBD21F66D}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -262,6 +290,8 @@ Global {8454E8D5-777D-46CB-B050-76C5119B624B} = {DDA4741E-097F-40C3-A252-2E1E3476C1A7} {FC2B529F-4AF4-4C39-BC4F-A3836CC7B37C} = {DDA4741E-097F-40C3-A252-2E1E3476C1A7} {8C8507D6-903F-4786-8F18-ACA54257454B} = {DDA4741E-097F-40C3-A252-2E1E3476C1A7} + {AC215A13-27AE-4417-A66A-6894BB732D2B} = {DDA4741E-097F-40C3-A252-2E1E3476C1A7} + {8EE55816-8775-4A04-A3C3-05CCBD21F66D} = {DDA4741E-097F-40C3-A252-2E1E3476C1A7} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {ADB30AA2-A848-4CB3-8A20-488C80F1BA9E} diff --git a/src/SuperSocket.Quic.Connection/QuicPipeConnection.cs b/src/SuperSocket.Quic.Connection/QuicPipeConnection.cs new file mode 100644 index 000000000..88f0221f1 --- /dev/null +++ b/src/SuperSocket.Quic.Connection/QuicPipeConnection.cs @@ -0,0 +1,55 @@ +using System; +using System.IO; +using System.Net; +using System.Net.Quic; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; +using SuperSocket.Connection; + +#pragma warning disable CA2252 + +namespace SuperSocket.Quic.Connection; + +public class QuicPipeConnection : StreamPipeConnection +{ + private readonly Stream _stream; + + public QuicPipeConnection(Stream stream, EndPoint remoteEndPoint, ConnectionOptions options) + : this(stream, remoteEndPoint, null, options) + { + } + + public QuicPipeConnection(Stream stream, EndPoint remoteEndPoint, EndPoint localEndPoint, ConnectionOptions options) + : base(stream, remoteEndPoint, localEndPoint, options) + { + if (stream is not QuicStream or QuicPipeStream) + throw new NotSupportedException("QuicPipeConnection only supports QuicStream or QuicPipeStream"); + + _stream = stream; + } + + protected override async Task StartInputPipeTask(IObjectPipe packagePipe, + CancellationToken cancellationToken) + { + if (_stream is QuicPipeStream quicPipeStream) + await quicPipeStream.OpenStreamAsync(cancellationToken); + + await base.StartInputPipeTask(packagePipe, cancellationToken); + } + + protected override bool IsIgnorableException(Exception e) + { + if (base.IsIgnorableException(e)) + return true; + + switch (e) + { + case QuicException: + case SocketException se when se.IsIgnorableSocketException(): + return true; + default: + return false; + } + } +} diff --git a/src/SuperSocket.Quic.Connection/QuicPipeStream.cs b/src/SuperSocket.Quic.Connection/QuicPipeStream.cs new file mode 100644 index 000000000..b1bb6ecd5 --- /dev/null +++ b/src/SuperSocket.Quic.Connection/QuicPipeStream.cs @@ -0,0 +1,66 @@ +using System; +using System.IO; +using System.Net.Quic; +using System.Threading; +using System.Threading.Tasks; + +namespace SuperSocket.Quic.Connection; + +#pragma warning disable CA2252 + +public sealed class QuicPipeStream : Stream +{ + private Stream _stream; + + private readonly QuicConnection _connection; + + public QuicPipeStream(QuicConnection connection) + { + _connection = connection; + } + + public override bool CanRead => _stream.CanRead; + public override bool CanSeek => _stream.CanSeek; + public override bool CanWrite => _stream.CanWrite; + public override long Length => _stream.Length; + + public override long Position + { + get => _stream.Position; + set => _stream.Position = value; + } + + public async ValueTask OpenStreamAsync(CancellationToken cancellationToken) + { + _stream = await _connection.AcceptInboundStreamAsync(cancellationToken); + } + + public override void Flush() => _stream.Flush(); + + public override int Read(byte[] buffer, int offset, int count) => _stream.Read(buffer, offset, count); + + public override long Seek(long offset, SeekOrigin origin) => _stream.Seek(offset, origin); + + public override void SetLength(long value) => _stream.Flush(); + + public override void Close() => _stream.Close(); + + public override Task FlushAsync(CancellationToken cancellationToken) => _stream.FlushAsync(cancellationToken); + + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => + _stream.ReadAsync(buffer, offset, count, cancellationToken); + + public override ValueTask ReadAsync(Memory buffer, + CancellationToken cancellationToken = default) => _stream.ReadAsync(buffer, cancellationToken); + + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => + _stream.WriteAsync(buffer, offset, count, cancellationToken); + + public override ValueTask WriteAsync(ReadOnlyMemory buffer, + CancellationToken cancellationToken = default) => + _stream.WriteAsync(buffer, cancellationToken); + + public override void Write(ReadOnlySpan buffer) => _stream.Flush(); + + public override void Write(byte[] buffer, int offset, int count) => _stream.Flush(); +} \ No newline at end of file diff --git a/src/SuperSocket.Quic.Connection/SuperSocket.Quic.Connection.csproj b/src/SuperSocket.Quic.Connection/SuperSocket.Quic.Connection.csproj new file mode 100644 index 000000000..e1e646928 --- /dev/null +++ b/src/SuperSocket.Quic.Connection/SuperSocket.Quic.Connection.csproj @@ -0,0 +1,12 @@ + + + + SuperSocket quic connection library. + net7.0;net8.0 + + + + + + + diff --git a/src/SuperSocket.Quic/QuicConnectionFactory.cs b/src/SuperSocket.Quic/QuicConnectionFactory.cs new file mode 100644 index 000000000..a59e5950c --- /dev/null +++ b/src/SuperSocket.Quic/QuicConnectionFactory.cs @@ -0,0 +1,29 @@ +using System.Net.Quic; +using System.Threading; +using System.Threading.Tasks; +using SuperSocket.Connection; +using SuperSocket.Quic.Connection; + +#pragma warning disable CA2252 +namespace SuperSocket.Quic; + +internal class QuicConnectionFactory : IConnectionFactory +{ + private readonly ConnectionOptions _connectionOptions; + + public QuicConnectionFactory(ConnectionOptions connectionOptions) + { + _connectionOptions = connectionOptions; + } + + public Task CreateConnection(object connection, CancellationToken cancellationToken) + { + var quicConnection = (QuicConnection)connection; + + var quicStream = new QuicPipeStream(quicConnection); + + var pipeConnection = new QuicPipeConnection(quicStream, quicConnection.RemoteEndPoint, quicConnection.LocalEndPoint, _connectionOptions); + + return Task.FromResult(pipeConnection); + } +} \ No newline at end of file diff --git a/src/SuperSocket.Quic/QuicConnectionFactoryBuilder.cs b/src/SuperSocket.Quic/QuicConnectionFactoryBuilder.cs new file mode 100644 index 000000000..7ca1e740c --- /dev/null +++ b/src/SuperSocket.Quic/QuicConnectionFactoryBuilder.cs @@ -0,0 +1,13 @@ +using SuperSocket.Connection; +using SuperSocket.Server.Abstractions; +using SuperSocket.Server.Abstractions.Connections; + +namespace SuperSocket.Quic; + +internal class QuicConnectionFactoryBuilder : IConnectionFactoryBuilder +{ + public IConnectionFactory Build(ListenOptions listenOptions, ConnectionOptions connectionOptions) + { + return new QuicConnectionFactory(connectionOptions); + } +} \ No newline at end of file diff --git a/src/SuperSocket.Quic/QuicConnectionListener.cs b/src/SuperSocket.Quic/QuicConnectionListener.cs new file mode 100644 index 000000000..ded0514f2 --- /dev/null +++ b/src/SuperSocket.Quic/QuicConnectionListener.cs @@ -0,0 +1,157 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using SuperSocket.Connection; +using SuperSocket.Server.Abstractions; +using SuperSocket.Server.Abstractions.Connections; +using System.Net.Quic; +using System.Net.Security; + +#pragma warning disable CA2252 +namespace SuperSocket.Quic; + +internal sealed class QuicConnectionListener : IConnectionListener +{ + private readonly ILogger _logger; + private readonly QuicTransportOptions _quicTransportOptions; + + private QuicListener _listenQuic; + private CancellationTokenSource _cancellationTokenSource; + private TaskCompletionSource _stopTaskCompletionSource; + public IConnectionFactory ConnectionFactory { get; } + public ListenOptions Options { get; } + public bool IsRunning { get; private set; } + + public QuicConnectionListener(ListenOptions options, + QuicTransportOptions quicTransportOptions, + IConnectionFactory connectionFactory, ILogger logger) + { + Options = options; + ConnectionFactory = connectionFactory; + _logger = logger; + _quicTransportOptions = quicTransportOptions; + } + + public bool Start() + { + var options = Options; + + try + { + var listenEndpoint = options.ToEndPoint(); + + if (options.CertificateOptions == null) + throw new ArgumentNullException(nameof(options.CertificateOptions),"Quic requires an ssl certificate"); + + if (options.CertificateOptions.Certificate == null) + options.CertificateOptions.EnsureCertificate(); + + var quicListenerOptions = new QuicListenerOptions + { + ListenBacklog = options.BackLog, + ListenEndPoint = listenEndpoint, + ApplicationProtocols = new List { SslApplicationProtocol.Http3 }, + ConnectionOptionsCallback = (connection, ssl, token) => ValueTask.FromResult( + new QuicServerConnectionOptions + { + DefaultStreamErrorCode = _quicTransportOptions.DefaultStreamErrorCode, + DefaultCloseErrorCode = _quicTransportOptions.DefaultCloseErrorCode, + IdleTimeout = _quicTransportOptions.IdleTimeout.HasValue + ? TimeSpan.FromMicroseconds(_quicTransportOptions.IdleTimeout.Value) + : Timeout.InfiniteTimeSpan, + MaxInboundBidirectionalStreams = _quicTransportOptions.MaxBidirectionalStreamCount, + MaxInboundUnidirectionalStreams = _quicTransportOptions.MaxUnidirectionalStreamCount, + ServerAuthenticationOptions = new SslServerAuthenticationOptions() + { + ApplicationProtocols = + new List { SslApplicationProtocol.Http3 }, + ServerCertificate = options.CertificateOptions.Certificate, + RemoteCertificateValidationCallback = + options.CertificateOptions.RemoteCertificateValidationCallback, + } + }) + }; + + var listenSocket = _listenQuic = QuicListener.ListenAsync(quicListenerOptions).GetAwaiter().GetResult(); + + IsRunning = true; + + _cancellationTokenSource = new CancellationTokenSource(); + + KeepAcceptAsync(listenSocket, _cancellationTokenSource.Token).DoNotAwait(); + return true; + } + catch (Exception e) + { + _logger.LogError(e, $"The listener[{this.ToString()}] failed to start."); + return false; + } + } + + + private async Task KeepAcceptAsync(QuicListener listenSocket, CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { + try + { + var quicConnection = + await listenSocket.AcceptConnectionAsync(cancellationToken).ConfigureAwait(false); + OnNewClientAccept(quicConnection, cancellationToken); + } + catch (Exception e) + { + _logger.LogError(e, $"Listener[{this.ToString()}] failed to do AcceptAsync"); + } + } + + _stopTaskCompletionSource.TrySetResult(true); + } + + public event NewConnectionAcceptHandler NewConnectionAccept; + + private async void OnNewClientAccept(QuicConnection quicConnection, CancellationToken cancellationToken) + { + var handler = NewConnectionAccept; + + if (handler == null) + return; + + IConnection connection = null; + + try + { + using var cts = CancellationTokenSourcePool.Shared.Rent(Options.ConnectionAcceptTimeOut); + connection = await ConnectionFactory.CreateConnection(quicConnection, cts.Token); + } + catch (Exception e) + { + _logger.LogError(e, $"Failed to create quicConnection for {quicConnection.RemoteEndPoint}."); + return; + } + + await handler.Invoke(this.Options, connection); + } + + public async Task StopAsync() + { + var listenSocket = _listenQuic; + + if (listenSocket == null) + return; + + _stopTaskCompletionSource = new TaskCompletionSource(); + + _cancellationTokenSource.Cancel(); + await _listenQuic.DisposeAsync(); + + await _stopTaskCompletionSource.Task; + } + + public override string ToString() + { + return Options?.ToString(); + } +} \ No newline at end of file diff --git a/src/SuperSocket.Quic/QuicConnectionListenerFactory.cs b/src/SuperSocket.Quic/QuicConnectionListenerFactory.cs new file mode 100644 index 000000000..c6e5cc00f --- /dev/null +++ b/src/SuperSocket.Quic/QuicConnectionListenerFactory.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Net.Quic; +using System.Net.Security; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using SuperSocket.Connection; +using SuperSocket.Server.Abstractions; +using SuperSocket.Server.Abstractions.Connections; + +namespace SuperSocket.Quic; +#pragma warning disable CA2252 +internal class QuicConnectionListenerFactory : IConnectionListenerFactory +{ + private readonly QuicTransportOptions _quicTransportOptions; + private readonly IConnectionFactoryBuilder _connectionFactoryBuilder; + + public QuicConnectionListenerFactory(IConnectionFactoryBuilder connectionFactoryBuilder, + IOptions options) + { + _connectionFactoryBuilder = connectionFactoryBuilder; + _quicTransportOptions = options.Value; + } + + public IConnectionListener CreateConnectionListener(ListenOptions options, ConnectionOptions connectionOptions, + ILoggerFactory loggerFactory) + { + connectionOptions.Logger = loggerFactory.CreateLogger(nameof(IConnection)); + var connectionFactoryLogger = loggerFactory.CreateLogger(nameof(QuicConnectionListener)); + + var connectionFactory = _connectionFactoryBuilder.Build(options, connectionOptions); + + var listenEndpoint = options.ToEndPoint(); + + if (options.CertificateOptions == null) + throw new ArgumentNullException(nameof(options.CertificateOptions),"Quic requires an ssl certificate"); + + if (options.CertificateOptions.Certificate == null) + options.CertificateOptions.EnsureCertificate(); + + var quicListenerOptions = new QuicListenerOptions + { + ListenBacklog = options.BackLog, + ListenEndPoint = listenEndpoint, + ApplicationProtocols = new List { SslApplicationProtocol.Http3 }, + ConnectionOptionsCallback = (connection, ssl, token) => ValueTask.FromResult( + new QuicServerConnectionOptions + { + DefaultStreamErrorCode = _quicTransportOptions.DefaultStreamErrorCode, + DefaultCloseErrorCode = _quicTransportOptions.DefaultCloseErrorCode, + IdleTimeout = _quicTransportOptions.IdleTimeout.HasValue + ? TimeSpan.FromMicroseconds(_quicTransportOptions.IdleTimeout.Value) + : Timeout.InfiniteTimeSpan, + MaxInboundBidirectionalStreams = _quicTransportOptions.MaxBidirectionalStreamCount, + MaxInboundUnidirectionalStreams = _quicTransportOptions.MaxUnidirectionalStreamCount, + ServerAuthenticationOptions = new SslServerAuthenticationOptions() + { + ApplicationProtocols = + new List { SslApplicationProtocol.Http3 }, + ServerCertificate = options.CertificateOptions.Certificate, + RemoteCertificateValidationCallback = + options.CertificateOptions.RemoteCertificateValidationCallback, + } + }) + }; + + return new QuicConnectionListener(options, _quicTransportOptions, connectionFactory, + connectionFactoryLogger); + } +} \ No newline at end of file diff --git a/src/SuperSocket.Quic/QuicServerHostBuilderExtensions.cs b/src/SuperSocket.Quic/QuicServerHostBuilderExtensions.cs new file mode 100644 index 000000000..a931c0a37 --- /dev/null +++ b/src/SuperSocket.Quic/QuicServerHostBuilderExtensions.cs @@ -0,0 +1,44 @@ +using System; +using System.Net.Quic; +using Microsoft.Extensions.DependencyInjection; +using SuperSocket.Server.Abstractions.Connections; +using SuperSocket.Server.Abstractions.Host; +using SuperSocket.Quic; + +#pragma warning disable CA2252 + +namespace SuperSocket.Server; + +public static class QuicServerHostBuilderExtensions +{ + public static ISuperSocketHostBuilder UseQuic(this ISuperSocketHostBuilder hostBuilder) + { + return hostBuilder.UseQuic(o => { }); + } + + public static ISuperSocketHostBuilder UseQuic( + this ISuperSocketHostBuilder hostBuilder) + { + return (hostBuilder as ISuperSocketHostBuilder).UseQuic(o => { }) as + ISuperSocketHostBuilder; + } + + public static ISuperSocketHostBuilder UseQuic(this ISuperSocketHostBuilder hostBuilder, Action globalConfigure) + { + return (hostBuilder as ISuperSocketHostBuilder).UseQuic(globalConfigure) as + ISuperSocketHostBuilder; + } + + public static ISuperSocketHostBuilder UseQuic(this ISuperSocketHostBuilder hostBuilder, Action globalConfigure) + { + if (!QuicListener.IsSupported) + throw new PlatformNotSupportedException("System.Net.Quic is not supported on this platform."); + + return hostBuilder.ConfigureServices((_, services) => + { + services.Configure(globalConfigure); + services.AddSingleton(); + services.AddSingleton(); + }) as ISuperSocketHostBuilder; + } +} \ No newline at end of file diff --git a/src/SuperSocket.Quic/QuicTransportOptions.cs b/src/SuperSocket.Quic/QuicTransportOptions.cs new file mode 100644 index 000000000..6793c8f58 --- /dev/null +++ b/src/SuperSocket.Quic/QuicTransportOptions.cs @@ -0,0 +1,27 @@ +namespace SuperSocket.Quic; + +public sealed class QuicTransportOptions +{ + public int MaxBidirectionalStreamCount { get; set; } = 100; + + /// + /// The maximum number of concurrent inbound uni-directional streams per connection. + /// + public int MaxUnidirectionalStreamCount { get; set; } = 10; + + /// + /// Error code used when the stream needs to abort the read or write side of the stream internally. + /// + public long DefaultStreamErrorCode { get; set; } + + /// + /// Error code used when an open connection is disposed + /// + public long DefaultCloseErrorCode { get; set; } + + /// Gets or sets the idle timeout for connections. The idle timeout is the time after which the connection will be closed. + /// Default means underlying implementation default idle timeout. + /// The idle timeout for connections. The default is , which means that the default idle timeout of the underlying implementation is used. + public int? IdleTimeout { get; set; } + +} \ No newline at end of file diff --git a/src/SuperSocket.Quic/SuperSocket.Quic.csproj b/src/SuperSocket.Quic/SuperSocket.Quic.csproj new file mode 100644 index 000000000..0be6201ae --- /dev/null +++ b/src/SuperSocket.Quic/SuperSocket.Quic.csproj @@ -0,0 +1,15 @@ + + + + SuperSocket quic library. + net7.0;net8.0 + True + + + + + + + + + diff --git a/test/SuperSocket.Tests/ClientTest.cs b/test/SuperSocket.Tests/ClientTest.cs index b36cff6e2..0f1103425 100644 --- a/test/SuperSocket.Tests/ClientTest.cs +++ b/test/SuperSocket.Tests/ClientTest.cs @@ -37,6 +37,7 @@ public ClientTest(ITestOutputHelper outputHelper) [InlineData(typeof(GzipHostConfigurator), false)] [InlineData(typeof(GzipSecureHostConfigurator), false)] [InlineData(typeof(RegularHostConfigurator), true)] + [InlineData(typeof(QuicHostConfigurator), false)] public async Task TestEcho(Type hostConfiguratorType, bool clientReadAsDemand) { var serverSessionEvent = new AutoResetEvent(false); @@ -99,6 +100,7 @@ public async Task TestEcho(Type hostConfiguratorType, bool clientReadAsDemand) [Theory] [InlineData(typeof(RegularHostConfigurator))] [InlineData(typeof(GzipHostConfigurator))] + [InlineData(typeof(QuicHostConfigurator))] [Trait("Category", "Client.TestBindLocalEndPoint")] public async Task TestBindLocalEndPoint(Type hostConfiguratorType) { @@ -197,6 +199,7 @@ public void TestCancellationTokenIsBeingUsedWhenConnecting() [InlineData(typeof(SecureHostConfigurator))] [InlineData(typeof(GzipSecureHostConfigurator))] [InlineData(typeof(GzipHostConfigurator))] + [InlineData(typeof(QuicHostConfigurator))] public async Task TestCommandLine(Type hostConfiguratorType) { var packageEvent = new AutoResetEvent(false); diff --git a/test/SuperSocket.Tests/QuicHostConfigurator.cs b/test/SuperSocket.Tests/QuicHostConfigurator.cs new file mode 100644 index 000000000..9fb08ffaf --- /dev/null +++ b/test/SuperSocket.Tests/QuicHostConfigurator.cs @@ -0,0 +1,148 @@ +using System; +using System.Buffers; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Quic; +using System.Net.Security; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using SuperSocket.Connection; +using SuperSocket.Server; +using SuperSocket.Server.Abstractions; +using SuperSocket.Server.Abstractions.Host; +using SuperSocket.Client; +using SuperSocket.ProtoBase; +using SuperSocket.Quic; +using SuperSocket.Quic.Connection; + +#pragma warning disable CA2252 +namespace SuperSocket.Tests +{ + public class QuicHostConfigurator : IHostConfigurator + { + private static readonly ArrayPool _bufferPool = ArrayPool.Shared; + + public string WebSocketSchema => "ws"; + + public bool IsSecure => false; + + public ListenOptions Listener { get; private set; } + + public IEasyClient ConfigureEasyClient(IPipelineFilter pipelineFilter, + ConnectionOptions options) where TPackageInfo : class + { + return new QuicClient(pipelineFilter, options); + } + + private static Random _rd = new Random(); + + public void Configure(ISuperSocketHostBuilder hostBuilder) + { + hostBuilder + .UseQuic() + .ConfigureServices((ctx, services) => + { + services.Configure((options) => + { + var listener = options.Listeners[0]; + listener.CertificateOptions = new CertificateOptions + { + FilePath = "supersocket.pfx", + Password = "supersocket" + }; + Listener = listener; + }); + } + ); + } + + public TextReader GetStreamReader(Stream stream, Encoding encoding) + { + throw new NotImplementedException(); + } + + public ValueTask GetClientStream(Socket socket) + { + throw new NotImplementedException(); + } + + private IPipelineFilter GetPipelineFilter() + { + return new TerminatorPipelineFilter(new[] { (byte)'\r', (byte)'\n' }) + { + Decoder = new UdpPackageDecoder() + }; + } + + class UdpPackageDecoder : IPackageDecoder + { + public TextPackageInfo Decode(ref ReadOnlySequence buffer, object context) + { + return new TextPackageInfo { Text = buffer.GetString(Encoding.UTF8) }; + } + } + + class QuicClient : EasyClient + where TReceivePackage : class + { + public QuicClient(IPipelineFilter pipelineFilter, ConnectionOptions options) + : base(pipelineFilter, options) + { + } + + protected override async ValueTask ConnectAsync(EndPoint remoteEndPoint, + CancellationToken cancellationToken) + { + var quicConnection = await QuicConnection.ConnectAsync( + cancellationToken: cancellationToken, + options: new QuicClientConnectionOptions + { + DefaultCloseErrorCode = 0, + DefaultStreamErrorCode = 0, + RemoteEndPoint = remoteEndPoint, + LocalEndPoint = LocalEndPoint, + IdleTimeout = TimeSpan.FromSeconds(10), + ClientAuthenticationOptions = new SslClientAuthenticationOptions + { + ApplicationProtocols = new List { SslApplicationProtocol.Http3 }, + RemoteCertificateValidationCallback = (sender, certificate, chain, errors) => + { + return true; + } + } + }); + + if (cancellationToken.IsCancellationRequested) + { + OnError($"The connection to {remoteEndPoint} was cancelled."); + return false; + } + + var quicStream = + await quicConnection.OpenOutboundStreamAsync(QuicStreamType.Bidirectional, cancellationToken); + + var connection = new QuicPipeConnection(quicStream, quicConnection.RemoteEndPoint, + quicConnection.LocalEndPoint, Options); + SetupConnection(connection); + + return true; + } + } + + public async ValueTask KeepSequence() + { + await Task.Delay(200); + } + + public Socket CreateClient() + { + throw new NotImplementedException(); + } + } +} diff --git a/test/SuperSocket.Tests/SuperSocket.Tests.csproj b/test/SuperSocket.Tests/SuperSocket.Tests.csproj index 2f292b952..084867a20 100755 --- a/test/SuperSocket.Tests/SuperSocket.Tests.csproj +++ b/test/SuperSocket.Tests/SuperSocket.Tests.csproj @@ -13,6 +13,7 @@ + From 78193efd8c90ce4406b663b7177bcaa2deaf8f7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E4=BF=8A?= <8400684@qq.com> Date: Sat, 4 May 2024 21:55:18 +0800 Subject: [PATCH 2/8] fix bugs --- src/SuperSocket.Quic.Connection/QuicPipeConnection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SuperSocket.Quic.Connection/QuicPipeConnection.cs b/src/SuperSocket.Quic.Connection/QuicPipeConnection.cs index 88f0221f1..20d3fadb0 100644 --- a/src/SuperSocket.Quic.Connection/QuicPipeConnection.cs +++ b/src/SuperSocket.Quic.Connection/QuicPipeConnection.cs @@ -23,7 +23,7 @@ public QuicPipeConnection(Stream stream, EndPoint remoteEndPoint, ConnectionOpti public QuicPipeConnection(Stream stream, EndPoint remoteEndPoint, EndPoint localEndPoint, ConnectionOptions options) : base(stream, remoteEndPoint, localEndPoint, options) { - if (stream is not QuicStream or QuicPipeStream) + if (stream is not QuicStream && stream is not QuicPipeStream) throw new NotSupportedException("QuicPipeConnection only supports QuicStream or QuicPipeStream"); _stream = stream; From 209c614801aaf8c53c6cdfa9cec648d53bcb970f Mon Sep 17 00:00:00 2001 From: mockingjay <8400684@qq.com> Date: Mon, 6 May 2024 11:25:38 +0800 Subject: [PATCH 3/8] fix bugs --- src/SuperSocket.Quic/QuicConnectionListener.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SuperSocket.Quic/QuicConnectionListener.cs b/src/SuperSocket.Quic/QuicConnectionListener.cs index ded0514f2..f8639b96c 100644 --- a/src/SuperSocket.Quic/QuicConnectionListener.cs +++ b/src/SuperSocket.Quic/QuicConnectionListener.cs @@ -99,7 +99,7 @@ private async Task KeepAcceptAsync(QuicListener listenSocket, CancellationToken { var quicConnection = await listenSocket.AcceptConnectionAsync(cancellationToken).ConfigureAwait(false); - OnNewClientAccept(quicConnection, cancellationToken); + OnNewClientAccept(quicConnection); } catch (Exception e) { @@ -112,7 +112,7 @@ private async Task KeepAcceptAsync(QuicListener listenSocket, CancellationToken public event NewConnectionAcceptHandler NewConnectionAccept; - private async void OnNewClientAccept(QuicConnection quicConnection, CancellationToken cancellationToken) + private async void OnNewClientAccept(QuicConnection quicConnection) { var handler = NewConnectionAccept; From 26fd35c23d8f9e877783da2ce024728747bb4b8f Mon Sep 17 00:00:00 2001 From: mockingjay <8400684@qq.com> Date: Sat, 11 May 2024 10:12:20 +0800 Subject: [PATCH 4/8] fix bugs --- .../PipeConnectionBase.cs | 7 +------ .../QuicPipeConnection.cs | 19 ++----------------- src/SuperSocket.Quic/QuicConnectionFactory.cs | 9 ++++----- 3 files changed, 7 insertions(+), 28 deletions(-) diff --git a/src/SuperSocket.Connection/PipeConnectionBase.cs b/src/SuperSocket.Connection/PipeConnectionBase.cs index cba380d7a..d390ddc15 100644 --- a/src/SuperSocket.Connection/PipeConnectionBase.cs +++ b/src/SuperSocket.Connection/PipeConnectionBase.cs @@ -82,12 +82,7 @@ public async override IAsyncEnumerable RunAsync(IPip { var package = await packagePipe.ReadAsync().ConfigureAwait(false); - if (package == null) - { - yield break; - } - - yield return package; + e } //How do empty a pipe? diff --git a/src/SuperSocket.Quic.Connection/QuicPipeConnection.cs b/src/SuperSocket.Quic.Connection/QuicPipeConnection.cs index 20d3fadb0..91a224edf 100644 --- a/src/SuperSocket.Quic.Connection/QuicPipeConnection.cs +++ b/src/SuperSocket.Quic.Connection/QuicPipeConnection.cs @@ -13,29 +13,14 @@ namespace SuperSocket.Quic.Connection; public class QuicPipeConnection : StreamPipeConnection { - private readonly Stream _stream; - - public QuicPipeConnection(Stream stream, EndPoint remoteEndPoint, ConnectionOptions options) + public QuicPipeConnection(QuicStream stream, EndPoint remoteEndPoint, ConnectionOptions options) : this(stream, remoteEndPoint, null, options) { } - public QuicPipeConnection(Stream stream, EndPoint remoteEndPoint, EndPoint localEndPoint, ConnectionOptions options) + public QuicPipeConnection(QuicStream stream, EndPoint remoteEndPoint, EndPoint localEndPoint, ConnectionOptions options) : base(stream, remoteEndPoint, localEndPoint, options) { - if (stream is not QuicStream && stream is not QuicPipeStream) - throw new NotSupportedException("QuicPipeConnection only supports QuicStream or QuicPipeStream"); - - _stream = stream; - } - - protected override async Task StartInputPipeTask(IObjectPipe packagePipe, - CancellationToken cancellationToken) - { - if (_stream is QuicPipeStream quicPipeStream) - await quicPipeStream.OpenStreamAsync(cancellationToken); - - await base.StartInputPipeTask(packagePipe, cancellationToken); } protected override bool IsIgnorableException(Exception e) diff --git a/src/SuperSocket.Quic/QuicConnectionFactory.cs b/src/SuperSocket.Quic/QuicConnectionFactory.cs index a59e5950c..e2b69ade9 100644 --- a/src/SuperSocket.Quic/QuicConnectionFactory.cs +++ b/src/SuperSocket.Quic/QuicConnectionFactory.cs @@ -16,14 +16,13 @@ public QuicConnectionFactory(ConnectionOptions connectionOptions) _connectionOptions = connectionOptions; } - public Task CreateConnection(object connection, CancellationToken cancellationToken) + public async Task CreateConnection(object connection, CancellationToken cancellationToken) { var quicConnection = (QuicConnection)connection; - var quicStream = new QuicPipeStream(quicConnection); + var stream = await quicConnection.AcceptInboundStreamAsync(cancellationToken); - var pipeConnection = new QuicPipeConnection(quicStream, quicConnection.RemoteEndPoint, quicConnection.LocalEndPoint, _connectionOptions); - - return Task.FromResult(pipeConnection); + return new QuicPipeConnection(stream, quicConnection.RemoteEndPoint, + quicConnection.LocalEndPoint, _connectionOptions); } } \ No newline at end of file From 6b11ba8286576d0a99dffc39a66b6efd925c604d Mon Sep 17 00:00:00 2001 From: mockingjay <8400684@qq.com> Date: Sat, 11 May 2024 10:13:16 +0800 Subject: [PATCH 5/8] fix bus --- src/SuperSocket.Connection/PipeConnectionBase.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/SuperSocket.Connection/PipeConnectionBase.cs b/src/SuperSocket.Connection/PipeConnectionBase.cs index d390ddc15..cba380d7a 100644 --- a/src/SuperSocket.Connection/PipeConnectionBase.cs +++ b/src/SuperSocket.Connection/PipeConnectionBase.cs @@ -82,7 +82,12 @@ public async override IAsyncEnumerable RunAsync(IPip { var package = await packagePipe.ReadAsync().ConfigureAwait(false); - e + if (package == null) + { + yield break; + } + + yield return package; } //How do empty a pipe? From f9894eb40663d211765df4c7e2bb3a52e9bf0a38 Mon Sep 17 00:00:00 2001 From: mockingjay <8400684@qq.com> Date: Sat, 11 May 2024 10:27:47 +0800 Subject: [PATCH 6/8] Revert "fix bugs" This reverts commit 26fd35c23d8f9e877783da2ce024728747bb4b8f. --- .../QuicPipeConnection.cs | 19 +++++++++++++++++-- src/SuperSocket.Quic/QuicConnectionFactory.cs | 9 +++++---- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/SuperSocket.Quic.Connection/QuicPipeConnection.cs b/src/SuperSocket.Quic.Connection/QuicPipeConnection.cs index 91a224edf..20d3fadb0 100644 --- a/src/SuperSocket.Quic.Connection/QuicPipeConnection.cs +++ b/src/SuperSocket.Quic.Connection/QuicPipeConnection.cs @@ -13,14 +13,29 @@ namespace SuperSocket.Quic.Connection; public class QuicPipeConnection : StreamPipeConnection { - public QuicPipeConnection(QuicStream stream, EndPoint remoteEndPoint, ConnectionOptions options) + private readonly Stream _stream; + + public QuicPipeConnection(Stream stream, EndPoint remoteEndPoint, ConnectionOptions options) : this(stream, remoteEndPoint, null, options) { } - public QuicPipeConnection(QuicStream stream, EndPoint remoteEndPoint, EndPoint localEndPoint, ConnectionOptions options) + public QuicPipeConnection(Stream stream, EndPoint remoteEndPoint, EndPoint localEndPoint, ConnectionOptions options) : base(stream, remoteEndPoint, localEndPoint, options) { + if (stream is not QuicStream && stream is not QuicPipeStream) + throw new NotSupportedException("QuicPipeConnection only supports QuicStream or QuicPipeStream"); + + _stream = stream; + } + + protected override async Task StartInputPipeTask(IObjectPipe packagePipe, + CancellationToken cancellationToken) + { + if (_stream is QuicPipeStream quicPipeStream) + await quicPipeStream.OpenStreamAsync(cancellationToken); + + await base.StartInputPipeTask(packagePipe, cancellationToken); } protected override bool IsIgnorableException(Exception e) diff --git a/src/SuperSocket.Quic/QuicConnectionFactory.cs b/src/SuperSocket.Quic/QuicConnectionFactory.cs index e2b69ade9..a59e5950c 100644 --- a/src/SuperSocket.Quic/QuicConnectionFactory.cs +++ b/src/SuperSocket.Quic/QuicConnectionFactory.cs @@ -16,13 +16,14 @@ public QuicConnectionFactory(ConnectionOptions connectionOptions) _connectionOptions = connectionOptions; } - public async Task CreateConnection(object connection, CancellationToken cancellationToken) + public Task CreateConnection(object connection, CancellationToken cancellationToken) { var quicConnection = (QuicConnection)connection; - var stream = await quicConnection.AcceptInboundStreamAsync(cancellationToken); + var quicStream = new QuicPipeStream(quicConnection); - return new QuicPipeConnection(stream, quicConnection.RemoteEndPoint, - quicConnection.LocalEndPoint, _connectionOptions); + var pipeConnection = new QuicPipeConnection(quicStream, quicConnection.RemoteEndPoint, quicConnection.LocalEndPoint, _connectionOptions); + + return Task.FromResult(pipeConnection); } } \ No newline at end of file From 263e4a7e7d25ffba6491125ab7f15a85c8361bbe Mon Sep 17 00:00:00 2001 From: mockingjay <8400684@qq.com> Date: Sun, 12 May 2024 09:47:47 +0800 Subject: [PATCH 7/8] try test --- src/SuperSocket.Quic.Connection/QuicPipeStream.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/SuperSocket.Quic.Connection/QuicPipeStream.cs b/src/SuperSocket.Quic.Connection/QuicPipeStream.cs index b1bb6ecd5..df8556217 100644 --- a/src/SuperSocket.Quic.Connection/QuicPipeStream.cs +++ b/src/SuperSocket.Quic.Connection/QuicPipeStream.cs @@ -23,7 +23,11 @@ public QuicPipeStream(QuicConnection connection) public override bool CanSeek => _stream.CanSeek; public override bool CanWrite => _stream.CanWrite; public override long Length => _stream.Length; - + public override int ReadTimeout + { + get => _stream.ReadTimeout; + set => _stream.ReadTimeout = value; + } public override long Position { get => _stream.Position; From 41513a0ea172a9b7cafe5f45627bcb12de3dbb2c Mon Sep 17 00:00:00 2001 From: mockingjay <8400684@qq.com> Date: Sun, 12 May 2024 12:28:18 +0800 Subject: [PATCH 8/8] try test --- .../QuicPipeConnection.cs | 16 ++++++++-------- src/SuperSocket.Quic/QuicConnectionFactory.cs | 16 ++++++++++------ 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/SuperSocket.Quic.Connection/QuicPipeConnection.cs b/src/SuperSocket.Quic.Connection/QuicPipeConnection.cs index 20d3fadb0..39a3daf4b 100644 --- a/src/SuperSocket.Quic.Connection/QuicPipeConnection.cs +++ b/src/SuperSocket.Quic.Connection/QuicPipeConnection.cs @@ -29,14 +29,14 @@ public QuicPipeConnection(Stream stream, EndPoint remoteEndPoint, EndPoint local _stream = stream; } - protected override async Task StartInputPipeTask(IObjectPipe packagePipe, - CancellationToken cancellationToken) - { - if (_stream is QuicPipeStream quicPipeStream) - await quicPipeStream.OpenStreamAsync(cancellationToken); - - await base.StartInputPipeTask(packagePipe, cancellationToken); - } + // protected override async Task StartInputPipeTask(IObjectPipe packagePipe, + // CancellationToken cancellationToken) + // { + // if (_stream is QuicPipeStream quicPipeStream) + // await quicPipeStream.OpenStreamAsync(cancellationToken); + // + // await base.StartInputPipeTask(packagePipe, cancellationToken); + // } protected override bool IsIgnorableException(Exception e) { diff --git a/src/SuperSocket.Quic/QuicConnectionFactory.cs b/src/SuperSocket.Quic/QuicConnectionFactory.cs index a59e5950c..e7db55565 100644 --- a/src/SuperSocket.Quic/QuicConnectionFactory.cs +++ b/src/SuperSocket.Quic/QuicConnectionFactory.cs @@ -16,14 +16,18 @@ public QuicConnectionFactory(ConnectionOptions connectionOptions) _connectionOptions = connectionOptions; } - public Task CreateConnection(object connection, CancellationToken cancellationToken) + public async Task CreateConnection(object connection, CancellationToken cancellationToken) { var quicConnection = (QuicConnection)connection; - var quicStream = new QuicPipeStream(quicConnection); - - var pipeConnection = new QuicPipeConnection(quicStream, quicConnection.RemoteEndPoint, quicConnection.LocalEndPoint, _connectionOptions); - - return Task.FromResult(pipeConnection); + var quicStream = await quicConnection.AcceptInboundStreamAsync(CancellationToken.None); + + return new QuicPipeConnection(quicStream, quicConnection.RemoteEndPoint, quicConnection.LocalEndPoint, _connectionOptions); + + // var quicStream = new QuicPipeStream(quicConnection); + // + // var pipeConnection = new QuicPipeConnection(quicStream, quicConnection.RemoteEndPoint, quicConnection.LocalEndPoint, _connectionOptions); + // + // return Task.FromResult(pipeConnection); } } \ No newline at end of file