A src/Core/NosSmooth.Comms.Tcp/Extensions/ServiceCollectionExtensions.cs => src/Core/NosSmooth.Comms.Tcp/Extensions/ServiceCollectionExtensions.cs +65 -0
@@ 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;
+
+/// <summary>
+/// Extension methods for <see cref="IServiceCollection"/>.
+/// </summary>
+public static class ServiceCollectionExtensions
+{
+ /// <summary>
+ /// Adds tcp server as <see cref="IServer"/>.
+ /// </summary>
+ /// <param name="serviceCollection">The service collection.</param>
+ /// <param name="bindResolver">A resovler used for resolving the ip and port to bind to.</param>
+ /// <returns>The same service collection.</returns>
+ public static IServiceCollection AddTcpServer
+ (
+ this IServiceCollection serviceCollection,
+ Func<IServiceProvider, (IPAddress BindAddress, int Port)> bindResolver
+ )
+ {
+ return serviceCollection
+ .AddSingleton<IServer>(p => p.GetRequiredService<TcpServer>())
+ .AddSingleton<TcpServer>
+ (
+ p =>
+ {
+ var resolved = bindResolver(p);
+ return new TcpServer(resolved.BindAddress, resolved.Port);
+ }
+ );
+ }
+
+ /// <summary>
+ /// Adds tcp client as <see cref="IClient"/>.
+ /// </summary>
+ /// <param name="serviceCollection">The service collection.</param>
+ /// <param name="connectionResolver">A resovler used for resolving the hostname and port to connect to.</param>
+ /// <returns>The same service collection.</returns>
+ public static IServiceCollection AddTcpClient
+ (
+ this IServiceCollection serviceCollection,
+ Func<IServiceProvider, (string Hostname, int Port)> connectionResolver
+ )
+ {
+ return serviceCollection
+ .AddSingleton<IClient>(p => p.GetRequiredService<TcpClient>())
+ .AddSingleton<TcpClient>
+ (
+ p =>
+ {
+ var resolved = connectionResolver(p);
+ return new TcpClient(resolved.Hostname, resolved.Port);
+ }
+ );
+ }
+}<
\ No newline at end of file
A src/Core/NosSmooth.Comms.Tcp/NosSmooth.Comms.Tcp.csproj => src/Core/NosSmooth.Comms.Tcp/NosSmooth.Comms.Tcp.csproj +13 -0
@@ 0,0 1,13 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>net7.0</TargetFramework>
+ <ImplicitUsings>enable</ImplicitUsings>
+ <Nullable>enable</Nullable>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\NosSmooth.Comms.Abstractions\NosSmooth.Comms.Abstractions.csproj" />
+ </ItemGroup>
+
+</Project>
A src/Core/NosSmooth.Comms.Tcp/TcpClient.cs => src/Core/NosSmooth.Comms.Tcp/TcpClient.cs +62 -0
@@ 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;
+
+/// <summary>
+/// A client using tcp.
+/// </summary>
+public class TcpClient : IClient
+{
+ private readonly string _hostname;
+ private readonly int _port;
+ private readonly System.Net.Sockets.TcpClient _client;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="TcpClient"/> class.
+ /// </summary>
+ /// <param name="hostname">The hostname to connet to.</param>
+ /// <param name="port">Teh port to connect to.</param>
+ public TcpClient(string hostname, int port)
+ {
+ _hostname = hostname;
+ _port = port;
+ _client = new System.Net.Sockets.TcpClient();
+ }
+
+ /// <inheritdoc />
+ public ConnectionState State => _client.Connected ? ConnectionState.Open : ConnectionState.Closed;
+
+ /// <inheritdoc />
+ public Stream ReadStream => _client.GetStream();
+
+ /// <inheritdoc />
+ public Stream WriteStream => _client.GetStream();
+
+ /// <inheritdoc />
+ public void Disconnect()
+ {
+ _client.Close();
+ }
+
+ /// <inheritdoc />
+ public async Task<Result> 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
A src/Core/NosSmooth.Comms.Tcp/TcpServer.cs => src/Core/NosSmooth.Comms.Tcp/TcpServer.cs +101 -0
@@ 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;
+
+/// <summary>
+/// A server using tcp.
+/// </summary>
+public class TcpServer : IServer, IDisposable
+{
+ private readonly TcpListener _listener;
+ private readonly List<IConnection> _connections;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="TcpServer"/> class.
+ /// </summary>
+ /// <param name="ip">The ip to bind to.</param>
+ /// <param name="port">The port to bind to.</param>
+ public TcpServer(IPAddress ip, int port)
+ {
+ _listener = new TcpListener(ip, port);
+ _connections = new List<IConnection>();
+ }
+
+ /// <inheritdoc />
+ public IReadOnlyList<IConnection> Clients => _connections.AsReadOnly();
+
+ /// <inheritdoc />
+ public async Task<Result<IConnection>> WaitForConnectionAsync(CancellationToken ct = default)
+ {
+ var tcpClient = await _listener.AcceptTcpClientAsync(ct);
+ var connection = new TcpConnection(tcpClient);
+
+ _connections.Add(connection);
+ return connection;
+ }
+
+ /// <inheritdoc />
+ public Task<Result> ListenAsync(CancellationToken stopToken = default)
+ {
+ try
+ {
+ _listener.Start();
+ return Task.FromResult(Result.FromSuccess());
+ }
+ catch (Exception e)
+ {
+ return Task.FromResult<Result>(e);
+ }
+ }
+
+ /// <inheritdoc />
+ 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();
+ }
+ }
+
+ /// <inheritdoc />
+ public void Dispose()
+ {
+ foreach (var connection in _connections.Cast<IDisposable>())
+ {
+ connection.Dispose();
+ }
+ }
+}<
\ No newline at end of file