RandomEngy / PipeMethodCalls

Lightweight .NET Standard 2.0 library for method calls over named pipes for IPC. Supports two-way communication with callbacks.
https://www.nuget.org/packages/PipeMethodCalls
MIT License
145 stars 24 forks source link

Can multiple Clients with same Pipename connect to a Single Server? #11

Open Michael-Kempe opened 3 years ago

Michael-Kempe commented 3 years ago

Hello, I like your pipe implementation very much! Great! Just one question : Is it possible to have more than one client connection to a server with identical pipe name? I'm asking because in my short test I see that the first client connects successfully, but the second just hangs...

Any idea on how to manage this? Kind regards, Micha

RandomEngy commented 3 years ago

Yes but you need to manually start multiple PipeServers. The .NET docs have an example of how to do it with raw pipes; the concept would be the same for my library.

https://docs.microsoft.com/en-us/dotnet/standard/io/how-to-use-named-pipes-for-network-interprocess-communication

Ideally you'd have some kind of pool of PipeServer objects, put the instances back in the pool after they've closed and handle the case where all of them are used up. I haven't written that because I have not needed it yet. But if it were added it would probably go in a separate library like PipeMethodCalls.MultiInstanceServer .

caesay commented 2 years ago

@RandomEngy I was just searching for a nice NamedPipeServerStream wrapper and stumbled on your library. Would you accept a PR which adds a PipeServerMultiClient which has a TcpListener-like interface? Imagine something such as the following:

var server = new PipeServerMultiClient<ICalculator>(new NetJsonPipeSerializer(), "namedPipe", () => new Calculator());
while(true) {
    var clientStream = await server.AcceptClientConnectionAsync();
    // do something with the stream if you please, such as send a message to the client. 
    // Or don't! and let the client send messages to Calculator
}

I think this simple interface would be enough to handle multiple parallel connection requests without needing a pool of PipeServer's.

RandomEngy commented 2 years ago

Small world! I might make the handling/callback code another lambda function argument, and have the internals handle the connections. That way you don't accidentally block the handling loop on some other task.

var server = new PipeServerMulti<ICalculator>(
    new NetJsonPipeSerializer(),
    "namedPipe",
    5, // Max simultaneous pipes to have open. Underlying .NET core API requires this.
    () => new Calculator(),
    async connection => {
        // Do something like send a message back. This argument could be optional in the one-way, and required for the `WithCallback` variant.
    });

Later you could call server.Dispose() to shut it down. The max instance behavior is described here. The implementation could create new NamedPipeServerStream objects on-demand up to the instance limit as new clients connect.

But yeah, I'd accept a PR along these lines. Not sure if I'd want to keep it in a separate DLL/NuGet package or not; probably depends on how heavy the implementation is.

caesay commented 2 years ago

Thanks for that link! I ended up with a suitable solution with just a few lines of code, so leaving this here in case anyone else needs an example.

private async Task AcceptConnection()
{
    var server = new PipeServer<IArgForwardingServer>(new NetJsonPipeSerializer(), "pipeName", () => this, maxNumberOfServerInstances: -1);
    await server.WaitForConnectionAsync(_cts.Token);
    server.WaitForRemotePipeCloseAsync().ContinueWith(v => server.Dispose());
}

private async void ListenForConnectionRequests()
{
    while (!_cts.IsCancellationRequested)
    {
        await AcceptConnection();
    }
}
mtinnes commented 1 year ago

Is there a general solution to providing resiliency against either side (server or client) dropping, then reconnecting later. I'd like to provide the ability to reestablish the connection if either side disconnects/reconnects.

RandomEngy commented 1 year ago

I don't have anything like that built into the library. The idea is that if the connection closes you should open a new one to continue invoking method calls. Each side tracks pending method calls with incrementing call IDs and they could get out of sync if we tried to re-use a connection.