命名管道是否能够做我想做的事情?

时间:2015-10-13 04:09:10

标签: c# named-pipes

这是第二次,我在另一周发布,my question被搁置,我调整了我的文字,但无法进行审核,系统关闭了原始帖子。

逻辑

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

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

调用client.exe
  • 这个工作流程可以在管道中处理吗?例如仅显示第一个客户端消息的客户端代码片段由"服务器"

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

代码段

    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()就是你想要的情况并且是可选的。我想这一切都会触发谁拥有管道,即如果服务器打开管道并等待某人为其写入,为什么需要等待连接呢? (我希望服务器是所有者,所以当它结束时,管道消失了)

1 个答案:

答案 0 :(得分:2)

如果没有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);
    }
}