0

I am able to read messages sent over NamedPipeClientStreams by 2 threads using the following code:

Clients:

private static async void DoLogging(string context)
{
    try
    {
        var pipeClient = new NamedPipeClientStream(".", PipeName, PipeDirection.InOut, PipeOptions.Asynchronous);

        for (var i = 0; i < 10; i++)
        {
            if (!pipeClient.IsConnected)
                pipeClient.Connect(1000);

            var buffer = Encoding.UTF8.GetBytes($"Context {context}: Message {i}");

            await pipeClient.WriteAsync(buffer, 0, buffer.Length);

            pipeClient.Flush();

            Thread.Sleep(100);  // For testing purposes
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}

Server:

private static void ListenForConnection()
{
    while (true)
    {
        var pipeServer = new NamedPipeServerStream(PipeName, PipeDirection.InOut,
            NamedPipeServerStream.MaxAllowedServerInstances,
            PipeTransmissionMode.Message, PipeOptions.Asynchronous);

        var namedPipeListener = pipeServer.WaitForConnectionAsync(CancellationToken.None).ToObservable();

        namedPipeListener.SubscribeOn(Scheduler.Default).Subscribe(unit =>
        {
            HandleClient(pipeServer);
        });
    }
}

private static void HandleClient(PipeStream pipeServer)
{
    var bytes = new byte[BufferSize];
    var sb = new StringBuilder();

    while (true)
    {
        int numBytes;
        do
        {
            numBytes = pipeServer.Read(bytes, 0, BufferSize);
            if (numBytes > 0)
            {
                sb.Append(Encoding.UTF8.GetString(bytes, 0, numBytes));
            }
        } while (numBytes > 0 && !pipeServer.IsMessageComplete);

        var text = sb.ToString();

        if (!string.IsNullOrWhiteSpace(text))
        {
            Console.WriteLine(text.Trim());
            sb.Clear();
        }
    }
}

The above is called like this:

private const string PipeName = "SvTest.Pipe.Name";
private const int BufferSize = 1024;

private static void Main()
{
    try
    {
        var tasks = new Task[3];

        tasks[0] = Task.Run(() => ListenForConnection());
        tasks[1] = Task.Run(() => DoLogging("Thread 1"));
        tasks[2] = Task.Run(() => DoLogging("Thread 2"));

        Task.WaitAll(tasks);
    }
    catch (Exception ex)
    {
        LogException(ex);
    }

    Console.ReadLine();
}

If I try to read the stream using RX I can read the first message an then I get a "Pipe is broken" error in the client threads when the HandleClient method looks like this:

private static void HandleClient(Stream pipeStream)
{
    var buffer = new byte[pipeStream.Length];
    var sb = new StringBuilder();

    var readObservable = pipeStream.ReadAsync(buffer, 0, buffer.Length).ToObservable();

    readObservable.SubscribeOn(Scheduler.Default).Subscribe(
        unit =>
        {
            Read(buffer, sb);
        },
        OnError,
        () => { ReadCompleted(sb); }, CancellationToken.None);
}

I assume the server stream is closed after it is read. I am not sure how to keep the stream open and reading from it. The code samples and documentation I was able to find make use of old APM techniques. I want to use the latest/recommended techniques offered by .Net 4.6 and the RX library. In my application the clients will be sending messages at will and over a long period of time. So I want the pipe to remain open per client so as not to incur the connection overhead with every message.

I would also appreciate any guidance on the edge cases so that I end up with a rock solid solution.

Gerhard Wessels
  • 681
  • 1
  • 9
  • 18

0 Answers0