【问题标题】:Gracefully closing a named pipe and disposing of streams优雅地关闭命名管道并处理流
【发布时间】:2017-12-31 16:12:48
【问题描述】:

我有一个双向命名管道。不过,我不知道如何优雅地关闭它,一旦我完成它 - 如果我从客户端关闭连接,服务器端在尝试处理 StreamReader 和 StreamWriter 时会抛出异常。米使用。我目前正在赶上它,但这对我来说似乎是一项杂乱无章的工作。

服务器端代码:

Thread pipeServer = new Thread(ServerThread);
pipeServer.Start();

private void ServerThread(object data)
{
    int threadId = Thread.CurrentThread.ManagedThreadId;
    log.Debug("Spawned thread " + threadId);

    PipeSecurity ps = new PipeSecurity();
    SecurityIdentifier sid = new SecurityIdentifier(WellKnownSidType.WorldSid, null);
    ps.AddAccessRule(new PipeAccessRule(sid, PipeAccessRights.ReadWrite, System.Security.AccessControl.AccessControlType.Allow));
    ps.AddAccessRule(new PipeAccessRule(WindowsIdentity.GetCurrent().Owner, PipeAccessRights.FullControl, System.Security.AccessControl.AccessControlType.Allow));
    log.Debug("Pipe security settings set [Thread " + threadId + "]");

    NamedPipeServerStream pipeServer =
        new NamedPipeServerStream("RDPCommunicationPipe", PipeDirection.InOut, numThreads, PipeTransmissionMode.Message, PipeOptions.None, 0x1000, 0x1000, ps);

    log.Debug("Pipe Servers created");

    // Wait for a client to connect
    log.Info("Pipe created on thread " + threadId + ". Listening for client connection.");
    pipeServer.WaitForConnection();
    log.Debug("Pipe server connection established [Thread " + threadId + "]");

    Thread nextServer = new Thread(ServerThread);
    nextServer.Start();

    try
    {
        // Read the request from the client. Once the client has
        // written to the pipe its security token will be available.

        using (StreamReader sr = new StreamReader(pipeServer))
        {
            using (StreamWriter sw = new StreamWriter(pipeServer) { AutoFlush = true })
            {
                // Verify our identity to the connected client using a
                // string that the client anticipates.

                sw.WriteLine("I am the one true server!");

                log.Debug("[Thread " + threadId + "]" + sr.ReadLine());

                log.Info(string.Format("Client connected on thread {0}. Client ID: {1}", threadId, pipeServer.GetImpersonationUserName()));
                while (!sr.EndOfStream)
                {
                    log.Debug("[Thread " + threadId + "]" + sr.ReadLine());
                }
            }
        }
    }
    // Catch the IOException that is raised if the pipe is broken
    // or disconnected.
    catch (IOException e)
    {
        log.Error("ERROR: " + e);
    }
}

客户端代码:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Starting...");
        var client = new NamedPipeClientStream(".", "RDPCommunicationPipe", PipeDirection.InOut);
        client.Connect();
        Console.WriteLine("Pipe connected successfully");

        using (StreamReader sr = new StreamReader(client))
        {
            using (StreamWriter sw = new StreamWriter(client) { AutoFlush = true })
            {
                string temp;
                do
                {
                    temp = sr.ReadLine();
                    Console.WriteLine(temp);
                } while (temp.Trim() != "I am the one true server!");

                sw.WriteLine("Message received and understood");
                while (!string.IsNullOrEmpty(temp = Console.ReadLine()))
                {
                    sw.WriteLine(temp);
                }
            }
        }
        client.Close();
    }
}

它运行良好,直到我在客户端应用程序的一个空行上按 Enter 键,这将终止它,关闭客户端。然后,服务器应用程序在到达 StreamWriter using 块的末尾时抛出 System.IO.IOException: Pipe is broken.。如何正确处置我的流处理程序?

(代码基于发现的想法 herehere。)

