【问题标题】:Is a named pipe able to do what i want to do?命名管道能做我想做的事吗?
【发布时间】:2015-10-13 16:57:29
【问题描述】:

这是take II,我前一周发的my question被搁置了,我调整了我的文字但无法获得评论,系统关闭了原帖。

逻辑

服务器端只读 - 服务器打开管道,然后定期检查是否有内容(即不在流的末尾)并读取信息。此检查必须基于轮询,因为只有在轮询期间才会有有效的上下文来传递数据。

客户端只写 - 打开管道,写入管道,关闭(client.exe调用多次,寿命短,下面的代码是测试代码),例如其他一些脚本将“使用信息调用 client.exe”

  • 可以在管道中处理此工作流程吗?例如客户端代码的 sn -p 仅显示“服务器”看到的第一条客户端消息

  • 如果管道可以在编码提示后执行此操作,因为大多数示例都是针对具有相似生命周期的客户端 - 服务器。

代码 sn-ps

    for (int i = 0; i < 10; i++)
    {
        //Client - simulate exe starting and ending
        var client = new NamedPipeClientStream(".", "PipesOfPiece", PipeDirection.Out, PipeOptions.WriteThrough);
        client.Connect();
        StreamWriter writer = new StreamWriter(client);

        Console.WriteLine("Client about to send message");
        writer.WriteLine("Called from client i = {0}", i);

        writer.Close();
        client.Close();

        Thread.Sleep(5000);
    }
    // server snippet
        var server = new NamedPipeServerStream("PipesOfPiece", PipeDirection.In);
        server.WaitForConnection(); <= can this we optional with code below
        StreamReader reader = new StreamReader(server);
        while (true)
        {
            // simulate start of poll code
            if (server.IsConnected)
            {
                if (!reader.EndOfStream)
                {
                    var line = reader.ReadToEnd();
                    Console.WriteLine("Server: {0}", line);
                }
            } // End of poll code
            Thread.Sleep(1000);
        }
      // server snippet
    var server = new NamedPipeServerStream("PipesOfPiece", PipeDirection.In);
        server.WaitForConnection(); <= can this we optional with code below
        StreamReader reader = new StreamReader(server);
        while (true)
        {
            // simulate start of poll code
            if (server.IsConnected)
            {
                if (!reader.EndOfStream)
                {
                    var line = reader.ReadToEnd();
                    Console.WriteLine("Server: {0}", line);
                }
            } // End of poll code
            Thread.Sleep(1000);
        }

所以我的管道生锈了,我希望管道可以打开,写入然后读取,并且 waitforconnect() 用于您想要的情况并且是可选的。我想这一切都围绕谁拥有管道触发,即如果服务器打开管道并等待有人为它写,为什么它需要等待连接? (我希望服务器是所有者,所以当它结束时,管道消失)

【问题讨论】:

  • 不确定你不明白什么。客户端可以打开管道并写入吗?是的。服务器可以打开管道并读取它吗?是的。 doco 会告诉你的。
  • 上面的代码只适用于第一个客户端。记录在案的代码没有涵盖我的上下文。所以也许你可以按用例告诉我 doco 在封面的哪个位置?
  • "only works for the first client pass" - 你的意思是你的代码只适用于 first client 并且后续客户端失败?代码的哪一部分失败了?你是在调试器中运行的吗?
  • 我想避免代码中的细节,以防我走错了路。我想用代码来表示类似我的用例。那么我可以请任何人评论管道功能是否允许服务器打开一次并打开和关闭多个客户端?
  • 没问题。是的,Windows 中的命名管道允许多个客户端使用唯一的管道实例写入同一个逻辑管道(或 管道名称),以便可以同时使用管道。见msdn.microsoft.com/en-us/library/windows/desktop/…*.com/questions/1266849/…

标签: c# named-pipes


【解决方案1】:

如果没有a good, minimal, complete code example 可靠地重现您遇到的任何特定问题,就不可能就如何解决该问题提供具体建议。不过,我至少可以尝试回答您关于如何使用命名管道的一些问题,并提供一个代码示例来说明一些概念。


首先,一些规则和观察:

  • 一个管道实例只能用于一个连接。请注意,管道继承Stream,并且流具有非常特定的范例:您打开一个,读到最后,然后您就完成了流。一些流,比如FileStream,是可搜索的,但即使在那里你也只能处理一个资源(即原始文件……你不能将FileStream重新连接到不同的文件),甚至网络流都不是可搜索。
  • 在对管道执行 I/O 之前,必须先连接管道。
  • 您可能有多个具有相同名称的管道实例(如果您正确初始化它们……默认情况下,您可能只有一个具有任何给定名称的管道)。
  • 尝试连接到命名管道的客户端将等待,直到这样的管道存在。它不需要在客户端启动连接时就存在。
  • 只有一个客户端可以连接到任何给定的管道实例。任何给定的服务器管道实例在其整个生命周期内都只能处理一个客户端(参见上面的第一点)。

那么,你的问题呢?


这个工作流程可以在管道中处理吗?

如果我正确理解工作流程,是的。但是你需要小心正确地实现它。

据我了解,您希望您的服务器仅尝试定期从客户端读取。同时,您希望客户端能够随时写入管道。这可以做到,但并不简单。

请注意,您不能打开一个单个服务器管道,然后让多个客户端定期从该管道连接和断开连接。一旦第一个客户端连接,管道就不再可用。它是一个流,第一个客户端的断开连接会导致流到达其末端。完成了。

还要注意,虽然客户端可以尝试连接到尚不存在的管道,但它会一直等到可以连接为止。因此,如果您希望您的客户端不必等到轮询间隔到期,您将需要维护一个可随时连接的服务器管道。

