From 8a197a09502eae1c7335ff552b207eec96714188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Boh=C3=A1=C4=8Dek?= Date: Wed, 25 Jan 2023 16:21:57 +0100 Subject: [PATCH] feat: add tcp implementation --- .../Extensions/ServiceCollectionExtensions.cs | 65 +++++++++++ .../NosSmooth.Comms.Tcp.csproj | 13 +++ src/Core/NosSmooth.Comms.Tcp/TcpClient.cs | 62 +++++++++++ src/Core/NosSmooth.Comms.Tcp/TcpServer.cs | 101 ++++++++++++++++++ 4 files changed, 241 insertions(+) create mode 100644 src/Core/NosSmooth.Comms.Tcp/Extensions/ServiceCollectionExtensions.cs create mode 100644 src/Core/NosSmooth.Comms.Tcp/NosSmooth.Comms.Tcp.csproj create mode 100644 src/Core/NosSmooth.Comms.Tcp/TcpClient.cs create mode 100644 src/Core/NosSmooth.Comms.Tcp/TcpServer.cs diff --git a/src/Core/NosSmooth.Comms.Tcp/Extensions/ServiceCollectionExtensions.cs b/src/Core/NosSmooth.Comms.Tcp/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..2bf483b --- /dev/null +++ b/src/Core/NosSmooth.Comms.Tcp/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,65 @@ +// +// ServiceCollectionExtensions.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Net; +using Microsoft.Extensions.DependencyInjection; +using NosSmooth.Comms.Data; + +namespace NosSmooth.Comms.Tcp.Extensions; + +/// +/// Extension methods for . +/// +public static class ServiceCollectionExtensions +{ + /// + /// Adds tcp server as . + /// + /// The service collection. + /// A resovler used for resolving the ip and port to bind to. + /// The same service collection. + public static IServiceCollection AddTcpServer + ( + this IServiceCollection serviceCollection, + Func bindResolver + ) + { + return serviceCollection + .AddSingleton(p => p.GetRequiredService()) + .AddSingleton + ( + p => + { + var resolved = bindResolver(p); + return new TcpServer(resolved.BindAddress, resolved.Port); + } + ); + } + + /// + /// Adds tcp client as . + /// + /// The service collection. + /// A resovler used for resolving the hostname and port to connect to. + /// The same service collection. + public static IServiceCollection AddTcpClient + ( + this IServiceCollection serviceCollection, + Func connectionResolver + ) + { + return serviceCollection + .AddSingleton(p => p.GetRequiredService()) + .AddSingleton + ( + p => + { + var resolved = connectionResolver(p); + return new TcpClient(resolved.Hostname, resolved.Port); + } + ); + } +} \ No newline at end of file diff --git a/src/Core/NosSmooth.Comms.Tcp/NosSmooth.Comms.Tcp.csproj b/src/Core/NosSmooth.Comms.Tcp/NosSmooth.Comms.Tcp.csproj new file mode 100644 index 0000000..62eb048 --- /dev/null +++ b/src/Core/NosSmooth.Comms.Tcp/NosSmooth.Comms.Tcp.csproj @@ -0,0 +1,13 @@ + + + + net7.0 + enable + enable + + + + + + + diff --git a/src/Core/NosSmooth.Comms.Tcp/TcpClient.cs b/src/Core/NosSmooth.Comms.Tcp/TcpClient.cs new file mode 100644 index 0000000..3b47ebb --- /dev/null +++ b/src/Core/NosSmooth.Comms.Tcp/TcpClient.cs @@ -0,0 +1,62 @@ +// +// TcpClient.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Data; +using NosSmooth.Comms.Data; +using Remora.Results; + +namespace NosSmooth.Comms.Tcp; + +/// +/// A client using tcp. +/// +public class TcpClient : IClient +{ + private readonly string _hostname; + private readonly int _port; + private readonly System.Net.Sockets.TcpClient _client; + + /// + /// Initializes a new instance of the class. + /// + /// The hostname to connet to. + /// Teh port to connect to. + public TcpClient(string hostname, int port) + { + _hostname = hostname; + _port = port; + _client = new System.Net.Sockets.TcpClient(); + } + + /// + public ConnectionState State => _client.Connected ? ConnectionState.Open : ConnectionState.Closed; + + /// + public Stream ReadStream => _client.GetStream(); + + /// + public Stream WriteStream => _client.GetStream(); + + /// + public void Disconnect() + { + _client.Close(); + } + + /// + public async Task ConnectAsync(CancellationToken ct = default) + { + try + { + await _client.ConnectAsync(_hostname, _port, ct); + return Result.FromSuccess(); + } + catch (Exception e) + { + return e; + } + } +} \ No newline at end of file diff --git a/src/Core/NosSmooth.Comms.Tcp/TcpServer.cs b/src/Core/NosSmooth.Comms.Tcp/TcpServer.cs new file mode 100644 index 0000000..b2b153e --- /dev/null +++ b/src/Core/NosSmooth.Comms.Tcp/TcpServer.cs @@ -0,0 +1,101 @@ +// +// TcpServer.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Data; +using System.Net; +using System.Net.Sockets; +using NosSmooth.Comms.Data; +using Remora.Results; + +namespace NosSmooth.Comms.Tcp; + +/// +/// A server using tcp. +/// +public class TcpServer : IServer, IDisposable +{ + private readonly TcpListener _listener; + private readonly List _connections; + + /// + /// Initializes a new instance of the class. + /// + /// The ip to bind to. + /// The port to bind to. + public TcpServer(IPAddress ip, int port) + { + _listener = new TcpListener(ip, port); + _connections = new List(); + } + + /// + public IReadOnlyList Clients => _connections.AsReadOnly(); + + /// + public async Task> WaitForConnectionAsync(CancellationToken ct = default) + { + var tcpClient = await _listener.AcceptTcpClientAsync(ct); + var connection = new TcpConnection(tcpClient); + + _connections.Add(connection); + return connection; + } + + /// + public Task ListenAsync(CancellationToken stopToken = default) + { + try + { + _listener.Start(); + return Task.FromResult(Result.FromSuccess()); + } + catch (Exception e) + { + return Task.FromResult(e); + } + } + + /// + public void Close() + { + _listener.Stop(); + } + + private class TcpConnection : IConnection, IDisposable + { + private readonly System.Net.Sockets.TcpClient _client; + + public TcpConnection(System.Net.Sockets.TcpClient client) + { + _client = client; + } + + public ConnectionState State => _client.Connected ? ConnectionState.Open : ConnectionState.Closed; + + public Stream ReadStream => _client.GetStream(); + + public Stream WriteStream => _client.GetStream(); + + public void Disconnect() + { + _client.Close(); + } + + public void Dispose() + { + _client.Dispose(); + } + } + + /// + public void Dispose() + { + foreach (var connection in _connections.Cast()) + { + connection.Dispose(); + } + } +} \ No newline at end of file -- 2.48.1