【问题标题】:The need to call DisposeLocalCopyOfClientHandle() after establishing a connection建立连接后需要调用 DisposeLocalCopyOfClientHandle()
【发布时间】:2017-02-02 14:27:58
【问题描述】:

与匿名管道建立连接之后的步骤需要服务器调用DisposeLocalCopyOfClientHandle。 MSDN 解释:

DisposeLocalCopyOfClientHandle 方法应该在 客户端句柄已传递给客户端。如果这个方法不 调用,AnonymousPipeServerStream 对象将不会收到通知 当客户端释放其 PipeStream 对象时。

试图理解为什么当客户端关闭时服务器不会被注意到,我继续查看参考源上的DisposeLocalCopyOfClientHandle

// This method is an annoying one but it has to exist at least until we make passing handles between
// processes first class.  We need this because once the child handle is inherited, the OS considers
// the parent and child's handles to be different.  Therefore, if a child closes its handle, our 
// Read/Write methods won't throw because the OS will think that there is still a child handle around
// that can still Write/Read to/from the other end of the pipe.
//
// Ideally, we would want the Process class to close this handle after it has been inherited.  See
// the pipe spec future features section for more information.
// 
// Right now, this is the best signal to set the anonymous pipe as connected; if this is called, we
// know the client has been passed the handle and so the connection is live.
[System.Security.SecurityCritical]
public void DisposeLocalCopyOfClientHandle() {
    if (m_clientHandle != null && !m_clientHandle.IsClosed) {
       m_clientHandle.Dispose();
    }
}

这句话让我很困惑:

一旦子句柄被继承,操作系统就会认为父句柄和子句柄不同。

父母的句柄和孩子的句柄(即,服务器的m_handle服务器的 m_clientHandle,传递给孩子)一开始就不是不同的吗?这里的“不同”是指“引用不同的对象”(我是这样理解的),还是有其他含义?

【问题讨论】:

    标签: c# windows pipe


    【解决方案1】:

    在 .NET 中很难看到的晦涩细节是 CreateProcess()bInheritHandles 参数,这是一种在 winapi 中蔓延的令人讨厌的小统一主义。确定该参数的正确值非常很难推理,你必须对你开始的过程有很多了解,而且它的扩展性真的很差,这是一个全有或全无的选择。 Raymond Chen 有一个blog post 讨论丑陋的角落案例以及他们在 Windows 6.0 版中为解决问题所做的工作。

    不是可以在 .NET 中使用的解决方案。主要是因为它仍然支持直到 .NET 4.5 的旧 Windows 版本。而且它很难使用。因此,ProcessStartInfo 类没有允许您显式控制 bInheritHandles 参数值的属性,Process.Start() 始终传递 TRUE。这就是“直到我们在进程之间传递句柄第一类”评论所指的内容。

    更进一步的细节是,子进程继承的句柄是一个独立的句柄,与父进程的句柄不同。所以总共需要两次 CloseHandle 调用来销毁系统对象。或者换句话说,父母和孩子都需要停止使用该对象。这就是“操作系统认为父子句柄不同”的注释所指的内容。

    用于创建匿名管道的底层 CreatePipe() winapi 函数返回两个句柄,一个用于读取,一个用于写入。根据管道方向,一个应该由父进程(又名服务器)使用,一个由子进程(又名客户端)使用。这些句柄是可继承的句柄,因此在您启动子进程后,总共需要 四个 CloseHandle 调用来销毁管道对象。

    这很不愉快。 .NET 包装器可以对服务器句柄做一些事情。它调用DuplicateHandle() 来制作服务器端句柄的副本,为bInheritHandle 参数传递FALSE。然后关闭原始手柄。很好,子进程将不再继承服务器端句柄,因此现在只需要 三个 CloseHandle 调用。

    然而,同样的技巧不适用于子进程需要使用的管道句柄。毕竟,目的是让它继承句柄,以便它可以与服务器对话。这就是为什么您必须明确地这样做,之后您知道子进程已正确启动。在您的 DisposeLocalCopyOfClientHandle() 方法调用之后,现在只需要 两个 CloseHandle 调用。

    客户端的 CloseHandle 调用非常简单,它通过在 AnonymousPipeClientStream 上调用 Close 或 Dispose 来实现。或者通过导致进程崩溃的未处理异常而崩溃,然后操作系统负责关闭句柄。现在只剩下 一个 CloseHandle 调用了。

    一个去,服务器端更难。它只知道在收到子进程不再使用它的“通知”时关闭/处置其 AnonymousPipeServerStream。围绕“通知”的可怕引用,没有任何事件可以告诉您这一点。正确的方法是让子进程发送明确的“再见”消息,以便服务器知道调用 Close。不太正确但并不罕见的方式是孩子没有很好地告别,然后服务器只能从它继续使用管道时得到的异常中知道它不再存在。

    这是关键,只有当操作系统看到服务器尝试使用管道并且在另一侧没有打开剩余的句柄时,您才会得到异常。或者换句话说,如果你忘记调用 DisposeLocalCopyOfClientHandle() 那么你不会得到异常。不好。

    【讨论】:

      【解决方案2】:

      您的困惑源于服务器和客户端也是父进程和子进程的事实。管道句柄是服务器或客户端,但可以存在于父级和子级中。在服务器生成客户端之后但在调用 DisposeLocalCopyOfClientHandle 之前,有一小段时间,三个句柄在起作用:

      • 管道的服务器句柄,在服务器(父)进程中。
      • 管道的客户端句柄,在服务器(父)进程中。
      • 管道的客户端句柄,在客户端(子)进程中,继承自父进程。

      在子进程启动并运行后,需要关闭第二个句柄,因为正如评论所解释的,在所有客户端句柄都关闭之前,管道仍然可用。如果第二个句柄停留在附近,它将阻止服务器检测到子进程已完成。

      除了使用继承之外,实现还可以生成子进程并使用DuplicateHandle,这样就不需要这个辅助方法,因为原始句柄可以立即关闭。这大概就是“使[ing]在进程之间传递句柄一流”的意思。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-03-10
        • 1970-01-01
        • 1970-01-01
        • 2015-10-01
        • 1970-01-01
        • 1970-01-01
        • 2020-06-12
        • 1970-01-01
        相关资源
        最近更新 更多