Skip to content

Commit d0887cd

Browse files
committed
Custom listener service
Closes #115
1 parent 8b4b8d7 commit d0887cd

6 files changed

Lines changed: 118 additions & 13 deletions

File tree

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// <copyright file="IFtpListenerService.cs" company="Fubar Development Junker">
2+
// Copyright (c) Fubar Development Junker. All rights reserved.
3+
// </copyright>
4+
5+
using System;
6+
using System.Net.Sockets;
7+
using System.Threading;
8+
using System.Threading.Channels;
9+
10+
namespace FubarDev.FtpServer
11+
{
12+
/// <summary>
13+
/// Service to control the listener for FTP connections.
14+
/// </summary>
15+
public interface IFtpListenerService : IPausableFtpService
16+
{
17+
/// <summary>
18+
/// Event to be triggered when the listener started.
19+
/// </summary>
20+
event EventHandler<ListenerStartedEventArgs>? ListenerStarted;
21+
22+
/// <summary>
23+
/// Gets the <see cref="CancellationTokenSource"/> for the listener.
24+
/// </summary>
25+
CancellationTokenSource ListenerShutdown { get; }
26+
27+
/// <summary>
28+
/// Gets the channel with new TCP clients.
29+
/// </summary>
30+
ChannelReader<TcpClient> Channel { get; }
31+
}
32+
}

src/FubarDev.FtpServer/FtpServer.cs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
using FubarDev.FtpServer.ConnectionChecks;
1919
using FubarDev.FtpServer.Features;
2020
using FubarDev.FtpServer.Localization;
21-
using FubarDev.FtpServer.Networking;
2221
using FubarDev.FtpServer.ServerCommands;
2322

2423
using Microsoft.Extensions.DependencyInjection;
@@ -37,10 +36,9 @@ public sealed class FtpServer : IFtpServer, IDisposable
3736
private readonly List<IFtpConnectionConfigurator> _connectionConfigurators;
3837
private readonly List<IFtpControlStreamAdapter> _controlStreamAdapters;
3938
private readonly ConcurrentDictionary<IFtpConnection, FtpConnectionInfo> _connections = new ConcurrentDictionary<IFtpConnection, FtpConnectionInfo>();
40-
private readonly FtpServerListenerService _serverListener;
39+
private readonly IFtpListenerService _serverListener;
4140
private readonly ILogger<FtpServer>? _log;
4241
private readonly Task _clientReader;
43-
private readonly CancellationTokenSource _serverShutdown = new CancellationTokenSource();
4442
private readonly Timer? _connectionTimeoutChecker;
4543

