From eb854597d939ef2468087399e211ed7df19e5c26 Mon Sep 17 00:00:00 2001 From: mockingjay <8400684@qq.com> Date: Fri, 3 May 2024 16:31:24 +0800 Subject: [PATCH 1/8] supporting quic --- SuperSocket.sln | 30 ++++ .../QuicPipeConnection.cs | 52 ++++++ .../QuicPipeStream.cs | 75 +++++++++ .../QuicStreamOptions.cs | 12 ++ .../SuperSocket.Quic.Connection.csproj | 12 ++ src/SuperSocket.Quic/QuicConnectionFactory.cs | 36 ++++ .../QuicConnectionFactoryBuilder.cs | 14 ++ .../QuicConnectionListener.cs | 159 ++++++++++++++++++ .../QuicConnectionListenerFactory.cs | 27 +++ .../QuicServerHostBuilderExtensions.cs | 30 ++++ src/SuperSocket.Quic/QuicTransportOptions.cs | 28 +++ src/SuperSocket.Quic/SuperSocket.Quic.csproj | 15 ++ test/SuperSocket.Tests/ClientTest.cs | 3 + .../SuperSocket.Tests/QuicHostConfigurator.cs | 155 +++++++++++++++++ .../SuperSocket.Tests.csproj | 1 + 15 files changed, 649 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/QuicStreamOptions.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..bc58ba436 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", "{F5FDE719-657B-43A8-9F08-E65C7987D357}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SuperSocket.Quic.Connection", "src\SuperSocket.Quic.Connection\SuperSocket.Quic.Connection.csproj", "{6EFA7A93-3B62-46C1-8816-E1802EBF037A}" +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 + {F5FDE719-657B-43A8-9F08-E65C7987D357}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F5FDE719-657B-43A8-9F08-E65C7987D357}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F5FDE719-657B-43A8-9F08-E65C7987D357}.Debug|x64.ActiveCfg = Debug|Any CPU + {F5FDE719-657B-43A8-9F08-E65C7987D357}.Debug|x64.Build.0 = Debug|Any CPU + {F5FDE719-657B-43A8-9F08-E65C7987D357}.Debug|x86.ActiveCfg = Debug|Any CPU + {F5FDE719-657B-43A8-9F08-E65C7987D357}.Debug|x86.Build.0 = Debug|Any CPU + {F5FDE719-657B-43A8-9F08-E65C7987D357}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F5FDE719-657B-43A8-9F08-E65C7987D357}.Release|Any CPU.Build.0 = Release|Any CPU + {F5FDE719-657B-43A8-9F08-E65C7987D357}.Release|x64.ActiveCfg = Release|Any CPU + {F5FDE719-657B-43A8-9F08-E65C7987D357}.Release|x64.Build.0 = Release|Any CPU + {F5FDE719-657B-43A8-9F08-E65C7987D357}.Release|x86.ActiveCfg = Release|Any CPU + {F5FDE719-657B-43A8-9F08-E65C7987D357}.Release|x86.Build.0 = Release|Any CPU + {6EFA7A93-3B62-46C1-8816-E1802EBF037A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6EFA7A93-3B62-46C1-8816-E1802EBF037A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6EFA7A93-3B62-46C1-8816-E1802EBF037A}.Debug|x64.ActiveCfg = Debug|Any CPU + {6EFA7A93-3B62-46C1-8816-E1802EBF037A}.Debug|x64.Build.0 = Debug|Any CPU + {6EFA7A93-3B62-46C1-8816-E1802EBF037A}.Debug|x86.ActiveCfg = Debug|Any CPU + {6EFA7A93-3B62-46C1-8816-E1802EBF037A}.Debug|x86.Build.0 = Debug|Any CPU + {6EFA7A93-3B62-46C1-8816-E1802EBF037A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6EFA7A93-3B62-46C1-8816-E1802EBF037A}.Release|Any CPU.Build.0 = Release|Any CPU + {6EFA7A93-3B62-46C1-8816-E1802EBF037A}.Release|x64.ActiveCfg = Release|Any CPU + {6EFA7A93-3B62-46C1-8816-E1802EBF037A}.Release|x64.Build.0 = Release|Any CPU + {6EFA7A93-3B62-46C1-8816-E1802EBF037A}.Release|x86.ActiveCfg = Release|Any CPU + {6EFA7A93-3B62-46C1-8816-E1802EBF037A}.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} + {F5FDE719-657B-43A8-9F08-E65C7987D357} = {DDA4741E-097F-40C3-A252-2E1E3476C1A7} + {6EFA7A93-3B62-46C1-8816-E1802EBF037A} = {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..56e3f2772 --- /dev/null +++ b/src/SuperSocket.Quic.Connection/QuicPipeConnection.cs @@ -0,0 +1,52 @@ +using System; +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 QuicPipeStream _stream; + + public QuicPipeConnection(QuicPipeStream stream, EndPoint remoteEndPoint, ConnectionOptions options) + : this(stream, remoteEndPoint, null, options) + { + _stream = stream; + } + + public QuicPipeConnection(QuicPipeStream stream, EndPoint remoteEndPoint, EndPoint localEndPoint, + ConnectionOptions options) + : base(stream, remoteEndPoint, localEndPoint, options) + { + _stream = stream; + } + + protected override async Task StartInputPipeTask(IObjectPipe packagePipe, + CancellationToken cancellationToken) + { + await _stream.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; + } + } + } +} \ No newline at end of file diff --git a/src/SuperSocket.Quic.Connection/QuicPipeStream.cs b/src/SuperSocket.Quic.Connection/QuicPipeStream.cs new file mode 100644 index 000000000..f8dee1eea --- /dev/null +++ b/src/SuperSocket.Quic.Connection/QuicPipeStream.cs @@ -0,0 +1,75 @@ +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 QuicStreamOptions _serverOptions; + private readonly QuicConnection _connection; + + public QuicPipeStream(QuicConnection connection, QuicStreamOptions streamOptions) + { + _connection = connection; + _serverOptions = streamOptions; + } + + 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) + { + if (_serverOptions.ServerStream) + { + _stream = await _connection.AcceptInboundStreamAsync(cancellationToken); + } + else + { + _stream = await _connection.OpenOutboundStreamAsync(_serverOptions.StreamType, 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/QuicStreamOptions.cs b/src/SuperSocket.Quic.Connection/QuicStreamOptions.cs new file mode 100644 index 000000000..c497fc8ab --- /dev/null +++ b/src/SuperSocket.Quic.Connection/QuicStreamOptions.cs @@ -0,0 +1,12 @@ +using System.Net.Quic; + +#pragma warning disable CA2252 +namespace SuperSocket.Quic.Connection +{ + public sealed class QuicStreamOptions + { + public bool ServerStream { get; set; } + + public QuicStreamType StreamType { get; set; } + } +} \ 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..437fd5036 --- /dev/null +++ b/src/SuperSocket.Quic/QuicConnectionFactory.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using System.IO; +using System.Net.Quic; +using System.Threading; +using System.Threading.Tasks; +using SuperSocket.Connection; +using SuperSocket.Quic.Connection; +using SuperSocket.Server.Abstractions; +using SuperSocket.Server.Abstractions.Connections; + +#pragma warning disable CA2252 +namespace SuperSocket.Quic +{ + internal class QuicConnectionFactory : IConnectionFactory + { + private readonly ListenOptions _listenOptions; + private readonly ConnectionOptions _connectionOptions; + public QuicConnectionFactory(ListenOptions listenOptions, ConnectionOptions connectionOptions) + { + _listenOptions = listenOptions; + _connectionOptions = connectionOptions; + } + + public async Task CreateConnection(object connection, CancellationToken cancellationToken) + { + var quicConnection = connection as QuicConnection; + + var quicStream = new QuicPipeStream(quicConnection, new QuicStreamOptions + { + ServerStream = true, + }); + + return new QuicPipeConnection(quicStream, quicConnection.RemoteEndPoint, quicConnection.LocalEndPoint, _connectionOptions); + } + } +} diff --git a/src/SuperSocket.Quic/QuicConnectionFactoryBuilder.cs b/src/SuperSocket.Quic/QuicConnectionFactoryBuilder.cs new file mode 100644 index 000000000..ee4df7c9f --- /dev/null +++ b/src/SuperSocket.Quic/QuicConnectionFactoryBuilder.cs @@ -0,0 +1,14 @@ +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(listenOptions, 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..86916c9ee --- /dev/null +++ b/src/SuperSocket.Quic/QuicConnectionListener.cs @@ -0,0 +1,159 @@ +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 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, IConnectionFactory connectionFactory, ILogger logger) + { + Options = options; + ConnectionFactory = connectionFactory; + _logger = logger; + } + + public bool Start() + { + var options = Options; + + try + { + var listenEndpoint = options.ToEndPoint(); + + ArgumentNullException.ThrowIfNull(options.CertificateOptions); + + 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 = 0, + DefaultCloseErrorCode = 0, + IdleTimeout = TimeSpan.FromMicroseconds(10), + //MaxInboundBidirectionalStreams = 0, + //MaxInboundUnidirectionalStreams = 0, + ServerAuthenticationOptions = new SslServerAuthenticationOptions() + { + ApplicationProtocols = + new List { SslApplicationProtocol.Http3 }, + ServerCertificate = options.CertificateOptions.Certificate, + RemoteCertificateValidationCallback = + options.CertificateOptions.RemoteCertificateValidationCallback, + } + }) + }; + + var result = QuicListener.ListenAsync(quicListenerOptions); + + if (result.IsCompleted) + _listenQuic = result.Result; + else + _listenQuic = result.GetAwaiter().GetResult(); + + var listenSocket = _listenQuic; + + 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(); + } + } +} diff --git a/src/SuperSocket.Quic/QuicConnectionListenerFactory.cs b/src/SuperSocket.Quic/QuicConnectionListenerFactory.cs new file mode 100644 index 000000000..2f3326098 --- /dev/null +++ b/src/SuperSocket.Quic/QuicConnectionListenerFactory.cs @@ -0,0 +1,27 @@ +using Microsoft.Extensions.Logging; +using SuperSocket.Connection; +using SuperSocket.Server.Abstractions; +using SuperSocket.Server.Abstractions.Connections; + +namespace SuperSocket.Quic +{ + internal class QuicConnectionListenerFactory : IConnectionListenerFactory + { + private readonly IConnectionFactoryBuilder _connectionFactoryBuilder; + + public QuicConnectionListenerFactory(IConnectionFactoryBuilder connectionFactoryBuilder) + { + _connectionFactoryBuilder = connectionFactoryBuilder; + } + + 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); + + return new QuicConnectionListener(options, 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..bb6c0f674 --- /dev/null +++ b/src/SuperSocket.Quic/QuicServerHostBuilderExtensions.cs @@ -0,0 +1,30 @@ +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) + { + if (!QuicListener.IsSupported) + throw new PlatformNotSupportedException("System.Net.Quic is not supported on this platform."); + + return hostBuilder.ConfigureServices((_, services) => + { + services.AddSingleton(); + services.AddSingleton(); + }) as ISuperSocketHostBuilder; + } + + public static ISuperSocketHostBuilder UseQuic(this ISuperSocketHostBuilder hostBuilder) + { + return (hostBuilder as ISuperSocketHostBuilder).UseQuic() as ISuperSocketHostBuilder; + } + } +} diff --git a/src/SuperSocket.Quic/QuicTransportOptions.cs b/src/SuperSocket.Quic/QuicTransportOptions.cs new file mode 100644 index 000000000..9857b9378 --- /dev/null +++ b/src/SuperSocket.Quic/QuicTransportOptions.cs @@ -0,0 +1,28 @@ +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; + + /// The maximum read size. + public long MaxReadBufferSize { get; set; } = 1048576L; + + /// The maximum write size. + public long MaxWriteBufferSize { get; set; } = 65536L; + + /// The maximum length of the pending connection queue. + public int Backlog { get; set; } = 512; + + /// + /// 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; } +} \ 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..58bce05de 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..f4cb28bca --- /dev/null +++ b/test/SuperSocket.Tests/QuicHostConfigurator.cs @@ -0,0 +1,155 @@ +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; + +namespace SuperSocket.Tests +{ +#if NET7_0_OR_GREATER + + 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) + { +#pragma warning disable CA2252 + var quicConnection = await QuicConnection.ConnectAsync( + cancellationToken: cancellationToken, + options: new QuicClientConnectionOptions + { + DefaultCloseErrorCode = 0, + DefaultStreamErrorCode = 0, + RemoteEndPoint = remoteEndPoint, + LocalEndPoint = LocalEndPoint, + 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 = new QuicPipeStream(quicConnection, new QuicStreamOptions + { + ServerStream = false, + StreamType = QuicStreamType.Bidirectional, + }); + + 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(); + } + } + +#endif +} \ No newline at end of file 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 e0ea1a35e080661fc18d08293bf1a9e7d350fa7d Mon Sep 17 00:00:00 2001 From: wujun <8400684@qq.com> Date: Fri, 3 May 2024 16:37:27 +0800 Subject: [PATCH 2/8] add QuicHostConfigurator for TestBindLocalEndPoint --- test/SuperSocket.Tests/ClientTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/SuperSocket.Tests/ClientTest.cs b/test/SuperSocket.Tests/ClientTest.cs index 58bce05de..0f1103425 100644 --- a/test/SuperSocket.Tests/ClientTest.cs +++ b/test/SuperSocket.Tests/ClientTest.cs @@ -100,7 +100,7 @@ public async Task TestEcho(Type hostConfiguratorType, bool clientReadAsDemand) [Theory] [InlineData(typeof(RegularHostConfigurator))] [InlineData(typeof(GzipHostConfigurator))] - //[InlineData(typeof(QuicHostConfigurator))] + [InlineData(typeof(QuicHostConfigurator))] [Trait("Category", "Client.TestBindLocalEndPoint")] public async Task TestBindLocalEndPoint(Type hostConfiguratorType) { @@ -199,7 +199,7 @@ public void TestCancellationTokenIsBeingUsedWhenConnecting() [InlineData(typeof(SecureHostConfigurator))] [InlineData(typeof(GzipSecureHostConfigurator))] [InlineData(typeof(GzipHostConfigurator))] - //[InlineData(typeof(QuicHostConfigurator))] + [InlineData(typeof(QuicHostConfigurator))] public async Task TestCommandLine(Type hostConfiguratorType) { var packageEvent = new AutoResetEvent(false); From 95ae5983f2ead1c80d950e86fa7e59d805683d1f Mon Sep 17 00:00:00 2001 From: mockingjay <8400684@qq.com> Date: Fri, 3 May 2024 19:07:05 +0800 Subject: [PATCH 3/8] fix bugs --- .../QuicPipeConnection.cs | 56 ++++++++++++++----- src/SuperSocket.Quic/QuicConnectionFactory.cs | 16 ++++-- 2 files changed, 53 insertions(+), 19 deletions(-) diff --git a/src/SuperSocket.Quic.Connection/QuicPipeConnection.cs b/src/SuperSocket.Quic.Connection/QuicPipeConnection.cs index 56e3f2772..515318b4b 100644 --- a/src/SuperSocket.Quic.Connection/QuicPipeConnection.cs +++ b/src/SuperSocket.Quic.Connection/QuicPipeConnection.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Net; using System.Net.Quic; using System.Net.Sockets; @@ -12,26 +13,15 @@ namespace SuperSocket.Quic.Connection { public class QuicPipeConnection : StreamPipeConnection { - private readonly QuicPipeStream _stream; - - public QuicPipeConnection(QuicPipeStream stream, EndPoint remoteEndPoint, ConnectionOptions options) + public QuicPipeConnection(Stream stream, EndPoint remoteEndPoint, ConnectionOptions options) : this(stream, remoteEndPoint, null, options) { - _stream = stream; } - public QuicPipeConnection(QuicPipeStream stream, EndPoint remoteEndPoint, EndPoint localEndPoint, + public QuicPipeConnection(Stream stream, EndPoint remoteEndPoint, EndPoint localEndPoint, ConnectionOptions options) : base(stream, remoteEndPoint, localEndPoint, options) { - _stream = stream; - } - - protected override async Task StartInputPipeTask(IObjectPipe packagePipe, - CancellationToken cancellationToken) - { - await _stream.OpenStreamAsync(cancellationToken); - await base.StartInputPipeTask(packagePipe, cancellationToken); } protected override bool IsIgnorableException(Exception e) @@ -49,4 +39,44 @@ protected override bool IsIgnorableException(Exception e) } } } + + // public class QuicPipeConnection : StreamPipeConnection + // { + // private readonly QuicPipeStream _stream; + // + // public QuicPipeConnection(QuicPipeStream stream, EndPoint remoteEndPoint, ConnectionOptions options) + // : this(stream, remoteEndPoint, null, options) + // { + // _stream = stream; + // } + // + // public QuicPipeConnection(QuicPipeStream stream, EndPoint remoteEndPoint, EndPoint localEndPoint, + // ConnectionOptions options) + // : base(stream, remoteEndPoint, localEndPoint, options) + // { + // _stream = stream; + // } + // + // protected override async Task StartInputPipeTask(IObjectPipe packagePipe, + // CancellationToken cancellationToken) + // { + // await _stream.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; + // } + // } + // } } \ No newline at end of file diff --git a/src/SuperSocket.Quic/QuicConnectionFactory.cs b/src/SuperSocket.Quic/QuicConnectionFactory.cs index 437fd5036..8a519e036 100644 --- a/src/SuperSocket.Quic/QuicConnectionFactory.cs +++ b/src/SuperSocket.Quic/QuicConnectionFactory.cs @@ -15,6 +15,7 @@ internal class QuicConnectionFactory : IConnectionFactory { private readonly ListenOptions _listenOptions; private readonly ConnectionOptions _connectionOptions; + public QuicConnectionFactory(ListenOptions listenOptions, ConnectionOptions connectionOptions) { _listenOptions = listenOptions; @@ -25,12 +26,15 @@ public async Task CreateConnection(object connection, CancellationT { var quicConnection = connection as QuicConnection; - var quicStream = new QuicPipeStream(quicConnection, new QuicStreamOptions - { - ServerStream = true, - }); + var quicStream = await quicConnection.AcceptInboundStreamAsync(CancellationToken.None); + + // var quicStream = new QuicPipeStream(quicConnection, new QuicStreamOptions + // { + // ServerStream = true, + // }); - return new QuicPipeConnection(quicStream, quicConnection.RemoteEndPoint, quicConnection.LocalEndPoint, _connectionOptions); + return new QuicPipeConnection(quicStream, quicConnection.RemoteEndPoint, quicConnection.LocalEndPoint, + _connectionOptions); } } -} +} \ No newline at end of file From 2d1fe329d5b47a1aec4a7768454159bfa7beef5d Mon Sep 17 00:00:00 2001 From: mockingjay <8400684@qq.com> Date: Fri, 3 May 2024 19:10:06 +0800 Subject: [PATCH 4/8] Revert "fix bugs" This reverts commit 95ae5983f2ead1c80d950e86fa7e59d805683d1f. --- .../QuicPipeConnection.cs | 56 +++++-------------- src/SuperSocket.Quic/QuicConnectionFactory.cs | 16 ++---- 2 files changed, 19 insertions(+), 53 deletions(-) diff --git a/src/SuperSocket.Quic.Connection/QuicPipeConnection.cs b/src/SuperSocket.Quic.Connection/QuicPipeConnection.cs index 515318b4b..56e3f2772 100644 --- a/src/SuperSocket.Quic.Connection/QuicPipeConnection.cs +++ b/src/SuperSocket.Quic.Connection/QuicPipeConnection.cs @@ -1,5 +1,4 @@ using System; -using System.IO; using System.Net; using System.Net.Quic; using System.Net.Sockets; @@ -13,15 +12,26 @@ namespace SuperSocket.Quic.Connection { public class QuicPipeConnection : StreamPipeConnection { - public QuicPipeConnection(Stream stream, EndPoint remoteEndPoint, ConnectionOptions options) + private readonly QuicPipeStream _stream; + + public QuicPipeConnection(QuicPipeStream stream, EndPoint remoteEndPoint, ConnectionOptions options) : this(stream, remoteEndPoint, null, options) { + _stream = stream; } - public QuicPipeConnection(Stream stream, EndPoint remoteEndPoint, EndPoint localEndPoint, + public QuicPipeConnection(QuicPipeStream stream, EndPoint remoteEndPoint, EndPoint localEndPoint, ConnectionOptions options) : base(stream, remoteEndPoint, localEndPoint, options) { + _stream = stream; + } + + protected override async Task StartInputPipeTask(IObjectPipe packagePipe, + CancellationToken cancellationToken) + { + await _stream.OpenStreamAsync(cancellationToken); + await base.StartInputPipeTask(packagePipe, cancellationToken); } protected override bool IsIgnorableException(Exception e) @@ -39,44 +49,4 @@ protected override bool IsIgnorableException(Exception e) } } } - - // public class QuicPipeConnection : StreamPipeConnection - // { - // private readonly QuicPipeStream _stream; - // - // public QuicPipeConnection(QuicPipeStream stream, EndPoint remoteEndPoint, ConnectionOptions options) - // : this(stream, remoteEndPoint, null, options) - // { - // _stream = stream; - // } - // - // public QuicPipeConnection(QuicPipeStream stream, EndPoint remoteEndPoint, EndPoint localEndPoint, - // ConnectionOptions options) - // : base(stream, remoteEndPoint, localEndPoint, options) - // { - // _stream = stream; - // } - // - // protected override async Task StartInputPipeTask(IObjectPipe packagePipe, - // CancellationToken cancellationToken) - // { - // await _stream.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; - // } - // } - // } } \ No newline at end of file diff --git a/src/SuperSocket.Quic/QuicConnectionFactory.cs b/src/SuperSocket.Quic/QuicConnectionFactory.cs index 8a519e036..437fd5036 100644 --- a/src/SuperSocket.Quic/QuicConnectionFactory.cs +++ b/src/SuperSocket.Quic/QuicConnectionFactory.cs @@ -15,7 +15,6 @@ internal class QuicConnectionFactory : IConnectionFactory { private readonly ListenOptions _listenOptions; private readonly ConnectionOptions _connectionOptions; - public QuicConnectionFactory(ListenOptions listenOptions, ConnectionOptions connectionOptions) { _listenOptions = listenOptions; @@ -26,15 +25,12 @@ public async Task CreateConnection(object connection, CancellationT { var quicConnection = connection as QuicConnection; - var quicStream = await quicConnection.AcceptInboundStreamAsync(CancellationToken.None); - - // var quicStream = new QuicPipeStream(quicConnection, new QuicStreamOptions - // { - // ServerStream = true, - // }); + var quicStream = new QuicPipeStream(quicConnection, new QuicStreamOptions + { + ServerStream = true, + }); - return new QuicPipeConnection(quicStream, quicConnection.RemoteEndPoint, quicConnection.LocalEndPoint, - _connectionOptions); + return new QuicPipeConnection(quicStream, quicConnection.RemoteEndPoint, quicConnection.LocalEndPoint, _connectionOptions); } } -} \ No newline at end of file +} From 49b83caa40f7669085dc9ab48ae8f81eb480cb95 Mon Sep 17 00:00:00 2001 From: mockingjay <8400684@qq.com> Date: Fri, 3 May 2024 19:12:59 +0800 Subject: [PATCH 5/8] fix bugs --- .../QuicPipeConnection.cs | 11 +++++++---- src/SuperSocket.Quic.Connection/QuicPipeStream.cs | 13 ++----------- .../QuicStreamOptions.cs | 12 ------------ src/SuperSocket.Quic/QuicConnectionFactory.cs | 5 +---- test/SuperSocket.Tests/QuicHostConfigurator.cs | 7 ++----- 5 files changed, 12 insertions(+), 36 deletions(-) delete mode 100644 src/SuperSocket.Quic.Connection/QuicStreamOptions.cs diff --git a/src/SuperSocket.Quic.Connection/QuicPipeConnection.cs b/src/SuperSocket.Quic.Connection/QuicPipeConnection.cs index 56e3f2772..7d76bcf86 100644 --- a/src/SuperSocket.Quic.Connection/QuicPipeConnection.cs +++ b/src/SuperSocket.Quic.Connection/QuicPipeConnection.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Net; using System.Net.Quic; using System.Net.Sockets; @@ -12,15 +13,15 @@ namespace SuperSocket.Quic.Connection { public class QuicPipeConnection : StreamPipeConnection { - private readonly QuicPipeStream _stream; + private readonly Stream _stream; - public QuicPipeConnection(QuicPipeStream stream, EndPoint remoteEndPoint, ConnectionOptions options) + public QuicPipeConnection(Stream stream, EndPoint remoteEndPoint, ConnectionOptions options) : this(stream, remoteEndPoint, null, options) { _stream = stream; } - public QuicPipeConnection(QuicPipeStream stream, EndPoint remoteEndPoint, EndPoint localEndPoint, + public QuicPipeConnection(Stream stream, EndPoint remoteEndPoint, EndPoint localEndPoint, ConnectionOptions options) : base(stream, remoteEndPoint, localEndPoint, options) { @@ -30,7 +31,9 @@ public QuicPipeConnection(QuicPipeStream stream, EndPoint remoteEndPoint, EndPoi protected override async Task StartInputPipeTask(IObjectPipe packagePipe, CancellationToken cancellationToken) { - await _stream.OpenStreamAsync(cancellationToken); + if (_stream is QuicPipeStream quicPipeStream) + await quicPipeStream.OpenStreamAsync(cancellationToken); + await base.StartInputPipeTask(packagePipe, cancellationToken); } diff --git a/src/SuperSocket.Quic.Connection/QuicPipeStream.cs b/src/SuperSocket.Quic.Connection/QuicPipeStream.cs index f8dee1eea..b1bb6ecd5 100644 --- a/src/SuperSocket.Quic.Connection/QuicPipeStream.cs +++ b/src/SuperSocket.Quic.Connection/QuicPipeStream.cs @@ -12,13 +12,11 @@ public sealed class QuicPipeStream : Stream { private Stream _stream; - private readonly QuicStreamOptions _serverOptions; private readonly QuicConnection _connection; - public QuicPipeStream(QuicConnection connection, QuicStreamOptions streamOptions) + public QuicPipeStream(QuicConnection connection) { _connection = connection; - _serverOptions = streamOptions; } public override bool CanRead => _stream.CanRead; @@ -34,14 +32,7 @@ public override long Position public async ValueTask OpenStreamAsync(CancellationToken cancellationToken) { - if (_serverOptions.ServerStream) - { - _stream = await _connection.AcceptInboundStreamAsync(cancellationToken); - } - else - { - _stream = await _connection.OpenOutboundStreamAsync(_serverOptions.StreamType, cancellationToken); - } + _stream = await _connection.AcceptInboundStreamAsync(cancellationToken); } public override void Flush() => _stream.Flush(); diff --git a/src/SuperSocket.Quic.Connection/QuicStreamOptions.cs b/src/SuperSocket.Quic.Connection/QuicStreamOptions.cs deleted file mode 100644 index c497fc8ab..000000000 --- a/src/SuperSocket.Quic.Connection/QuicStreamOptions.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Net.Quic; - -#pragma warning disable CA2252 -namespace SuperSocket.Quic.Connection -{ - public sealed class QuicStreamOptions - { - public bool ServerStream { get; set; } - - public QuicStreamType StreamType { get; set; } - } -} \ No newline at end of file diff --git a/src/SuperSocket.Quic/QuicConnectionFactory.cs b/src/SuperSocket.Quic/QuicConnectionFactory.cs index 437fd5036..89629a283 100644 --- a/src/SuperSocket.Quic/QuicConnectionFactory.cs +++ b/src/SuperSocket.Quic/QuicConnectionFactory.cs @@ -25,10 +25,7 @@ public async Task CreateConnection(object connection, CancellationT { var quicConnection = connection as QuicConnection; - var quicStream = new QuicPipeStream(quicConnection, new QuicStreamOptions - { - ServerStream = true, - }); + var quicStream = new QuicPipeStream(quicConnection); return new QuicPipeConnection(quicStream, quicConnection.RemoteEndPoint, quicConnection.LocalEndPoint, _connectionOptions); } diff --git a/test/SuperSocket.Tests/QuicHostConfigurator.cs b/test/SuperSocket.Tests/QuicHostConfigurator.cs index f4cb28bca..7eb847541 100644 --- a/test/SuperSocket.Tests/QuicHostConfigurator.cs +++ b/test/SuperSocket.Tests/QuicHostConfigurator.cs @@ -125,11 +125,8 @@ protected override async ValueTask ConnectAsync(EndPoint remoteEndPoint, return false; } - var quicStream = new QuicPipeStream(quicConnection, new QuicStreamOptions - { - ServerStream = false, - StreamType = QuicStreamType.Bidirectional, - }); + var quicStream = + await quicConnection.OpenOutboundStreamAsync(QuicStreamType.Bidirectional, cancellationToken); var connection = new QuicPipeConnection(quicStream, quicConnection.RemoteEndPoint, quicConnection.LocalEndPoint, Options); From 564e88f728eb3575a713bb6f6a80df00dbb6f8e9 Mon Sep 17 00:00:00 2001 From: mockingjay <8400684@qq.com> Date: Fri, 3 May 2024 19:17:54 +0800 Subject: [PATCH 6/8] fix bugs --- src/SuperSocket.Quic/QuicConnectionFactory.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/SuperSocket.Quic/QuicConnectionFactory.cs b/src/SuperSocket.Quic/QuicConnectionFactory.cs index 89629a283..7fe4a4012 100644 --- a/src/SuperSocket.Quic/QuicConnectionFactory.cs +++ b/src/SuperSocket.Quic/QuicConnectionFactory.cs @@ -1,12 +1,9 @@ -using System.Collections.Generic; -using System.IO; using System.Net.Quic; using System.Threading; using System.Threading.Tasks; using SuperSocket.Connection; using SuperSocket.Quic.Connection; using SuperSocket.Server.Abstractions; -using SuperSocket.Server.Abstractions.Connections; #pragma warning disable CA2252 namespace SuperSocket.Quic @@ -21,13 +18,15 @@ public QuicConnectionFactory(ListenOptions listenOptions, ConnectionOptions conn _connectionOptions = connectionOptions; } - public async Task CreateConnection(object connection, CancellationToken cancellationToken) + public Task CreateConnection(object connection, CancellationToken cancellationToken) { - var quicConnection = connection as QuicConnection; + var quicConnection = (QuicConnection)connection; var quicStream = new QuicPipeStream(quicConnection); - return new QuicPipeConnection(quicStream, quicConnection.RemoteEndPoint, quicConnection.LocalEndPoint, _connectionOptions); + var pipeConnection = new QuicPipeConnection(quicStream, quicConnection.RemoteEndPoint, quicConnection.LocalEndPoint, _connectionOptions); + + return Task.FromResult(pipeConnection); } } } From e7356eb629a30dacaacbeb5d4d4b574c311c12d3 Mon Sep 17 00:00:00 2001 From: mockingjay <8400684@qq.com> Date: Fri, 3 May 2024 23:02:50 +0800 Subject: [PATCH 7/8] fix bugs --- .../QuicConnectionListener.cs | 37 +++++++++---------- .../QuicConnectionListenerFactory.cs | 14 +++++-- .../QuicServerHostBuilderExtensions.cs | 19 ++++++++-- src/SuperSocket.Quic/QuicTransportOptions.cs | 21 +++++------ .../SuperSocket.Tests/QuicHostConfigurator.cs | 10 ++--- 5 files changed, 57 insertions(+), 44 deletions(-) diff --git a/src/SuperSocket.Quic/QuicConnectionListener.cs b/src/SuperSocket.Quic/QuicConnectionListener.cs index 86916c9ee..3cd766283 100644 --- a/src/SuperSocket.Quic/QuicConnectionListener.cs +++ b/src/SuperSocket.Quic/QuicConnectionListener.cs @@ -15,6 +15,7 @@ namespace SuperSocket.Quic internal sealed class QuicConnectionListener : IConnectionListener { private readonly ILogger _logger; + private readonly QuicTransportOptions _quicTransportOptions; private QuicListener _listenQuic; private CancellationTokenSource _cancellationTokenSource; @@ -23,11 +24,14 @@ internal sealed class QuicConnectionListener : IConnectionListener public ListenOptions Options { get; } public bool IsRunning { get; private set; } - public QuicConnectionListener(ListenOptions options, IConnectionFactory connectionFactory, ILogger logger) + public QuicConnectionListener(ListenOptions options, + QuicTransportOptions quicTransportOptions, + IConnectionFactory connectionFactory, ILogger logger) { Options = options; ConnectionFactory = connectionFactory; _logger = logger; + _quicTransportOptions = quicTransportOptions; } public bool Start() @@ -38,24 +42,27 @@ public bool Start() { var listenEndpoint = options.ToEndPoint(); - ArgumentNullException.ThrowIfNull(options.CertificateOptions); + 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() + new QuicServerConnectionOptions { - DefaultStreamErrorCode = 0, - DefaultCloseErrorCode = 0, - IdleTimeout = TimeSpan.FromMicroseconds(10), - //MaxInboundBidirectionalStreams = 0, - //MaxInboundUnidirectionalStreams = 0, + 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 = @@ -67,14 +74,7 @@ public bool Start() }) }; - var result = QuicListener.ListenAsync(quicListenerOptions); - - if (result.IsCompleted) - _listenQuic = result.Result; - else - _listenQuic = result.GetAwaiter().GetResult(); - - var listenSocket = _listenQuic; + var listenSocket = QuicListener.ListenAsync(quicListenerOptions).GetAwaiter().GetResult(); IsRunning = true; @@ -92,7 +92,6 @@ public bool Start() private async Task KeepAcceptAsync(QuicListener listenSocket, CancellationToken cancellationToken) - { while (!cancellationToken.IsCancellationRequested) { @@ -156,4 +155,4 @@ 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 index 2f3326098..e3dec8448 100644 --- a/src/SuperSocket.Quic/QuicConnectionListenerFactory.cs +++ b/src/SuperSocket.Quic/QuicConnectionListenerFactory.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using SuperSocket.Connection; using SuperSocket.Server.Abstractions; using SuperSocket.Server.Abstractions.Connections; @@ -7,21 +8,26 @@ namespace SuperSocket.Quic { internal class QuicConnectionListenerFactory : IConnectionListenerFactory { + private readonly QuicTransportOptions _quicTransportOptions; private readonly IConnectionFactoryBuilder _connectionFactoryBuilder; - public QuicConnectionListenerFactory(IConnectionFactoryBuilder connectionFactoryBuilder) + public QuicConnectionListenerFactory(IConnectionFactoryBuilder connectionFactoryBuilder, + IOptions options) { _connectionFactoryBuilder = connectionFactoryBuilder; + _quicTransportOptions = options.Value; } - - public IConnectionListener CreateConnectionListener(ListenOptions options, ConnectionOptions connectionOptions, ILoggerFactory loggerFactory) + + 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); - return new QuicConnectionListener(options, connectionFactory, connectionFactoryLogger); + 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 index bb6c0f674..008f5d3fe 100644 --- a/src/SuperSocket.Quic/QuicServerHostBuilderExtensions.cs +++ b/src/SuperSocket.Quic/QuicServerHostBuilderExtensions.cs @@ -4,6 +4,7 @@ using SuperSocket.Server.Abstractions.Connections; using SuperSocket.Server.Abstractions.Host; using SuperSocket.Quic; + #pragma warning disable CA2252 namespace SuperSocket.Server @@ -11,20 +12,30 @@ namespace SuperSocket.Server public static class QuicServerHostBuilderExtensions { public static ISuperSocketHostBuilder UseQuic(this ISuperSocketHostBuilder hostBuilder) + { + return (hostBuilder as ISuperSocketHostBuilder).UseQuic(o => { }) 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; } - public static ISuperSocketHostBuilder UseQuic(this ISuperSocketHostBuilder hostBuilder) + public static ISuperSocketHostBuilder UseQuic( + this ISuperSocketHostBuilder hostBuilder) { - return (hostBuilder as ISuperSocketHostBuilder).UseQuic() as ISuperSocketHostBuilder; + return (hostBuilder as ISuperSocketHostBuilder).UseQuic(o => { }) as + ISuperSocketHostBuilder; } } -} +} \ No newline at end of file diff --git a/src/SuperSocket.Quic/QuicTransportOptions.cs b/src/SuperSocket.Quic/QuicTransportOptions.cs index 9857b9378..6793c8f58 100644 --- a/src/SuperSocket.Quic/QuicTransportOptions.cs +++ b/src/SuperSocket.Quic/QuicTransportOptions.cs @@ -8,21 +8,20 @@ public sealed class QuicTransportOptions /// The maximum number of concurrent inbound uni-directional streams per connection. /// public int MaxUnidirectionalStreamCount { get; set; } = 10; - - /// The maximum read size. - public long MaxReadBufferSize { get; set; } = 1048576L; - - /// The maximum write size. - public long MaxWriteBufferSize { get; set; } = 65536L; - - /// The maximum length of the pending connection queue. - public int Backlog { get; set; } = 512; - + /// /// 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. + /// + /// 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/test/SuperSocket.Tests/QuicHostConfigurator.cs b/test/SuperSocket.Tests/QuicHostConfigurator.cs index 7eb847541..5aef63a38 100644 --- a/test/SuperSocket.Tests/QuicHostConfigurator.cs +++ b/test/SuperSocket.Tests/QuicHostConfigurator.cs @@ -21,10 +21,10 @@ using SuperSocket.Quic; using SuperSocket.Quic.Connection; +#pragma warning disable CA2252 +#if NET7_0_OR_GREATER namespace SuperSocket.Tests { -#if NET7_0_OR_GREATER - public class QuicHostConfigurator : IHostConfigurator { private static readonly ArrayPool _bufferPool = ArrayPool.Shared; @@ -100,7 +100,6 @@ public QuicClient(IPipelineFilter pipelineFilter, ConnectionOpt protected override async ValueTask ConnectAsync(EndPoint remoteEndPoint, CancellationToken cancellationToken) { -#pragma warning disable CA2252 var quicConnection = await QuicConnection.ConnectAsync( cancellationToken: cancellationToken, options: new QuicClientConnectionOptions @@ -147,6 +146,5 @@ public Socket CreateClient() throw new NotImplementedException(); } } - -#endif -} \ No newline at end of file +} +#endif \ No newline at end of file From 6d162737c8c8489fadf6b4f7fdbbc6aeac58b274 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 11:52:30 +0800 Subject: [PATCH 8/8] fix bugs --- .../PipeConnectionBase.cs | 3 +- .../QuicPipeConnection.cs | 63 +++-- src/SuperSocket.Quic/QuicConnectionFactory.cs | 33 ++- .../QuicConnectionFactoryBuilder.cs | 11 +- .../QuicConnectionListener.cs | 229 +++++++++--------- .../QuicConnectionListenerFactory.cs | 39 ++- .../QuicServerHostBuilderExtensions.cs | 53 ++-- test/SuperSocket.Tests/ClientTest.cs | 31 ++- .../SuperSocket.Tests/QuicHostConfigurator.cs | 1 + 9 files changed, 229 insertions(+), 234 deletions(-) diff --git a/src/SuperSocket.Connection/PipeConnectionBase.cs b/src/SuperSocket.Connection/PipeConnectionBase.cs index 6601420d2..cba380d7a 100644 --- a/src/SuperSocket.Connection/PipeConnectionBase.cs +++ b/src/SuperSocket.Connection/PipeConnectionBase.cs @@ -97,7 +97,8 @@ private async ValueTask HandleClosing() { try { - await _pipeTask.ConfigureAwait(false); + if (_pipeTask != null) + await _pipeTask.ConfigureAwait(false); } catch (OperationCanceledException) { diff --git a/src/SuperSocket.Quic.Connection/QuicPipeConnection.cs b/src/SuperSocket.Quic.Connection/QuicPipeConnection.cs index 7d76bcf86..ae5169220 100644 --- a/src/SuperSocket.Quic.Connection/QuicPipeConnection.cs +++ b/src/SuperSocket.Quic.Connection/QuicPipeConnection.cs @@ -9,47 +9,44 @@ #pragma warning disable CA2252 -namespace SuperSocket.Quic.Connection +namespace SuperSocket.Quic.Connection; + +public class QuicPipeConnection : StreamPipeConnection { - public class QuicPipeConnection : StreamPipeConnection + private readonly Stream _stream; + + public QuicPipeConnection(Stream stream, EndPoint remoteEndPoint, ConnectionOptions options) + : this(stream, remoteEndPoint, null, options) { - private readonly Stream _stream; + _stream = stream; + } - public QuicPipeConnection(Stream stream, EndPoint remoteEndPoint, ConnectionOptions options) - : this(stream, remoteEndPoint, null, options) - { - _stream = stream; - } + public QuicPipeConnection(Stream stream, EndPoint remoteEndPoint, EndPoint localEndPoint, ConnectionOptions options) + : base(stream, remoteEndPoint, localEndPoint, options) + { + _stream = stream; + } - public QuicPipeConnection(Stream stream, EndPoint remoteEndPoint, EndPoint localEndPoint, - ConnectionOptions options) - : base(stream, remoteEndPoint, localEndPoint, options) - { - _stream = stream; - } + protected override async Task StartInputPipeTask(IObjectPipe packagePipe, CancellationToken cancellationToken) + { + if (_stream is QuicPipeStream quicPipeStream) + await quicPipeStream.OpenStreamAsync(cancellationToken); - protected override async Task StartInputPipeTask(IObjectPipe packagePipe, - CancellationToken cancellationToken) - { - if (_stream is QuicPipeStream quicPipeStream) - await quicPipeStream.OpenStreamAsync(cancellationToken); - - await base.StartInputPipeTask(packagePipe, cancellationToken); - } + await base.StartInputPipeTask(packagePipe, cancellationToken); + } + + protected override bool IsIgnorableException(Exception e) + { + if (base.IsIgnorableException(e)) + return true; - protected override bool IsIgnorableException(Exception e) + switch (e) { - if (base.IsIgnorableException(e)) + case QuicException: + case SocketException se when se.IsIgnorableSocketException(): return true; - - switch (e) - { - case QuicException: - case SocketException se when se.IsIgnorableSocketException(): - return true; - default: - return false; - } + default: + return false; } } } \ No newline at end of file diff --git a/src/SuperSocket.Quic/QuicConnectionFactory.cs b/src/SuperSocket.Quic/QuicConnectionFactory.cs index 7fe4a4012..cea1cefbb 100644 --- a/src/SuperSocket.Quic/QuicConnectionFactory.cs +++ b/src/SuperSocket.Quic/QuicConnectionFactory.cs @@ -3,30 +3,27 @@ using System.Threading.Tasks; using SuperSocket.Connection; using SuperSocket.Quic.Connection; -using SuperSocket.Server.Abstractions; #pragma warning disable CA2252 -namespace SuperSocket.Quic +namespace SuperSocket.Quic; + +internal class QuicConnectionFactory : IConnectionFactory { - internal class QuicConnectionFactory : IConnectionFactory + private readonly ConnectionOptions _connectionOptions; + + public QuicConnectionFactory(ConnectionOptions connectionOptions) { - private readonly ListenOptions _listenOptions; - private readonly ConnectionOptions _connectionOptions; - public QuicConnectionFactory(ListenOptions listenOptions, ConnectionOptions connectionOptions) - { - _listenOptions = listenOptions; - _connectionOptions = connectionOptions; - } + _connectionOptions = connectionOptions; + } - public Task CreateConnection(object connection, CancellationToken cancellationToken) - { - var quicConnection = (QuicConnection)connection; + public Task CreateConnection(object connection, CancellationToken cancellationToken) + { + var quicConnection = (QuicConnection)connection; - var quicStream = new QuicPipeStream(quicConnection); + var quicStream = new QuicPipeStream(quicConnection); - var pipeConnection = new QuicPipeConnection(quicStream, quicConnection.RemoteEndPoint, quicConnection.LocalEndPoint, _connectionOptions); - - return Task.FromResult(pipeConnection); - } + var pipeConnection = new QuicPipeConnection(quicStream, quicConnection.RemoteEndPoint, quicConnection.LocalEndPoint, _connectionOptions); + + return Task.FromResult(pipeConnection); } } diff --git a/src/SuperSocket.Quic/QuicConnectionFactoryBuilder.cs b/src/SuperSocket.Quic/QuicConnectionFactoryBuilder.cs index ee4df7c9f..7ca1e740c 100644 --- a/src/SuperSocket.Quic/QuicConnectionFactoryBuilder.cs +++ b/src/SuperSocket.Quic/QuicConnectionFactoryBuilder.cs @@ -2,13 +2,12 @@ using SuperSocket.Server.Abstractions; using SuperSocket.Server.Abstractions.Connections; -namespace SuperSocket.Quic +namespace SuperSocket.Quic; + +internal class QuicConnectionFactoryBuilder : IConnectionFactoryBuilder { - internal class QuicConnectionFactoryBuilder : IConnectionFactoryBuilder + public IConnectionFactory Build(ListenOptions listenOptions, ConnectionOptions connectionOptions) { - public IConnectionFactory Build(ListenOptions listenOptions, ConnectionOptions connectionOptions) - { - return new QuicConnectionFactory(listenOptions, 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 index 3cd766283..ded0514f2 100644 --- a/src/SuperSocket.Quic/QuicConnectionListener.cs +++ b/src/SuperSocket.Quic/QuicConnectionListener.cs @@ -10,149 +10,148 @@ using System.Net.Security; #pragma warning disable CA2252 -namespace SuperSocket.Quic +namespace SuperSocket.Quic; + +internal sealed class QuicConnectionListener : IConnectionListener { - 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) { - 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; - } + Options = options; + ConnectionFactory = connectionFactory; + _logger = logger; + _quicTransportOptions = quicTransportOptions; + } + + public bool Start() + { + var options = Options; - public bool Start() + try { - var options = Options; + var listenEndpoint = options.ToEndPoint(); - try + 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 { - 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 + 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() { - 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 = 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; - } - } - + ApplicationProtocols = + new List { SslApplicationProtocol.Http3 }, + ServerCertificate = options.CertificateOptions.Certificate, + RemoteCertificateValidationCallback = + options.CertificateOptions.RemoteCertificateValidationCallback, + } + }) + }; - 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"); - } - } + var listenSocket = _listenQuic = QuicListener.ListenAsync(quicListenerOptions).GetAwaiter().GetResult(); - _stopTaskCompletionSource.TrySetResult(true); - } + IsRunning = true; - public event NewConnectionAcceptHandler NewConnectionAccept; + _cancellationTokenSource = new CancellationTokenSource(); - private async void OnNewClientAccept(QuicConnection quicConnection, CancellationToken cancellationToken) + KeepAcceptAsync(listenSocket, _cancellationTokenSource.Token).DoNotAwait(); + return true; + } + catch (Exception e) { - var handler = NewConnectionAccept; - - if (handler == null) - return; + _logger.LogError(e, $"The listener[{this.ToString()}] failed to start."); + return false; + } + } - IConnection connection = null; + private async Task KeepAcceptAsync(QuicListener listenSocket, CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { try { - using var cts = CancellationTokenSourcePool.Shared.Rent(Options.ConnectionAcceptTimeOut); - connection = await ConnectionFactory.CreateConnection(quicConnection, cts.Token); + var quicConnection = + await listenSocket.AcceptConnectionAsync(cancellationToken).ConfigureAwait(false); + OnNewClientAccept(quicConnection, cancellationToken); } catch (Exception e) { - _logger.LogError(e, $"Failed to create quicConnection for {quicConnection.RemoteEndPoint}."); - return; + _logger.LogError(e, $"Listener[{this.ToString()}] failed to do AcceptAsync"); } - - await handler.Invoke(this.Options, connection); } - public async Task StopAsync() - { - var listenSocket = _listenQuic; + _stopTaskCompletionSource.TrySetResult(true); + } - if (listenSocket == null) - return; + public event NewConnectionAcceptHandler NewConnectionAccept; - _stopTaskCompletionSource = new TaskCompletionSource(); + private async void OnNewClientAccept(QuicConnection quicConnection, CancellationToken cancellationToken) + { + var handler = NewConnectionAccept; - _cancellationTokenSource.Cancel(); - await _listenQuic.DisposeAsync(); + if (handler == null) + return; - await _stopTaskCompletionSource.Task; - } + IConnection connection = null; - public override string ToString() + try { - return Options?.ToString(); + 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 index e3dec8448..98242a9c6 100644 --- a/src/SuperSocket.Quic/QuicConnectionListenerFactory.cs +++ b/src/SuperSocket.Quic/QuicConnectionListenerFactory.cs @@ -4,30 +4,29 @@ using SuperSocket.Server.Abstractions; using SuperSocket.Server.Abstractions.Connections; -namespace SuperSocket.Quic +namespace SuperSocket.Quic; + +internal class QuicConnectionListenerFactory : IConnectionListenerFactory { - internal class QuicConnectionListenerFactory : IConnectionListenerFactory - { - private readonly QuicTransportOptions _quicTransportOptions; - private readonly IConnectionFactoryBuilder _connectionFactoryBuilder; + private readonly QuicTransportOptions _quicTransportOptions; + private readonly IConnectionFactoryBuilder _connectionFactoryBuilder; - public QuicConnectionListenerFactory(IConnectionFactoryBuilder connectionFactoryBuilder, - IOptions options) - { - _connectionFactoryBuilder = connectionFactoryBuilder; - _quicTransportOptions = options.Value; - } + 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)); + 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 connectionFactory = _connectionFactoryBuilder.Build(options, connectionOptions); - return new QuicConnectionListener(options, _quicTransportOptions, connectionFactory, - connectionFactoryLogger); - } + 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 index 008f5d3fe..a931c0a37 100644 --- a/src/SuperSocket.Quic/QuicServerHostBuilderExtensions.cs +++ b/src/SuperSocket.Quic/QuicServerHostBuilderExtensions.cs @@ -7,35 +7,38 @@ #pragma warning disable CA2252 -namespace SuperSocket.Server +namespace SuperSocket.Server; + +public static class QuicServerHostBuilderExtensions { - public static class QuicServerHostBuilderExtensions + public static ISuperSocketHostBuilder UseQuic(this ISuperSocketHostBuilder hostBuilder) { - public static ISuperSocketHostBuilder UseQuic(this ISuperSocketHostBuilder hostBuilder) - { - return (hostBuilder as ISuperSocketHostBuilder).UseQuic(o => { }) 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.UseQuic(o => { }); + } - return hostBuilder.ConfigureServices((_, services) => - { - services.Configure(globalConfigure); - services.AddSingleton(); - services.AddSingleton(); - }) as ISuperSocketHostBuilder; - } + 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."); - public static ISuperSocketHostBuilder UseQuic( - this ISuperSocketHostBuilder hostBuilder) + return hostBuilder.ConfigureServices((_, services) => { - return (hostBuilder as ISuperSocketHostBuilder).UseQuic(o => { }) as - ISuperSocketHostBuilder; - } + services.Configure(globalConfigure); + services.AddSingleton(); + services.AddSingleton(); + }) as ISuperSocketHostBuilder; } } \ No newline at end of file diff --git a/test/SuperSocket.Tests/ClientTest.cs b/test/SuperSocket.Tests/ClientTest.cs index 0f1103425..f9707aa0a 100644 --- a/test/SuperSocket.Tests/ClientTest.cs +++ b/test/SuperSocket.Tests/ClientTest.cs @@ -23,13 +23,13 @@ namespace SuperSocket.Tests public class ClientTest : TestClassBase { private static Random _rd = new Random(); - + public ClientTest(ITestOutputHelper outputHelper) : base(outputHelper) { } - + [Theory] [Trait("Category", "Client.TestEcho")] [InlineData(typeof(RegularHostConfigurator), false)] @@ -72,11 +72,11 @@ public async Task TestEcho(Type hostConfiguratorType, bool clientReadAsDemand) Logger = NullLogger.Instance, ReadAsDemand = clientReadAsDemand }; - + var client = hostConfigurator.ConfigureEasyClient(new LinePipelineFilter(), options); var connected = await client.ConnectAsync(new IPEndPoint(IPAddress.Loopback, hostConfigurator.Listener.Port)); - + Assert.True(connected); Assert.True(serverSessionEvent.WaitOne(1000)); @@ -88,7 +88,7 @@ public async Task TestEcho(Type hostConfiguratorType, bool clientReadAsDemand) var package = await client.ReceiveAsync(); Assert.NotNull(package); - Assert.Equal(msg, package.Text); + Assert.Equal(msg, package.Text); } await client.CloseAsync(); @@ -105,7 +105,6 @@ public async Task TestEcho(Type hostConfiguratorType, bool clientReadAsDemand) public async Task TestBindLocalEndPoint(Type hostConfiguratorType) { IAppSession session = default; - var hostConfigurator = CreateObject(hostConfiguratorType); using (var server = CreateSocketServerBuilder(hostConfigurator) .UseSessionHandler(async s => @@ -119,7 +118,7 @@ public async Task TestBindLocalEndPoint(Type hostConfiguratorType) Assert.Equal("TestServer", server.Name); Assert.True(await server.StartAsync()); - OutputHelper.WriteLine("Server started."); + OutputHelper.WriteLine("Server started."); var pipelineFilter = new CommandLinePipelineFilter { @@ -130,11 +129,11 @@ public async Task TestBindLocalEndPoint(Type hostConfiguratorType) { Logger = DefaultLoggerFactory.CreateLogger(nameof(TestBindLocalEndPoint)) }; - + var client = hostConfigurator.ConfigureEasyClient(pipelineFilter, options); var connected = false; var localPort = 0; - + for (var i = 0; i < 3; i++) { localPort = _rd.Next(40000, 50000); @@ -148,16 +147,16 @@ public async Task TestBindLocalEndPoint(Type hostConfiguratorType) { if (e.SocketErrorCode == SocketError.AccessDenied || e.SocketErrorCode == SocketError.AddressAlreadyInUse) continue; - + throw e; } - break; - } - + break; + } + Assert.True(connected); - await Task.Delay(500); + await Task.Delay(5000); Assert.NotNull(session); Assert.Equal(localPort, (session.RemoteEndPoint as IPEndPoint).Port); @@ -238,7 +237,7 @@ public async Task TestCommandLine(Type hostConfiguratorType) }; var connected = await client.ConnectAsync(new IPEndPoint(IPAddress.Loopback, hostConfigurator.Listener.Port)); - + Assert.True(connected); client.StartReceive(); @@ -325,7 +324,7 @@ async Task TestDetachableChannelInternal(IHostConfigurator hostConfigurator, Fun connection = connectionFactory(server, socket); await TestConnection(connection); - } + } await server.StopAsync(); } diff --git a/test/SuperSocket.Tests/QuicHostConfigurator.cs b/test/SuperSocket.Tests/QuicHostConfigurator.cs index 5aef63a38..2da44626d 100644 --- a/test/SuperSocket.Tests/QuicHostConfigurator.cs +++ b/test/SuperSocket.Tests/QuicHostConfigurator.cs @@ -108,6 +108,7 @@ protected override async ValueTask ConnectAsync(EndPoint remoteEndPoint, DefaultStreamErrorCode = 0, RemoteEndPoint = remoteEndPoint, LocalEndPoint = LocalEndPoint, + IdleTimeout = TimeSpan.Zero, ClientAuthenticationOptions = new SslClientAuthenticationOptions { ApplicationProtocols = new List { SslApplicationProtocol.Http3 },