但是您已经说过,您将无法在任意时间点处理从服务器管道读取的数据,而只能在轮询间隔期间处理。

因为管道本身并不支持这种特定场景,恕我直言,实现它的正确方法是将行为分成两个不同的组件。维护一个简单的管道服务器,它打开管道,等待客户端连接,读取客户端发送的任何内容,关闭管道,然后重新开始。

然后有一个中间类,它可以充当服务器 I/O 和最终接收数据的任何组件的中间人。该中介将在收到数据后保留一份数据副本(管道代码将在收到数据后立即将其交付给中介,无论轮询间隔如何);然后,轮询组件将在其下一个轮询间隔(即,当您所说的“上下文”实际上可用于传递数据时)检索数据。

我希望管道可以打开、写入然后读取,并且 waitforconnect() 用于您想要的情况并且是可选的

很遗憾,您的希望与现实不符。管道可以是双向的;即“写入然后读取”。但是WaitForConnect() 不是可选的。服务器在尝试从管道读取数据之前必须等待连接,并且对于该管道实例,它只能从单个客户端接收数据。

我希望服务器是所有者,所以当它结束时,管道消失

服务器进程是实际创建管道的进程。所以是的,从这个意义上说,它是所有者。是的,当服务器进程终止时,它创建的所有管道都会被销毁。


下面,请找到一个简单的代码示例,该示例说明了多个并发服务器和客户端的使用。您可以使用示例顶部声明的常量来调整每个的数量。

运行它时,请注意,如果活动的客户端多于服务器,则其他客户端将简单地等待,直到有服务器管道可以连接。一旦一个,他们将连接并正常进行。如果服务器管道实例的数量至少与尝试连接的客户端一样多,则所有客户端都会同时得到服务。

// NOTE: as a sample program, contrary to normal and correct
// programming practices error-handling has been omitted, and
// non-awaited async methods have been declared as void.
class Program
{
    private const string _kserverName = "TestSO33093954NamedPipeClients";
    private const int _kmaxServerCount = 3;
    private const int _kmaxClientCount = 3;

    static void Main(string[] args)
    {
        StartServers(_kmaxServerCount);
        StartClients(_kmaxClientCount);

        Console.WriteLine("Clients are being started. Press return to exit program.");
        Console.ReadLine();
    }

    private static async void StartClients(int clientCount)
    {
        for (int i = 0; i < clientCount; i++)
        {
            RunClient(i);
            await Task.Delay(300);
        }
    }

    private static async void RunClient(int instance)
    {
        NamedPipeClientStream client = new NamedPipeClientStream(
            ".", _kserverName, PipeDirection.InOut, PipeOptions.Asynchronous);

        client.Connect();

        ReadClient(client);

        using (StreamWriter writer = new StreamWriter(client))
        {
            writer.AutoFlush = true;

            for (int i = 0; i < 5; i++)
            {
                string text =
                    string.Format("Instance #{0}, iteration #{1}", instance, i);

                Console.WriteLine("Client send: " + text);
                await writer.WriteLineAsync(text);
                await Task.Delay(1000);
            }

            client.WaitForPipeDrain();
        }
    }

    private static async void ReadClient(Stream stream)
    {
        using (TextReader reader = new StreamReader(stream))
        {
            string line;

            while ((line = await reader.ReadLineAsync()) != null)
            {
                Console.WriteLine("Client recv: " + line);
            }
        }
    }

    private static void StartServers(int maxServerInstances)
    {
        for (int i = 0; i < maxServerInstances; i++)
        {
            RunServer(maxServerInstances);
        }
    }

    private static async void RunServer(int maxServerInstances)
    {
        while (true)
        {
            using (NamedPipeServerStream server = new NamedPipeServerStream(
                _kserverName, PipeDirection.InOut, maxServerInstances,
                 PipeTransmissionMode.Byte, PipeOptions.Asynchronous))
            {
                await server.WaitForConnectionAsync();

                byte[] buffer = new byte[1024];
                int bytesRead;
                Decoder decoder = Encoding.UTF8.GetDecoder();

                while ((bytesRead =
                    await server.ReadAsync(buffer, 0, buffer.Length)) > 0)
                {
                    int cch = decoder.GetCharCount(buffer, 0, bytesRead);
                    char[] rgch = new char[cch];

                    decoder.GetChars(buffer, 0, bytesRead, rgch, 0);
                    Console.Write("Server recv: " + new string(rgch));

                    await server.WriteAsync(buffer, 0, bytesRead);
                }
            }
        }
    }
}

static class PipeExtensions
{
    // As I am not running with .NET 4.6 yet, I need this little helper extension
    // to wrap the APM-based asynchronous connection-waiting with the await-friendly
    // Task-based syntax. Anyone using .NET 4.6 will have this in the framework already
    public static Task WaitForConnectionAsync(this NamedPipeServerStream server)
    {
        return Task.Factory.FromAsync(
            server.BeginWaitForConnection, server.EndWaitForConnection, null);
    }
}

【讨论】:

  • 感谢您提供的所有信息。我昨晚确实找到了codeproject.com/Articles/623268/Csharp-wrapper-for-Mailslots,这也是一个替代方案。
  • @GregBRoberts:欢迎来到 Stack Overflow。请阅读What should I do when someone answers my question? 了解有关 Stack Overflow 工作原理的信息。请使用投票按钮来表明您是否认为某个答案有用。请使用接受按钮(复选标记)表明您认为哪个答案最能解决您的问题(如果有)。
  • "第一个客户端连接后,管道不再可用。"诅咒我的无知。我无法告诉你我读了多少答案只是为了了解这个关键的缺失点。