4644
/// <summary>
@@ -50,12 +48,14 @@ public sealed class FtpServer : IFtpServer, IDisposable
5048
/// <param name="serviceProvider">The service provider used to query services.</param>
5149
/// <param name="controlStreamAdapters">Adapters for the control connection stream.</param>
5250
/// <param name="connectionConfigurators">Configurators for FTP connections.</param>
51+
/// <param name="ftpListenerService">Listener service for FTP connections.</param>
5352
/// <param name="logger">The FTP server logger.</param>
5453
public FtpServer(
5554
IOptions<FtpServerOptions> serverOptions,
5655
IServiceProvider serviceProvider,
5756
IEnumerable<IFtpControlStreamAdapter> controlStreamAdapters,
5857
IEnumerable<IFtpConnectionConfigurator> connectionConfigurators,
58+
IFtpListenerService ftpListenerService,
5959
ILogger<FtpServer>? logger = null)
6060
{
6161
_serviceProvider = serviceProvider;
@@ -66,15 +66,16 @@ public FtpServer(
6666
Port = serverOptions.Value.Port;
6767
MaxActiveConnections = serverOptions.Value.MaxActiveConnections;
6868

69-
var tcpClientChannel = Channel.CreateBounded<TcpClient>(5);
70-
_serverListener = new FtpServerListenerService(tcpClientChannel, serverOptions, _serverShutdown, logger);
69+
_serverListener = ftpListenerService;
7170
_serverListener.ListenerStarted += (s, e) =>
7271
{
7372
Port = e.Port;
7473
OnListenerStarted(e);
7574
};
7675

77-
_clientReader = ReadClientsAsync(tcpClientChannel, _serverShutdown.Token);
76+
_clientReader = ReadClientsAsync(
77+
_serverListener.Channel,
78+
_serverListener.ListenerShutdown.Token);
7879

7980
if (serverOptions.Value.ConnectionInactivityCheckInterval is TimeSpan checkInterval)
8081
{
@@ -120,7 +121,7 @@ public void Dispose()
120121

121122
_connectionTimeoutChecker?.Dispose();
122123

123-
_serverShutdown.Dispose();
124+
(_serverListener as IDisposable)?.Dispose();
124125
foreach (var connectionInfo in _connections.Values)
125126
{
126127
connectionInfo.Scope.Dispose();
@@ -177,9 +178,9 @@ public async Task StartAsync(CancellationToken cancellationToken)
177178
/// <inheritdoc />
178179
public async Task StopAsync(CancellationToken cancellationToken)
179180
{
180-
if (!_serverShutdown.IsCancellationRequested)
181+
if (!_serverListener.ListenerShutdown.IsCancellationRequested)
181182
{
182-
_serverShutdown.Cancel(true);
183+
_serverListener.ListenerShutdown.Cancel(true);
183184
}
184185

185186
await _serverListener.StopAsync(cancellationToken).ConfigureAwait(false);
@@ -297,7 +298,7 @@ private async Task AddClientAsync(TcpClient client)
297298
// Remember connection
298299
if (!_connections.TryAdd(connection, new FtpConnectionInfo(scope)))
299300
{
300-
_log.LogCritical("A new scope was created, but the connection couldn't be added to the list.");
301+
_log.LogCritical("A new scope was created, but the connection couldn't be added to the list");
301302
client.Dispose();
302303
scope.Dispose();
303304
return;
@@ -359,7 +360,7 @@ await connection.StartAsync()
359360
catch (Exception ex)
360361
{
361362
scope.Dispose();
362-
_log?.LogError(ex, ex.Message);
363+
_log?.LogError(ex, "Failed to start the client connection: {ErrorMessage}", ex.Message);
363364
}
364365
}
365366

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// <copyright file="DefaultFtpListenerService.cs" company="Fubar Development Junker">
2+
// Copyright (c) Fubar Development Junker. All rights reserved.
3+
// </copyright>
4+
5+
using System;
6+
using System.Net.Sockets;
7+
using System.Threading;
8+
using System.Threading.Channels;
9+
using System.Threading.Tasks;
10+
11+
using Microsoft.Extensions.Logging;
12+
using Microsoft.Extensions.Options;
13+
14+
namespace FubarDev.FtpServer.Networking
15+
{
16+
/// <summary>
17+
/// Default implementation of <see cref="IFtpListenerService"/>.
18+
/// </summary>
19+
internal class DefaultFtpListenerService : IFtpListenerService, IDisposable
20+
{
21+
private readonly CancellationTokenSource _serverShutdown = new CancellationTokenSource();
22+
private readonly Channel<TcpClient> _channels = System.Threading.Channels.Channel.CreateBounded<TcpClient>(5);
23+
private readonly FtpServerListenerService _listenerService;
24+
25+
public DefaultFtpListenerService(
26+
IOptions<FtpServerOptions> serverOptions,
27+
ILogger<DefaultFtpListenerService>? logger = null)
28+
{
29+
_listenerService = new FtpServerListenerService(
30+
_channels.Writer,
31+
serverOptions,
32+
_serverShutdown,
33+
logger);
34+
_listenerService.ListenerStarted += (sender, args) => ListenerStarted?.Invoke(sender, args);
35+
}
36+
37+
/// <inheritdoc />
38+
public event EventHandler<ListenerStartedEventArgs>? ListenerStarted;
39+
40+
/// <inheritdoc />
41+
public CancellationTokenSource ListenerShutdown => _serverShutdown;
42+
43+
/// <inheritdoc />
44+
public ChannelReader<TcpClient> Channel => _channels.Reader;
45+
46+
/// <inheritdoc />
47+
public FtpServiceStatus Status => _listenerService.Status;
48+
49+
/// <inheritdoc />
50+
public Task StartAsync(CancellationToken cancellationToken)
51+
=> _listenerService.StartAsync(cancellationToken);
52+
53+
/// <inheritdoc />
54+
public Task StopAsync(CancellationToken cancellationToken)
55+
=> _listenerService.StopAsync(cancellationToken);
56+
57+
/// <inheritdoc />
58+
public Task PauseAsync(CancellationToken cancellationToken)
59+
=> _listenerService.PauseAsync(cancellationToken);
60+
61+
/// <inheritdoc />
62+
public Task ContinueAsync(CancellationToken cancellationToken)
63+
=> _listenerService.ContinueAsync(cancellationToken);
64+
65+
/// <inheritdoc />
66+
public void Dispose()
67+
{
68+
_serverShutdown.Dispose();
69+
}
70+
}
71+
}

src/FubarDev.FtpServer/Networking/FtpServerListenerService.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ internal sealed class FtpServerListenerService : PausableFtpService
2323
{
2424
private readonly ChannelWriter<TcpClient> _newClientWriter;
2525
private readonly MultiBindingTcpListener _multiBindingTcpListener;
26-
2726
private readonly CancellationTokenSource _connectionClosedCts;
2827

2928
private Exception? _exception;

src/FubarDev.FtpServer/Networking/PausableFtpService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ namespace FubarDev.FtpServer.Networking
1414
/// <summary>
1515
/// Base class for communication services.
1616
/// </summary>
17-
internal abstract class PausableFtpService : IPausableFtpService
17+
public abstract class PausableFtpService : IPausableFtpService
1818
{
1919
private readonly CancellationTokenSource _jobStopped = new CancellationTokenSource();
2020

src/FubarDev.FtpServer/ServiceCollectionExtensions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
using FubarDev.FtpServer.DataConnection;
1717
using FubarDev.FtpServer.FileSystem;
1818
using FubarDev.FtpServer.Localization;
19+
using FubarDev.FtpServer.Networking;
1920
using FubarDev.FtpServer.ServerCommandHandlers;
2021
using FubarDev.FtpServer.ServerCommands;
2122

@@ -43,6 +44,7 @@ public static IServiceCollection AddFtpServer(
4344
services.AddOptions();
4445

4546
services.AddSingleton<IFtpServer, FtpServer>();
47+
services.AddSingleton<IFtpListenerService, DefaultFtpListenerService>();
4648
services.AddSingleton<ITemporaryDataFactory, TemporaryDataFactory>();
4749
services.AddSingleton<IPasvListenerFactory, PasvListenerFactory>();
4850
services.AddSingleton<IPasvAddressResolver, SimplePasvAddressResolver>();

0 commit comments

Comments
 (0)