~ruther/NosSmooth.Comms

8a197a09502eae1c7335ff552b207eec96714188 — František Boháček 2 years ago 59f89c1
feat: add tcp implementation
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

Do not follow this link