~ruther/NosSmooth.Comms

ref: 8a197a09502eae1c7335ff552b207eec96714188 NosSmooth.Comms/src/Core/NosSmooth.Comms.NamedPipes/NamedPipeServer.cs -rw-r--r-- 3.5 KiB
8a197a09 — František Boháček feat: add tcp implementation 2 years ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
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();
        }
    }
}