【问题讨论】:

    标签: c# named-pipes


    【解决方案1】:

    我目前正在赶上它,但这对我来说似乎是一个拼凑的工作。

    恕我直言,如果您想成为一个好邻居并处置您拥有的 StreamWriter 对象并且仍然投入最少的精力,那么它就与您将获得的一样好。

    也就是说,在我看来,在这种特殊情况下,也可以注释掉对 Dispose() 的调用——或者在你的情况下,不要使用 using 语句——并包含另一条注释解释在代码执行顺序中的那个时刻,您知道所有调用都会抛出异常,因此没有必要进行。

    当然,如果您只是不想处理StreamWriter,那么您将需要显式处理您的管道流。您可能还想使用具有leaveOpen 参数的StreamWriter 构造函数,并为该参数传递true,以记录您不让StreamWriter 拥有管道流对象的意图。

    无论哪种方式,您最终都会将对象留在终结器队列中,因为异常绕过了对GC.SuppressFinalize() 的调用,就像(当然)根本不费心调用Dispose() 一样。只要您不处理大量场景(即大量这些对象),那可能就可以了。但这肯定不理想。

    不幸的是,命名管道本身并没有提供套接字所具有的那种“优雅闭包”的语义。也就是说,端点指示它们已完成写入的唯一方法是断开连接(对于服务器管道)或关闭(对于服务器或客户端管道)。这两个选项都不让管道可供读取,因此在管道上实现优雅的闭包需要在应用程序协议本身内进行握手,而不是依赖于 I/O 对象。

    除了这种不便(我承认这与您的问题没有直接关系)之外,PipeStream.Flush() 的实现还会检查管道是否可写。 尽管它无意写任何东西!这是我觉得很烦人的最后一部分,当然直接导致了你所问的问题。在我看来,让 .NET Framework 中的代码在这些异常造成的麻烦多于好处的情况下抛出异常是不合理的。

    说了这么多,你还有其他选择:

    1. 子类化NamedPipeServerStreamNamedPipeClientStream 类型,并覆盖Flush() 方法,这样它实际上什么都不做。或者更确切地说,如果你能做到这一点,那就太好了。但是这些类型是sealed,所以你不能。
    2. 除了对这些类型进行子类化之外,您还可以将它们包装在您自己的 Stream 实现中。这更麻烦,尤其是因为您可能想要覆盖所有异步成员,至少如果您打算在任何对 I/O 性能感兴趣的情况下使用这些对象。
    3. 使用单独的单向管道进行读取和写入。在这个实现中,您可以关闭StreamWriter 本身作为关闭连接的一种方式,这会导致事情的正确顺序(即刷新发生在管道上的关闭之前)。这也解决了优雅的关闭问题,因为每个连接有两个管道,您可以拥有与套接字相同的基本“半关闭”语义。当然,由于难以确定哪对管道连接相互匹配,因此这个选项变得非常复杂。

    这两个(即第二个和第三个,即那些实际上可能的)都有一些明显的缺点。必须拥有自己的Stream 课程是一件痛苦的事,因为需要重复所有代码。并且将管道对象计数加倍似乎是解决异常的一种激进方式(但它可能是支持优雅闭包语义的可接受且理想的实现,具有消除StreamWriter.Dispose() 引发的异常问题的快乐副作用) .

    请注意,在大容量场景中(但是,为什么要使用管道??),高频率抛出和捕获异常可能是一个问题(它们很昂贵)。因此,在这种情况下,这两个替代选项中的一个或另一个可能会更可取,而不是捕获异常并且不费心关闭/处置您的StreamWriter(这两种方法都会增加效率低下,从而干扰大量场景)。

    【讨论】:

    • 幸好它不是大容量的——我正在编写一个用于回归测试的自动化测试套件,我目前不认为它需要超过四个左右的客户端连接。该管道用于允许从在管理员上运行的主应用程序控制在其他用户上运行的应用程序。我认为在这种情况下,我最好的选择可能就是不打电话给Dispose()
    • “我最好的选择可能就是不调用 Dispose?” -- 可能。我想一个名义上更好的实现是保留 try/catch,并在 catch 块中添加对 GC.SuppressFinalize() 的调用。这将允许在您自己的线程中抛出和捕获异常,而不是让终结器线程处理它。当然,捕获异常并且抑制终结器对我来说似乎更糟,因为异常会被抛出两次,一次在你的线程中,一次在终结器线程中。
    • 如果Dispose() 失败并且我调用GC.SuppressFinalize(),这是否意味着StreamWriter 将无限期地停留在内存中,永远不会被清理? (对不起,如果这非常不准确,我并没有真正掌握垃圾收集的工作原理。)
    • 不,这只是意味着不会调用 StreamWriter 终结器。这是“好”(对于“好”的一些广义定义......显然它并不理想)。它所要做的就是再次尝试拨打Dispose(bool)。从终结器调用,它不会尝试访问流,因此它会避免异常。但它也没有做任何真正有用的清理(流是你真正关心的东西),所以坐在终结器队列中只会增加开销。
    • 那么...是这样吗?我需要在try 块之外声明StreamReader srStreamWriter sw,然后在其中分配sr = new StreamReader(pipeServer)sw = new StreamWriter(pipeServer) { AutoFlush = true }。然后在try 块的末尾,我调用sw.Close(),它会抛出一个IOException。不必费心打电话给sr.Close(),因为永远无法到达该代码。然后在catch 中,我调用GC.SuppressFinalize(sw)GC.SuppressFinalize(sr)。是吗?
    猜你喜欢
    • 1970-01-01
    • 2021-12-28
    • 1970-01-01
    • 2012-08-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-08
    相关资源
    最近更新 更多