From d0887cd7a49eaaa9b04b027ae3be243bf0f0bdee Mon Sep 17 00:00:00 2001 From: Mark Junker Date: Fri, 8 Oct 2021 18:27:40 +0200 Subject: [PATCH] Custom listener service Closes #115 --- .../IFtpListenerService.cs | 32 +++++++++ src/FubarDev.FtpServer/FtpServer.cs | 23 +++--- .../Networking/DefaultFtpListenerService.cs | 71 +++++++++++++++++++ .../Networking/FtpServerListenerService.cs | 1 - .../Networking/PausableFtpService.cs | 2 +- .../ServiceCollectionExtensions.cs | 2 + 6 files changed, 118 insertions(+), 13 deletions(-) create mode 100644 src/FubarDev.FtpServer.Abstractions/IFtpListenerService.cs create mode 100644 src/FubarDev.FtpServer/Networking/DefaultFtpListenerService.cs diff --git a/src/FubarDev.FtpServer.Abstractions/IFtpListenerService.cs b/src/FubarDev.FtpServer.Abstractions/IFtpListenerService.cs new file mode 100644 index 00000000..3ea68473 --- /dev/null +++ b/src/FubarDev.FtpServer.Abstractions/IFtpListenerService.cs @@ -0,0 +1,32 @@ +// +// Copyright (c) Fubar Development Junker. All rights reserved. +// + +using System; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Channels; + +namespace FubarDev.FtpServer +{ + /// + /// Service to control the listener for FTP connections. + /// + public interface IFtpListenerService : IPausableFtpService + { + /// + /// Event to be triggered when the listener started. + /// + event EventHandler? ListenerStarted; + + /// + /// Gets the for the listener. + /// + CancellationTokenSource ListenerShutdown { get; } + + /// + /// Gets the channel with new TCP clients. + /// + ChannelReader Channel { get; } + } +} diff --git a/src/FubarDev.FtpServer/FtpServer.cs b/src/FubarDev.FtpServer/FtpServer.cs index d188341e..8f654653 100644 --- a/src/FubarDev.FtpServer/FtpServer.cs +++ b/src/FubarDev.FtpServer/FtpServer.cs @@ -18,7 +18,6 @@ using FubarDev.FtpServer.ConnectionChecks; using FubarDev.FtpServer.Features; using FubarDev.FtpServer.Localization; -using FubarDev.FtpServer.Networking; using FubarDev.FtpServer.ServerCommands; using Microsoft.Extensions.DependencyInjection; @@ -37,10 +36,9 @@ public sealed class FtpServer : IFtpServer, IDisposable private readonly List _connectionConfigurators; private readonly List _controlStreamAdapters; private readonly ConcurrentDictionary _connections = new ConcurrentDictionary(); - private readonly FtpServerListenerService _serverListener; + private readonly IFtpListenerService _serverListener; private readonly ILogger? _log; private readonly Task _clientReader; - private readonly CancellationTokenSource _serverShutdown = new CancellationTokenSource(); private readonly Timer? _connectionTimeoutChecker; /// @@ -50,12 +48,14 @@ public sealed class FtpServer : IFtpServer, IDisposable /// The service provider used to query services. /// Adapters for the control connection stream. /// Configurators for FTP connections. + /// Listener service for FTP connections. /// The FTP server logger. public FtpServer( IOptions serverOptions, IServiceProvider serviceProvider, IEnumerable controlStreamAdapters, IEnumerable connectionConfigurators, + IFtpListenerService ftpListenerService, ILogger? logger = null) { _serviceProvider = serviceProvider; @@ -66,15 +66,16 @@ public FtpServer( Port = serverOptions.Value.Port; MaxActiveConnections = serverOptions.Value.MaxActiveConnections; - var tcpClientChannel = Channel.CreateBounded(5); - _serverListener = new FtpServerListenerService(tcpClientChannel, serverOptions, _serverShutdown, logger); + _serverListener = ftpListenerService; _serverListener.ListenerStarted += (s, e) => { Port = e.Port; OnListenerStarted(e); }; - _clientReader = ReadClientsAsync(tcpClientChannel, _serverShutdown.Token); + _clientReader = ReadClientsAsync( + _serverListener.Channel, + _serverListener.ListenerShutdown.Token); if (serverOptions.Value.ConnectionInactivityCheckInterval is TimeSpan checkInterval) { @@ -120,7 +121,7 @@ public void Dispose() _connectionTimeoutChecker?.Dispose(); - _serverShutdown.Dispose(); + (_serverListener as IDisposable)?.Dispose(); foreach (var connectionInfo in _connections.Values) { connectionInfo.Scope.Dispose(); @@ -177,9 +178,9 @@ public async Task StartAsync(CancellationToken cancellationToken) /// public async Task StopAsync(CancellationToken cancellationToken) { - if (!_serverShutdown.IsCancellationRequested) + if (!_serverListener.ListenerShutdown.IsCancellationRequested) { - _serverShutdown.Cancel(true); + _serverListener.ListenerShutdown.Cancel(true); } await _serverListener.StopAsync(cancellationToken).ConfigureAwait(false); @@ -297,7 +298,7 @@ private async Task AddClientAsync(TcpClient client) // Remember connection if (!_connections.TryAdd(connection, new FtpConnectionInfo(scope))) { - _log.LogCritical("A new scope was created, but the connection couldn't be added to the list."); + _log.LogCritical("A new scope was created, but the connection couldn't be added to the list"); client.Dispose(); scope.Dispose(); return; @@ -359,7 +360,7 @@ await connection.StartAsync() catch (Exception ex) { scope.Dispose(); - _log?.LogError(ex, ex.Message); + _log?.LogError(ex, "Failed to start the client connection: {ErrorMessage}", ex.Message); } } diff --git a/src/FubarDev.FtpServer/Networking/DefaultFtpListenerService.cs b/src/FubarDev.FtpServer/Networking/DefaultFtpListenerService.cs new file mode 100644 index 00000000..58689275 --- /dev/null +++ b/src/FubarDev.FtpServer/Networking/DefaultFtpListenerService.cs @@ -0,0 +1,71 @@ +// +// Copyright (c) Fubar Development Junker. All rights reserved. +// + +using System; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Channels; +using System.Threading.Tasks; + +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace FubarDev.FtpServer.Networking +{ + /// + /// Default implementation of . + /// + internal class DefaultFtpListenerService : IFtpListenerService, IDisposable + { + private readonly CancellationTokenSource _serverShutdown = new CancellationTokenSource(); + private readonly Channel _channels = System.Threading.Channels.Channel.CreateBounded(5); + private readonly FtpServerListenerService _listenerService; + + public DefaultFtpListenerService( + IOptions serverOptions, + ILogger? logger = null) + { + _listenerService = new FtpServerListenerService( + _channels.Writer, + serverOptions, + _serverShutdown, + logger); + _listenerService.ListenerStarted += (sender, args) => ListenerStarted?.Invoke(sender, args); + } + + /// + public event EventHandler? ListenerStarted; + + /// + public CancellationTokenSource ListenerShutdown => _serverShutdown; + + /// + public ChannelReader Channel => _channels.Reader; + + /// + public FtpServiceStatus Status => _listenerService.Status; + + /// + public Task StartAsync(CancellationToken cancellationToken) + => _listenerService.StartAsync(cancellationToken); + + /// + public Task StopAsync(CancellationToken cancellationToken) + => _listenerService.StopAsync(cancellationToken); + + /// + public Task PauseAsync(CancellationToken cancellationToken) + => _listenerService.PauseAsync(cancellationToken); + + /// + public Task ContinueAsync(CancellationToken cancellationToken) + => _listenerService.ContinueAsync(cancellationToken); + + /// + public void Dispose() + { + _serverShutdown.Dispose(); + } + } +} diff --git a/src/FubarDev.FtpServer/Networking/FtpServerListenerService.cs b/src/FubarDev.FtpServer/Networking/FtpServerListenerService.cs index b5b636ee..a7c1b4ba 100644 --- a/src/FubarDev.FtpServer/Networking/FtpServerListenerService.cs +++ b/src/FubarDev.FtpServer/Networking/FtpServerListenerService.cs @@ -23,7 +23,6 @@ internal sealed class FtpServerListenerService : PausableFtpService { private readonly ChannelWriter _newClientWriter; private readonly MultiBindingTcpListener _multiBindingTcpListener; - private readonly CancellationTokenSource _connectionClosedCts; private Exception? _exception; diff --git a/src/FubarDev.FtpServer/Networking/PausableFtpService.cs b/src/FubarDev.FtpServer/Networking/PausableFtpService.cs index e20c9828..389af331 100644 --- a/src/FubarDev.FtpServer/Networking/PausableFtpService.cs +++ b/src/FubarDev.FtpServer/Networking/PausableFtpService.cs @@ -14,7 +14,7 @@ namespace FubarDev.FtpServer.Networking /// /// Base class for communication services. /// - internal abstract class PausableFtpService : IPausableFtpService + public abstract class PausableFtpService : IPausableFtpService { private readonly CancellationTokenSource _jobStopped = new CancellationTokenSource(); diff --git a/src/FubarDev.FtpServer/ServiceCollectionExtensions.cs b/src/FubarDev.FtpServer/ServiceCollectionExtensions.cs index a7714a6b..320cc587 100644 --- a/src/FubarDev.FtpServer/ServiceCollectionExtensions.cs +++ b/src/FubarDev.FtpServer/ServiceCollectionExtensions.cs @@ -16,6 +16,7 @@ using FubarDev.FtpServer.DataConnection; using FubarDev.FtpServer.FileSystem; using FubarDev.FtpServer.Localization; +using FubarDev.FtpServer.Networking; using FubarDev.FtpServer.ServerCommandHandlers; using FubarDev.FtpServer.ServerCommands; @@ -43,6 +44,7 @@ public static IServiceCollection AddFtpServer( services.AddOptions(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton();