异步使用NamedPipeServerStream和NamedPipeClientStream

时间:2018-01-02 10:36:34

标签: c# .net named-pipes

我对服务器/客户端架构有以下要求:

  1. 编写异步工作的服务器/客户端。

  2. 通信需要是双工的,即两端的读写。

  3. 多个客户端可以在任何给定时间连接到服务器。

  4. 服务器/客户端应该等到它们可用并最终建立连接。

  5. 客户端连接后,应该写入流。

  6. 然后服务器应该从流中读取并将响应写回客户端。

  7. 最后,客户应该阅读回复,通讯应该结束。

  8. 因此,考虑到以下要求,我编写了以下代码,但我不太确定它,因为管道的文档有点缺乏,遗憾的是代码似乎无法正常工作,它挂起来某一点。

    namespace PipesAsyncAwait471
    {
        using System;
        using System.Collections.Generic;
        using System.IO.Pipes;
        using System.Linq;
        using System.Threading.Tasks;
    
        internal class Program
        {
            private static async Task Main()
            {
                List<Task> tasks = new List<Task> {
                    HandleRequestAsync(),
                };
    
                tasks.AddRange(Enumerable.Range(0, 10).Select(i => SendRequestAsync(i, 0, 5)));
    
                await Task.WhenAll(tasks);
            }
    
            private static async Task HandleRequestAsync()
            {
                using (NamedPipeServerStream server = new NamedPipeServerStream("MyPipe",
                                                                                PipeDirection.InOut,
                                                                                NamedPipeServerStream.MaxAllowedServerInstances,
                                                                                PipeTransmissionMode.Message,
                                                                                PipeOptions.Asynchronous))
                {
                    Console.WriteLine("Waiting...");
    
                    await server.WaitForConnectionAsync().ConfigureAwait(false);
    
                    if (server.IsConnected)
                    {
                        Console.WriteLine("Connected");
    
                        if (server.CanRead) {
                            // Read something...
                        }
    
                        if (server.CanWrite) {
                            // Write something... 
    
                            await server.FlushAsync().ConfigureAwait(false);
    
                            server.WaitForPipeDrain();
                        }
    
                        server.Disconnect();
    
                        await HandleRequestAsync().ConfigureAwait(false);
                    }
                }
            }
    
            private static async Task SendRequestAsync(int index, int counter, int max)
            {
                using (NamedPipeClientStream client = new NamedPipeClientStream(".", "MyPipe", PipeDirection.InOut, PipeOptions.Asynchronous))
                {
                    await client.ConnectAsync().ConfigureAwait(false);
    
                    if (client.IsConnected)
                    {
                        Console.WriteLine($"Index: {index} Counter: {counter}");
    
                        if (client.CanWrite) {
                            // Write something...
    
                            await client.FlushAsync().ConfigureAwait(false);
    
                            client.WaitForPipeDrain();
                        }
    
                        if (client.CanRead) {
                            // Read something...
                        }
                    }
    
                    if (counter <= max) {
                        await SendRequestAsync(index, ++counter, max).ConfigureAwait(false);
                    }
                    else {
                        Console.WriteLine($"{index} Done!");
                    }
                }
            }
        }
    }
    

    假设:

    我期望它工作的方式是我在调用SendRequestAsync并行执行时所做的所有请求,其中每个请求然后发出其他请求,直到它到达6,最后,它应该打印“完成!”。

    备注:

    1. 我在.NET Framework 4.7.1和.NET Core 2.0上测试过,得到了相同的结果。

    2. 客户端与服务器之间的通信始终是本地,其中客户端是可以排队某些工作的Web应用程序,例如启动第三方流程和服务器将作为Windows服务部署在与部署这些客户端的Web服务器相同的计算机上。

2 个答案:

答案 0 :(得分:2)

断开连接时,由于管道损坏,WaitForPipeDrain()可能会抛出IOException

如果在您的服务器Task中发生这种情况,那么它将永远不会侦听下一个连接,并且所有剩余的客户端连接都会挂起ConnectAsync()

如果在其中一个客户端任务中发生这种情况,那么它将不会继续递归并递增该索引的计数器。

如果您将来自WaitForPipeDrain() / try的{​​{1}}打包,程序将继续永久运行,因为您的函数catch是无限递归的。

简而言之,要让它发挥作用:

  1. 处理来自HandleRequestAsync()
  2. IOException
  3. WaitForPipeDrain()必须在某个时候完成。

答案 1 :(得分:1)

以下是一些迭代后的完整代码:

namespace PipesAsyncAwait471
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.IO.Pipes;
    using System.Linq;
    using System.Threading.Tasks;

    internal class Program
    {
        private const int MAX_REQUESTS = 1000;

        private static void Main()
        {
            var tasks = new List<Task> {
                //Task.Run(() => HandleRequest(0))
                HandleRequestAsync(0)
            };

            tasks.AddRange(Enumerable.Range(0, MAX_REQUESTS).Select(i => Task.Factory.StartNew(() => SendRequest(i), TaskCreationOptions.LongRunning)));

            Task.WhenAll(tasks);

            Console.ReadKey();
        }

        private static void HandleRequest(int counter)
        {
            try {
                var server = new NamedPipeServerStream("MyPipe",
                                                    PipeDirection.InOut,
                                                    NamedPipeServerStream.MaxAllowedServerInstances,
                                                    PipeTransmissionMode.Message,
                                                    PipeOptions.Asynchronous);

                Console.WriteLine($"Waiting a client... {counter}");

                server.BeginWaitForConnection(WaitForConnectionCallback, server);
            }
            catch (Exception ex) {
                Console.WriteLine(ex);
            }

            void WaitForConnectionCallback(IAsyncResult result)
            {
                var server = (NamedPipeServerStream)result.AsyncState;

                int index = -1;

                try {
                    server.EndWaitForConnection(result);

                    HandleRequest(++counter);

                    if (server.IsConnected) {
                        var request = new byte[4];
                        server.BeginRead(request, 0, request.Length, ReadCallback, server);
                        index = BitConverter.ToInt32(request, 0);
                        Console.WriteLine($"{index} Request.");

                        var response = BitConverter.GetBytes(index);
                        server.BeginWrite(response, 0, response.Length, WriteCallback, server);
                        server.Flush();
                        server.WaitForPipeDrain();
                        Console.WriteLine($"{index} Pong.");

                        server.Disconnect();
                        Console.WriteLine($"{index} Disconnected.");
                    }
                }
                catch (IOException ex) {
                    Console.WriteLine($"{index}\n\t{ex}");
                }
                finally {
                    server.Dispose();
                }
            }

            void ReadCallback(IAsyncResult result) 
            {
                var server = (NamedPipeServerStream)result.AsyncState;

                try {
                    server.EndRead(result);
                }
                catch (IOException ex) {
                    Console.WriteLine(ex);
                }
            }

            void WriteCallback(IAsyncResult result) 
            {
                var server = (NamedPipeServerStream)result.AsyncState;

                try {
                    server.EndWrite(result);
                }
                catch (IOException ex) {
                    Console.WriteLine(ex);
                }
            }
        }

        private static async Task HandleRequestAsync(int counter)
        {
            NamedPipeServerStream server = null;

            int index = -1;

            try {
                server = new NamedPipeServerStream("MyPipe",
                                                PipeDirection.InOut,
                                                NamedPipeServerStream.MaxAllowedServerInstances,
                                                PipeTransmissionMode.Message,
                                                PipeOptions.Asynchronous);

                Console.WriteLine($"Waiting a client... {counter}");

                await server.WaitForConnectionAsync()
                            .ContinueWith(async t => await HandleRequestAsync(++counter).ConfigureAwait(false))
                            .ConfigureAwait(false);

                if (server.IsConnected) {
                    var request = new byte[4];
                    await server.ReadAsync(request, 0, request.Length).ConfigureAwait(false);
                    index = BitConverter.ToInt32(request, 0);
                    Console.WriteLine($"{index} Request.");

                    var response = BitConverter.GetBytes(index);
                    await server.WriteAsync(response, 0, response.Length).ConfigureAwait(false);
                    await server.FlushAsync().ConfigureAwait(false);
                    server.WaitForPipeDrain();
                    Console.WriteLine($"{index} Pong.");

                    server.Disconnect();
                    Console.WriteLine($"{index} Disconnected.");
                }
            }
            catch (IOException ex) {
                Console.WriteLine($"{index}\n\t{ex}");
            }
            finally {
                server?.Dispose();
            }
        }

        private static void SendRequest(int index)
        {
            NamedPipeClientStream client = null;

            try {
                client = new NamedPipeClientStream(".", "MyPipe", PipeDirection.InOut, PipeOptions.None);

                client.Connect();

                var request = BitConverter.GetBytes(index);
                client.Write(request, 0, request.Length);
                client.Flush();
                client.WaitForPipeDrain();
                Console.WriteLine($"{index} Ping.");

                var response = new byte[4];
                client.Read(response, 0, response.Length);
                index = BitConverter.ToInt32(response, 0);
                Console.WriteLine($"{index} Response.");
            }
            catch (Exception ex) {
                Console.WriteLine($"{index}\n\t{ex}");
            }
            finally {
                client?.Dispose();
            }
        }
    }
}

您可以对消息进行排序并观察以下内容:

  1. 正确打开和关闭连接。

  2. 正确发送和接收数据。

  3. 最后,服务器仍在等待进一步的连接。

  4. <强>更新

    1. PipeOptions.Asynchronous更改为PipeOptions.None,否则它似乎会在请求期间挂起,然后立即处理它们。

      < S>

      PipeOptions.Asynchronous只是导致与PipeOptions.None不同的执行顺序,并且在代码中暴露了竞争条件/死锁。如果你使用任务管理器,你可以看到它的效果,例如,监视你的进程的线程数...你应该看到它以每秒appx 1线程的速率爬升,直到它达到大约100个线程(可能是110左右),此时您的代码将运行完成。或者如果您在开头添加ThreadPool.SetMinThreads(200,200)。您的代码有一个问题,如果发生错误的排序(并且通过使用异步更有可能),您创建一个循环,直到有足够的线程运行您的主方法已排队的所有并发ConnectAsyncs时,它不能满足,这不是真正的异步,而只是创建一个工作项来调用同步Connect方法(这是不幸的,这是像这样的问题,我敦促人们不要暴露异步API,只是简单地将工作项排队到呼叫同步方法)。 Source

    2. 修改并简化了示例:

      1. 管道没有真正的异步Connect方法,ConnectAsync在场景后面使用Task.Factory.StartNew,所以您也可以使用Connect然后传递方法(我们的示例中为SendRequest)将同步Connect版本调用为Task.Factory.StartNew

      2. 服务器现在是完全异步的,据我所知,它没有任何问题。

      3. 为服务器添加了两个使用回调的实现,另一个使用异步/等待功能的实现只是因为我找不到这两个的好例子。

    3. 我希望它有所帮助。