-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add quic support and fix some bugs #724
Changes from all commits
eb85459
e0ea1a3
95ae598
2d1fe32
49b83ca
564e88f
e7356eb
6d16273
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does it really accept all kinds of Stream or only support QuicStream? |
||
: this(stream, remoteEndPoint, null, 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<TPackageInfo>(IObjectPipe<TPackageInfo> 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; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ignore ca1415 as well? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
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); | ||
Check warning on line 35 in src/SuperSocket.Quic.Connection/QuicPipeStream.cs GitHub Actions / build (ubuntu-latest)
Check warning on line 35 in src/SuperSocket.Quic.Connection/QuicPipeStream.cs GitHub Actions / build (ubuntu-latest)
Check warning on line 35 in src/SuperSocket.Quic.Connection/QuicPipeStream.cs GitHub Actions / build (ubuntu-latest)
Check warning on line 35 in src/SuperSocket.Quic.Connection/QuicPipeStream.cs GitHub Actions / build (windows-latest)
Check warning on line 35 in src/SuperSocket.Quic.Connection/QuicPipeStream.cs GitHub Actions / build (windows-latest)
Check warning on line 35 in src/SuperSocket.Quic.Connection/QuicPipeStream.cs GitHub Actions / build (macos-latest)
Check warning on line 35 in src/SuperSocket.Quic.Connection/QuicPipeStream.cs GitHub Actions / build (macos-latest)
Check warning on line 35 in src/SuperSocket.Quic.Connection/QuicPipeStream.cs GitHub Actions / build (macos-latest)
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's inbound stream, do we need handle the outbound stream? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Inbound Stream seems to be all that's needed on the server |
||
} | ||
|
||
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<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => | ||
_stream.ReadAsync(buffer, offset, count, cancellationToken); | ||
|
||
public override ValueTask<int> ReadAsync(Memory<byte> 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<byte> buffer, | ||
CancellationToken cancellationToken = default) => | ||
_stream.WriteAsync(buffer, cancellationToken); | ||
|
||
public override void Write(ReadOnlySpan<byte> buffer) => _stream.Flush(); | ||
|
||
public override void Write(byte[] buffer, int offset, int count) => _stream.Flush(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<Description>SuperSocket quic connection library.</Description> | ||
<TargetFrameworks>net7.0;net8.0</TargetFrameworks> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\SuperSocket.Connection\SuperSocket.Connection.csproj" /> | ||
</ItemGroup> | ||
|
||
</Project> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<IConnection> CreateConnection(object connection, CancellationToken cancellationToken) | ||
{ | ||
var quicConnection = (QuicConnection)connection; | ||
|
||
var quicStream = new QuicPipeStream(quicConnection); | ||
|
||
var pipeConnection = new QuicPipeConnection(quicStream, quicConnection.RemoteEndPoint, quicConnection.LocalEndPoint, _connectionOptions); | ||
Check warning on line 25 in src/SuperSocket.Quic/QuicConnectionFactory.cs GitHub Actions / build (ubuntu-latest)
Check warning on line 25 in src/SuperSocket.Quic/QuicConnectionFactory.cs GitHub Actions / build (ubuntu-latest)
Check warning on line 25 in src/SuperSocket.Quic/QuicConnectionFactory.cs GitHub Actions / build (ubuntu-latest)
Check warning on line 25 in src/SuperSocket.Quic/QuicConnectionFactory.cs GitHub Actions / build (ubuntu-latest)
Check warning on line 25 in src/SuperSocket.Quic/QuicConnectionFactory.cs GitHub Actions / build (windows-latest)
Check warning on line 25 in src/SuperSocket.Quic/QuicConnectionFactory.cs GitHub Actions / build (windows-latest)
Check warning on line 25 in src/SuperSocket.Quic/QuicConnectionFactory.cs GitHub Actions / build (windows-latest)
Check warning on line 25 in src/SuperSocket.Quic/QuicConnectionFactory.cs GitHub Actions / build (windows-latest)
Check warning on line 25 in src/SuperSocket.Quic/QuicConnectionFactory.cs GitHub Actions / build (macos-latest)
Check warning on line 25 in src/SuperSocket.Quic/QuicConnectionFactory.cs GitHub Actions / build (macos-latest)
|
||
|
||
return Task.FromResult<IConnection>(pipeConnection); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<bool> _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 | ||
Check warning on line 51 in src/SuperSocket.Quic/QuicConnectionListener.cs GitHub Actions / build (ubuntu-latest)
Check warning on line 51 in src/SuperSocket.Quic/QuicConnectionListener.cs GitHub Actions / build (macos-latest)
|
||
{ | ||
ListenBacklog = options.BackLog, | ||
Check warning on line 53 in src/SuperSocket.Quic/QuicConnectionListener.cs GitHub Actions / build (windows-latest)
Check warning on line 53 in src/SuperSocket.Quic/QuicConnectionListener.cs GitHub Actions / build (macos-latest)
|
||
ListenEndPoint = listenEndpoint, | ||
ApplicationProtocols = new List<SslApplicationProtocol> { SslApplicationProtocol.Http3 }, | ||
ConnectionOptionsCallback = (connection, ssl, token) => ValueTask.FromResult( | ||
Check warning on line 56 in src/SuperSocket.Quic/QuicConnectionListener.cs GitHub Actions / build (ubuntu-latest)
Check warning on line 56 in src/SuperSocket.Quic/QuicConnectionListener.cs GitHub Actions / build (windows-latest)
Check warning on line 56 in src/SuperSocket.Quic/QuicConnectionListener.cs GitHub Actions / build (macos-latest)
|
||
new QuicServerConnectionOptions | ||
{ | ||
DefaultStreamErrorCode = _quicTransportOptions.DefaultStreamErrorCode, | ||
DefaultCloseErrorCode = _quicTransportOptions.DefaultCloseErrorCode, | ||
Check warning on line 60 in src/SuperSocket.Quic/QuicConnectionListener.cs GitHub Actions / build (ubuntu-latest)
Check warning on line 60 in src/SuperSocket.Quic/QuicConnectionListener.cs GitHub Actions / build (macos-latest)
|
||
IdleTimeout = _quicTransportOptions.IdleTimeout.HasValue | ||
Check warning on line 61 in src/SuperSocket.Quic/QuicConnectionListener.cs GitHub Actions / build (macos-latest)
Check warning on line 61 in src/SuperSocket.Quic/QuicConnectionListener.cs GitHub Actions / build (macos-latest)
|
||
? TimeSpan.FromMicroseconds(_quicTransportOptions.IdleTimeout.Value) | ||
: Timeout.InfiniteTimeSpan, | ||
MaxInboundBidirectionalStreams = _quicTransportOptions.MaxBidirectionalStreamCount, | ||
Check warning on line 64 in src/SuperSocket.Quic/QuicConnectionListener.cs GitHub Actions / build (ubuntu-latest)
Check warning on line 64 in src/SuperSocket.Quic/QuicConnectionListener.cs GitHub Actions / build (macos-latest)
|
||
MaxInboundUnidirectionalStreams = _quicTransportOptions.MaxUnidirectionalStreamCount, | ||
Check warning on line 65 in src/SuperSocket.Quic/QuicConnectionListener.cs GitHub Actions / build (windows-latest)
Check warning on line 65 in src/SuperSocket.Quic/QuicConnectionListener.cs GitHub Actions / build (macos-latest)
|
||
ServerAuthenticationOptions = new SslServerAuthenticationOptions() | ||
Check warning on line 66 in src/SuperSocket.Quic/QuicConnectionListener.cs GitHub Actions / build (macos-latest)
Check warning on line 66 in src/SuperSocket.Quic/QuicConnectionListener.cs GitHub Actions / build (macos-latest)
|
||
{ | ||
ApplicationProtocols = | ||
new List<SslApplicationProtocol> { SslApplicationProtocol.Http3 }, | ||
ServerCertificate = options.CertificateOptions.Certificate, | ||
RemoteCertificateValidationCallback = | ||
options.CertificateOptions.RemoteCertificateValidationCallback, | ||
} | ||
}) | ||
}; | ||
|
||
var listenSocket = _listenQuic = QuicListener.ListenAsync(quicListenerOptions).GetAwaiter().GetResult(); | ||
Check warning on line 77 in src/SuperSocket.Quic/QuicConnectionListener.cs GitHub Actions / build (ubuntu-latest)
Check warning on line 77 in src/SuperSocket.Quic/QuicConnectionListener.cs GitHub Actions / build (windows-latest)
Check warning on line 77 in src/SuperSocket.Quic/QuicConnectionListener.cs GitHub Actions / build (macos-latest)
Check warning on line 77 in src/SuperSocket.Quic/QuicConnectionListener.cs GitHub Actions / build (macos-latest)
Check warning on line 77 in src/SuperSocket.Quic/QuicConnectionListener.cs GitHub Actions / build (macos-latest)
|
||
|
||
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); | ||
Check warning on line 101 in src/SuperSocket.Quic/QuicConnectionListener.cs GitHub Actions / build (ubuntu-latest)
Check warning on line 101 in src/SuperSocket.Quic/QuicConnectionListener.cs GitHub Actions / build (ubuntu-latest)
Check warning on line 101 in src/SuperSocket.Quic/QuicConnectionListener.cs GitHub Actions / build (ubuntu-latest)
Check warning on line 101 in src/SuperSocket.Quic/QuicConnectionListener.cs GitHub Actions / build (ubuntu-latest)
Check warning on line 101 in src/SuperSocket.Quic/QuicConnectionListener.cs GitHub Actions / build (windows-latest)
Check warning on line 101 in src/SuperSocket.Quic/QuicConnectionListener.cs GitHub Actions / build (windows-latest)
Check warning on line 101 in src/SuperSocket.Quic/QuicConnectionListener.cs GitHub Actions / build (windows-latest)
Check warning on line 101 in src/SuperSocket.Quic/QuicConnectionListener.cs GitHub Actions / build (windows-latest)
|
||
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}."); | ||
Check warning on line 131 in src/SuperSocket.Quic/QuicConnectionListener.cs GitHub Actions / build (ubuntu-latest)
Check warning on line 131 in src/SuperSocket.Quic/QuicConnectionListener.cs GitHub Actions / build (windows-latest)
Check warning on line 131 in src/SuperSocket.Quic/QuicConnectionListener.cs GitHub Actions / build (windows-latest)
|
||
return; | ||
} | ||
|
||
await handler.Invoke(this.Options, connection); | ||
} | ||
|
||
public async Task StopAsync() | ||
{ | ||
var listenSocket = _listenQuic; | ||
|
||
if (listenSocket == null) | ||
return; | ||
|
||
_stopTaskCompletionSource = new TaskCompletionSource<bool>(); | ||
|
||
_cancellationTokenSource.Cancel(); | ||
await _listenQuic.DisposeAsync(); | ||
Check warning on line 148 in src/SuperSocket.Quic/QuicConnectionListener.cs GitHub Actions / build (ubuntu-latest)
Check warning on line 148 in src/SuperSocket.Quic/QuicConnectionListener.cs GitHub Actions / build (windows-latest)
Check warning on line 148 in src/SuperSocket.Quic/QuicConnectionListener.cs GitHub Actions / build (windows-latest)
|
||
|
||
await _stopTaskCompletionSource.Task; | ||
} | ||
|
||
public override string ToString() | ||
{ | ||
return Options?.ToString(); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add comment for the scenario _pipeTask = null
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can put this change into a separated PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok