Skip to content

Commit

Permalink
Custom listener service
Browse files Browse the repository at this point in the history
Closes #115
  • Loading branch information
fubar-coder committed Oct 8, 2021
1 parent 8b4b8d7 commit d0887cd
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 13 deletions.
32 changes: 32 additions & 0 deletions src/FubarDev.FtpServer.Abstractions/IFtpListenerService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// <copyright file="IFtpListenerService.cs" company="Fubar Development Junker">
// Copyright (c) Fubar Development Junker. All rights reserved.
// </copyright>

using System;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Channels;

namespace FubarDev.FtpServer
{
/// <summary>
/// Service to control the listener for FTP connections.
/// </summary>
public interface IFtpListenerService : IPausableFtpService
{
/// <summary>
/// Event to be triggered when the listener started.
/// </summary>
event EventHandler<ListenerStartedEventArgs>? ListenerStarted;

/// <summary>
/// Gets the <see cref="CancellationTokenSource"/> for the listener.
/// </summary>
CancellationTokenSource ListenerShutdown { get; }

/// <summary>
/// Gets the channel with new TCP clients.
/// </summary>
ChannelReader<TcpClient> Channel { get; }
}
}
23 changes: 12 additions & 11 deletions src/FubarDev.FtpServer/FtpServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -37,10 +36,9 @@ public sealed class FtpServer : IFtpServer, IDisposable
private readonly List<IFtpConnectionConfigurator> _connectionConfigurators;
private readonly List<IFtpControlStreamAdapter> _controlStreamAdapters;
private readonly ConcurrentDictionary<IFtpConnection, FtpConnectionInfo> _connections = new ConcurrentDictionary<IFtpConnection, FtpConnectionInfo>();
private readonly FtpServerListenerService _serverListener;
private readonly IFtpListenerService _serverListener;
private readonly ILogger<FtpServer>? _log;
private readonly Task _clientReader;
private readonly CancellationTokenSource _serverShutdown = new CancellationTokenSource();
private readonly Timer? _connectionTimeoutChecker;

/// <summary>
Expand All @@ -50,12 +48,14 @@ public sealed class FtpServer : IFtpServer, IDisposable
/// <param name="serviceProvider">The service provider used to query services.</param>
/// <param name="controlStreamAdapters">Adapters for the control connection stream.</param>
/// <param name="connectionConfigurators">Configurators for FTP connections.</param>
/// <param name="ftpListenerService">Listener service for FTP connections.</param>
/// <param name="logger">The FTP server logger.</param>
public FtpServer(
IOptions<FtpServerOptions> serverOptions,
IServiceProvider serviceProvider,
IEnumerable<IFtpControlStreamAdapter> controlStreamAdapters,
IEnumerable<IFtpConnectionConfigurator> connectionConfigurators,
IFtpListenerService ftpListenerService,
ILogger<FtpServer>? logger = null)
{
_serviceProvider = serviceProvider;
Expand All @@ -66,15 +66,16 @@ public FtpServer(
Port = serverOptions.Value.Port;
MaxActiveConnections = serverOptions.Value.MaxActiveConnections;

var tcpClientChannel = Channel.CreateBounded<TcpClient>(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)
{
Expand Down Expand Up @@ -120,7 +121,7 @@ public void Dispose()

_connectionTimeoutChecker?.Dispose();

_serverShutdown.Dispose();
(_serverListener as IDisposable)?.Dispose();
foreach (var connectionInfo in _connections.Values)
{
connectionInfo.Scope.Dispose();
Expand Down Expand Up @@ -177,9 +178,9 @@ public async Task StartAsync(CancellationToken cancellationToken)
/// <inheritdoc />
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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}

Expand Down
71 changes: 71 additions & 0 deletions src/FubarDev.FtpServer/Networking/DefaultFtpListenerService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// <copyright file="DefaultFtpListenerService.cs" company="Fubar Development Junker">
// Copyright (c) Fubar Development Junker. All rights reserved.
// </copyright>

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
{
/// <summary>
/// Default implementation of <see cref="IFtpListenerService"/>.
/// </summary>
internal class DefaultFtpListenerService : IFtpListenerService, IDisposable
{
private readonly CancellationTokenSource _serverShutdown = new CancellationTokenSource();
private readonly Channel<TcpClient> _channels = System.Threading.Channels.Channel.CreateBounded<TcpClient>(5);
private readonly FtpServerListenerService _listenerService;

public DefaultFtpListenerService(
IOptions<FtpServerOptions> serverOptions,
ILogger<DefaultFtpListenerService>? logger = null)
{
_listenerService = new FtpServerListenerService(
_channels.Writer,
serverOptions,
_serverShutdown,
logger);
_listenerService.ListenerStarted += (sender, args) => ListenerStarted?.Invoke(sender, args);
}

/// <inheritdoc />
public event EventHandler<ListenerStartedEventArgs>? ListenerStarted;

/// <inheritdoc />
public CancellationTokenSource ListenerShutdown => _serverShutdown;

/// <inheritdoc />
public ChannelReader<TcpClient> Channel => _channels.Reader;

/// <inheritdoc />
public FtpServiceStatus Status => _listenerService.Status;

/// <inheritdoc />
public Task StartAsync(CancellationToken cancellationToken)
=> _listenerService.StartAsync(cancellationToken);

/// <inheritdoc />
public Task StopAsync(CancellationToken cancellationToken)
=> _listenerService.StopAsync(cancellationToken);

/// <inheritdoc />
public Task PauseAsync(CancellationToken cancellationToken)
=> _listenerService.PauseAsync(cancellationToken);

/// <inheritdoc />
public Task ContinueAsync(CancellationToken cancellationToken)
=> _listenerService.ContinueAsync(cancellationToken);

/// <inheritdoc />
public void Dispose()
{
_serverShutdown.Dispose();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ internal sealed class FtpServerListenerService : PausableFtpService
{
private readonly ChannelWriter<TcpClient> _newClientWriter;
private readonly MultiBindingTcpListener _multiBindingTcpListener;

private readonly CancellationTokenSource _connectionClosedCts;

private Exception? _exception;
Expand Down
2 changes: 1 addition & 1 deletion src/FubarDev.FtpServer/Networking/PausableFtpService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace FubarDev.FtpServer.Networking
/// <summary>
/// Base class for communication services.
/// </summary>
internal abstract class PausableFtpService : IPausableFtpService
public abstract class PausableFtpService : IPausableFtpService
{
private readonly CancellationTokenSource _jobStopped = new CancellationTokenSource();

Expand Down
2 changes: 2 additions & 0 deletions src/FubarDev.FtpServer/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -43,6 +44,7 @@ public static IServiceCollection AddFtpServer(
services.AddOptions();

services.AddSingleton<IFtpServer, FtpServer>();
services.AddSingleton<IFtpListenerService, DefaultFtpListenerService>();
services.AddSingleton<ITemporaryDataFactory, TemporaryDataFactory>();
services.AddSingleton<IPasvListenerFactory, PasvListenerFactory>();
services.AddSingleton<IPasvAddressResolver, SimplePasvAddressResolver>();
Expand Down

0 comments on commit d0887cd

Please sign in to comment.