~ruther/NosSmooth.Comms

59f89c10d3b19d2dff02b651d893181519a7f3e0 — František Boháček 2 years ago 5164e0a
feat: add named pipes implementation
A src/Core/NosSmooth.Comms.NamedPipes/Extensions/ServiceCollectionExtensions.cs => src/Core/NosSmooth.Comms.NamedPipes/Extensions/ServiceCollectionExtensions.cs +41 -0
@@ 0,0 1,41 @@
//
//  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.IO.Pipes;
using Microsoft.Extensions.DependencyInjection;
using NosSmooth.Comms.Data;

namespace NosSmooth.Comms.NamedPipes.Extensions;

/// <summary>
/// Extension methods for <see cref="IServiceCollection"/>.
/// </summary>
public static class ServiceCollectionExtensions
{
    /// <summary>
    /// Adds a named pipe client as a <see cref="IClient"/>.
    /// </summary>
    /// <param name="serviceCollection">The service collection.</param>
    /// <param name="pipeNameResolver">A function for resolving the name of the pipe using service provider.</param>
    /// <returns>The same service collection.</returns>
    public static IServiceCollection AddNamedPipeClient
        (this IServiceCollection serviceCollection, Func<IServiceProvider, string> pipeNameResolver)
        => serviceCollection
            .AddSingleton<NamedPipeClient>(p => new NamedPipeClient(pipeNameResolver(p)))
            .AddSingleton<IClient>(p => p.GetRequiredService<NamedPipeClient>());

    /// <summary>
    /// Adds a named pipe server as a <see cref="IServer"/>.
    /// </summary>
    /// <param name="serviceCollection">The service collection.</param>
    /// <param name="pipeNameResolver">A function for resolving the name of the pipe using service provider.</param>
    /// <returns>The same service collection.</returns>
    public static IServiceCollection AddNamedPipeServer
        (this IServiceCollection serviceCollection, Func<IServiceProvider, string> pipeNameResolver)
        => serviceCollection
            .AddSingleton<NamedPipeServer>(p => new NamedPipeServer(pipeNameResolver(p)))
            .AddSingleton<IServer>(p => p.GetRequiredService<NamedPipeServer>());
}
\ No newline at end of file

A src/Core/NosSmooth.Comms.NamedPipes/NamedPipeClient.cs => src/Core/NosSmooth.Comms.NamedPipes/NamedPipeClient.cs +64 -0
@@ 0,0 1,64 @@
//
//  NamedPipeClient.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.IO.Pipes;
using NosSmooth.Comms.Data;
using Remora.Results;

namespace NosSmooth.Comms.NamedPipes;

/// <summary>
/// A client using named pipes.
/// </summary>
public class NamedPipeClient : IClient
{
    private readonly NamedPipeClientStream _stream;

    /// <summary>
    /// Initializes a new instance of the <see cref="NamedPipeClient"/> class.
    /// </summary>
    /// <param name="pipeName">The name of the pipe.</param>
    public NamedPipeClient(string pipeName)
    {
        _stream = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous);
    }

    /// <inheritdoc />
    public ConnectionState State => _stream.IsConnected ? ConnectionState.Open : ConnectionState.Closed;

    /// <inheritdoc />
    public Stream ReadStream => _stream;

    /// <inheritdoc />
    public Stream WriteStream => _stream;

    /// <inheritdoc />
    public void Disconnect()
    {
        _stream.Close();
        _stream.Dispose();
    }

    /// <inheritdoc />
    public async Task<Result> ConnectAsync(CancellationToken ct)
    {
        try
        {
            await _stream.ConnectAsync(ct);
            return Result.FromSuccess();
        }
        catch (OperationCanceledException)
        {
            // ignored
            return Result.FromSuccess();
        }
        catch (Exception e)
        {
            return e;
        }
    }
}
\ No newline at end of file

A src/Core/NosSmooth.Comms.NamedPipes/NamedPipeServer.cs => src/Core/NosSmooth.Comms.NamedPipes/NamedPipeServer.cs +125 -0
@@ 0,0 1,125 @@
//
//  NamedPipeServer.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.IO.Pipes;
using NosSmooth.Comms.Data;
using Remora.Results;

namespace NosSmooth.Comms.NamedPipes;

/// <summary>
/// A server using named pipes.
/// </summary>
public class NamedPipeServer : IServer
{
    private readonly List<IConnection> _connections;
    private readonly ReaderWriterLockSlim _readerWriterLock;
    private readonly string _pipeName;
    private bool _listening;

    /// <summary>
    /// Initializes a new instance of the <see cref="NamedPipeServer"/> class.
    /// </summary>
    /// <param name="pipeName">The name of the pipe.</param>
    public NamedPipeServer(string pipeName)
    {
        _readerWriterLock = new ReaderWriterLockSlim();
        _pipeName = pipeName;
        _connections = new List<IConnection>();
    }

    /// <inheritdoc />
    public IReadOnlyList<IConnection> Clients
    {
        get
        {
            _readerWriterLock.EnterReadLock();
            var connections = new List<IConnection>(_connections);
            _readerWriterLock.ExitReadLock();
            return connections.AsReadOnly();
        }
    }

    /// <inheritdoc />
    public async Task<Result<IConnection>> WaitForConnectionAsync(CancellationToken ct = default)
    {
        if (!_listening)
        {
            throw new InvalidOperationException("The server is not listening.");
        }

        var serverStream = new NamedPipeServerStream
        (
            _pipeName,
            PipeDirection.InOut,
            NamedPipeServerStream.MaxAllowedServerInstances,
            PipeTransmissionMode.Byte,
            PipeOptions.Asynchronous
        );

        await serverStream.WaitForConnectionAsync(ct);

        var connection = new NamedPipeConnection(this, serverStream);
        _readerWriterLock.EnterWriteLock();
        _connections.Add(connection);
        _readerWriterLock.ExitWriteLock();

        return connection;
    }

    /// <inheritdoc />
    public Task<Result> ListenAsync(CancellationToken stopToken = default)
    {
        _listening = true;
        stopToken.Register(Close);
        return Task.FromResult(Result.FromSuccess());
    }

    /// <inheritdoc />
    public void Close()
    {
        _readerWriterLock.EnterReadLock();
        var connections = new List<IConnection>(_connections);
        _readerWriterLock.ExitReadLock();

        foreach (var connection in connections)
        {
            connection.Disconnect();
        }

        _listening = false;
    }

    private class NamedPipeConnection : IConnection
    {
        private readonly NamedPipeServer _server;
        private readonly NamedPipeServerStream _serverStream;

        public NamedPipeConnection(NamedPipeServer server, NamedPipeServerStream serverStream)
        {
            _server = server;
            _serverStream = serverStream;
        }

        public ConnectionState State { get; private set; } = ConnectionState.Open;

        public Stream ReadStream => _serverStream;

        public Stream WriteStream => _serverStream;

        public void Disconnect()
        {
            _serverStream.Disconnect();
            _serverStream.Close();
            State = ConnectionState.Closed;

            _server._readerWriterLock.EnterWriteLock();
            _server._connections.Remove(this);
            _server._readerWriterLock.ExitWriteLock();
        }
    }
}
\ No newline at end of file

A src/Core/NosSmooth.Comms.NamedPipes/NosSmooth.Comms.NamedPipes.csproj => src/Core/NosSmooth.Comms.NamedPipes/NosSmooth.Comms.NamedPipes.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>

Do not follow this link